file_systems/QueryParser: Rewrite parsing logic to avoid recursion.

This way, we can handle parse trees of arbitrary depth without
running into stack overflows. Of course, evaluation is still
a problem...

While at it, use "const char*" everywhere, and also put the
query parser into an Init() function so we can return
more statuses than just B_BAD_VALUE.

Part of #18692.

Change-Id: Ib81e6545935ce484df10dfe36ca4ffcf2b3cd607
Reviewed-on: https://review.haiku-os.org/c/haiku/+/7710
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Augustin Cavalier
2024-06-24 19:40:08 +00:00
committed by waddlesplash
parent e0ff85edaa
commit 6d7e181767
3 changed files with 232 additions and 181 deletions
+214 -167
View File
@@ -8,19 +8,11 @@
#define _FILE_SYSTEMS_QUERY_PARSER_H #define _FILE_SYSTEMS_QUERY_PARSER_H
/*! Query parsing and evaluation /*! Query parsing and evaluation. */
The pattern matching is roughly based on code originally written
by J. Kercheval, and on code written by Kenneth Almquist, though
it shares no code.
*/
// The parser has a very static design, but it will do what is required. // The operator precedence is =,!=,>,<,>=,<= .. && .. ||.
// // Apparently, the "!" (not) can only be used with parentheses.
// ParseOr(), ParseAnd(), ParseEquation() are guarantying the operator
// precedence, that is =,!=,>,<,>=,<= .. && .. ||.
// Apparently, the "!" (not) can only be used with brackets.
// //
// If you think that there are too few NULL pointer checks in some places // If you think that there are too few NULL pointer checks in some places
// of the code, just read the beginning of the query constructor. // of the code, just read the beginning of the query constructor.
@@ -48,8 +40,6 @@
# include <util/Stack.h> # include <util/Stack.h>
# include <query_private.h> # include <query_private.h>
# include <lock.h>
#endif // !FS_SHELL #endif // !FS_SHELL
#include <file_systems/QueryParserUtils.h> #include <file_systems/QueryParserUtils.h>
@@ -242,13 +232,13 @@ public:
typedef typename QueryPolicy::Context Context; typedef typename QueryPolicy::Context Context;
public: public:
Equation(char** expression); Equation(const char** expression);
virtual ~Equation(); virtual ~Equation();
virtual status_t InitCheck(); virtual status_t InitCheck();
status_t ParseQuotedString(char** _start, char** _end); status_t ParseQuotedString(const char** _start, const char** _end);
char* CopyString(char* start, char* end); char* CopyString(const char* start, const char* end);
inline bool _IsEquationChar(char c) const; inline bool _IsEquationChar(char c) const;
inline bool _IsOperatorChar(char c) const; inline bool _IsOperatorChar(char c) const;
@@ -349,26 +339,21 @@ public:
typedef typename QueryPolicy::Context Context; typedef typename QueryPolicy::Context Context;
public: public:
Expression(char* expr); Expression();
~Expression(); ~Expression();
status_t InitCheck(); status_t Init(const char* expr, const char** position);
const char* Position() const { return fPosition; }
Term<QueryPolicy>* Root() const { return fTerm; } Term<QueryPolicy>* Root() const { return fTerm; }
protected: protected:
Term<QueryPolicy>* ParseOr(char** expr); bool IsOperator(const char** expr, char op);
Term<QueryPolicy>* ParseAnd(char** expr);
Term<QueryPolicy>* ParseEquation(char** expr);
bool IsOperator(char** expr, char op);
private: private:
Expression(const Expression& other); Expression(const Expression& other);
Expression& operator=(const Expression& other); Expression& operator=(const Expression& other);
// no implementation // no implementation
char* fPosition;
Term<QueryPolicy>* fTerm; Term<QueryPolicy>* fTerm;
}; };
@@ -377,7 +362,7 @@ private:
template<typename QueryPolicy> template<typename QueryPolicy>
Equation<QueryPolicy>::Equation(char** expr) Equation<QueryPolicy>::Equation(const char** expr)
: :
Term<QueryPolicy>(OP_EQUATION), Term<QueryPolicy>(OP_EQUATION),
fAttribute(NULL), fAttribute(NULL),
@@ -385,9 +370,9 @@ Equation<QueryPolicy>::Equation(char** expr)
fType(0), fType(0),
fIsPattern(false) fIsPattern(false)
{ {
char* string = *expr; const char* string = *expr;
char* start = string; const char* start = string;
char* end = NULL; const char* end = NULL;
// Since the equation is the integral part of any query, we're just parsing // Since the equation is the integral part of any query, we're just parsing
// the whole thing here. // the whole thing here.
@@ -530,11 +515,11 @@ Equation<QueryPolicy>::InitCheck()
template<typename QueryPolicy> template<typename QueryPolicy>
status_t status_t
Equation<QueryPolicy>::ParseQuotedString(char** _start, char** _end) Equation<QueryPolicy>::ParseQuotedString(const char** _start, const char** _end)
{ {
char* start = *_start; const char* start = *_start;
char quote = *start++; const char quote = *start++;
char* end = start; const char* end = start;
for (; *end && *end != quote; end++) { for (; *end && *end != quote; end++) {
if (*end == '\\') if (*end == '\\')
@@ -552,7 +537,7 @@ Equation<QueryPolicy>::ParseQuotedString(char** _start, char** _end)
template<typename QueryPolicy> template<typename QueryPolicy>
char* char*
Equation<QueryPolicy>::CopyString(char* start, char* end) Equation<QueryPolicy>::CopyString(const char* start, const char* end)
{ {
// end points to the last character of the string - and the length // end points to the last character of the string - and the length
// also has to include the null-termination // also has to include the null-termination
@@ -676,7 +661,7 @@ Equation<QueryPolicy>::CompareTo(const uint8* value, size_t size)
// we have already validated the pattern, so we don't check for failing // we have already validated the pattern, so we don't check for failing
// here - if something is broken, and matchString() returns an error, // here - if something is broken, and matchString() returns an error,
// we just don't match // we just don't match
compare = matchString(fValue.String, (char*)value) == MATCH_OK ? 0 : 1; compare = matchString(fValue.String, (const char*)value) == MATCH_OK ? 0 : 1;
} else } else
compare = compareKeys(fType, value, size, Value(), fSize); compare = compareKeys(fType, value, size, Value(), fSize);
@@ -703,7 +688,7 @@ template<typename QueryPolicy>
void void
Equation<QueryPolicy>::Complement() Equation<QueryPolicy>::Complement()
{ {
QUERY_D(if (fOp <= OP_EQUATION || fOp > OP_LESS_THAN_OR_EQUAL) { QUERY_D(if (Term<QueryPolicy>::fOp <= OP_EQUATION || Term<QueryPolicy>::fOp > OP_LESS_THAN_OR_EQUAL) {
QUERY_FATAL("op out of range!\n"); QUERY_FATAL("op out of range!\n");
return; return;
}); });
@@ -1178,27 +1163,202 @@ Equation<QueryPolicy>::PrintToStream()
template<typename QueryPolicy> template<typename QueryPolicy>
Expression<QueryPolicy>::Expression(char* expr) Expression<QueryPolicy>::Expression()
: :
fPosition(NULL),
fTerm(NULL) fTerm(NULL)
{ {
if (expr == NULL) }
return;
fTerm = ParseOr(&expr);
if (fTerm != NULL && fTerm->InitCheck() < B_OK) { template<typename QueryPolicy>
QUERY_FATAL("Corrupt tree in expression!\n"); status_t
delete fTerm; Expression<QueryPolicy>::Init(const char* expr, const char** position)
fTerm = NULL; {
if (expr == NULL)
return B_BAD_VALUE;
if (fTerm != NULL)
return EALREADY;
struct ExpressionNode {
Term<QueryPolicy>* term = NULL;
bool negated = false;
ops op = OP_NONE;
};
status_t status = B_OK;
Stack<Stack<ExpressionNode>*> exprsTree;
Stack<ExpressionNode>* currentExpr = NULL;
ExpressionNode* current = NULL;
while (expr != NULL) {
skipWhitespace(&expr);
if (currentExpr == NULL) {
currentExpr = new(std::nothrow) Stack<ExpressionNode>;
if (currentExpr == NULL) {
status = B_NO_MEMORY;
break;
}
}
const char c = *expr;
bool complete = false;
if (c == ')' || c == '\0') {
if (currentExpr->IsEmpty())
break; // Illegal empty expression.
complete = true;
}
if (current == NULL && !complete) {
currentExpr->Push(ExpressionNode());
current = currentExpr->Array() + (currentExpr->CountItems() - 1);
}
if (c == '(') {
exprsTree.Push(currentExpr);
currentExpr = NULL;
current = NULL;
expr++;
} else if (c == '!') {
skipWhitespace(&expr, 1);
if (*expr != '(')
break; // Not allowed.
current->negated = true;
} else if (c == '|' || c == '&') {
if (current->term == NULL)
break; // Nothing to operate on.
ops op = OP_NONE;
if (IsOperator(&expr, '|'))
op = OP_OR;
else if (IsOperator(&expr, '&'))
op = OP_AND;
else
break; // Illegal operator.
current->op = op;
current = NULL;
} else if (!complete) {
if (current->term != NULL)
break; // There already is a term.
Equation<QueryPolicy>* equation
= new(std::nothrow) Equation<QueryPolicy>(&expr);
if (equation == NULL) {
status = B_NO_MEMORY;
break;
}
if (equation == NULL || equation->InitCheck() != B_OK) {
status = equation->InitCheck();
delete equation;
break;
}
current->term = equation;
}
if (!complete)
continue;
if (currentExpr->CountItems() == 1) {
if (current == NULL)
break; // Probably an anomalous operator.
}
// First pass: negation.
for (int32 i = 0; i < currentExpr->CountItems(); i++) {
current = currentExpr->Array() + i;
// If the term is negated, we just complement the tree, to get
// rid of the not, a.k.a. DeMorgan's Law.
if (current->negated) {
current->term->Complement();
current->negated = false;
}
}
// Second & third passes: && and ||.
int32 nodes = currentExpr->CountItems();
for (ops op = OP_AND; op <= OP_OR; op = (ops)(op + 1)) {
for (int32 i = 0; i < (currentExpr->CountItems() - 1); i++) {
ExpressionNode* left = currentExpr->Array() + i;
if (left->op != op)
continue;
// Find the right-hand expression (may have to jump over now-unused nodes.)
ExpressionNode* right = NULL;
for (int32 j = i + 1; j < currentExpr->CountItems(); j++) {
current = currentExpr->Array() + j;
if (current->term == NULL)
continue;
right = current;
break;
}
if (right == NULL)
break; // Invalid expression, somehow.
Term<QueryPolicy>* newTerm = new(std::nothrow) Operator<QueryPolicy>(
left->term, left->op, right->term);
if (newTerm == NULL) {
status = B_NO_MEMORY;
break;
}
left->term = newTerm;
left->op = right->op;
right->op = OP_NONE;
right->term = NULL;
nodes--;
}
}
// At this point we should have only one node left.
if (nodes != 1)
break;
current = currentExpr->Array();
Term<QueryPolicy>* term = current->term;
delete currentExpr;
currentExpr = NULL;
if (exprsTree.Pop(&currentExpr)) {
current = currentExpr->Array() + (currentExpr->CountItems() - 1);
if (current->term != NULL)
break; // There already is a term.
current->term = term;
} else {
if (c != '\0')
break; // Unexpected end of expression.
fTerm = term;
break;
}
expr++;
} }
if (position != NULL)
*position = expr;
do {
if (currentExpr == NULL)
continue;
ExpressionNode item;
while (currentExpr->Pop(&item))
delete item.term;
delete currentExpr;
} while (exprsTree.Pop(&currentExpr));
if (fTerm == NULL && status == B_OK)
return B_BAD_VALUE;
QUERY_D(if (fTerm != NULL) { QUERY_D(if (fTerm != NULL) {
fTerm->PrintToStream(); fTerm->PrintToStream();
QUERY_D(__out("\n")); QUERY_D(__out("\n"));
if (*expr != '\0') if (*expr != '\0')
PRINT(("Unexpected end of string: \"%s\"!\n", expr)); PRINT(("Unexpected end of string: \"%s\"!\n", expr));
}); });
fPosition = expr;
return status;
} }
@@ -1209,115 +1369,11 @@ Expression<QueryPolicy>::~Expression()
} }
template<typename QueryPolicy>
Term<QueryPolicy>*
Expression<QueryPolicy>::ParseEquation(char** expr)
{
skipWhitespace(expr);
bool _not = false;
if (**expr == '!') {
skipWhitespace(expr, 1);
if (**expr != '(')
return NULL;
_not = true;
}
if (**expr == ')') {
// shouldn't be handled here
return NULL;
} else if (**expr == '(') {
skipWhitespace(expr, 1);
Term<QueryPolicy>* term = ParseOr(expr);
skipWhitespace(expr);
if (**expr != ')') {
delete term;
return NULL;
}
// If the term is negated, we just complement the tree, to get
// rid of the not, a.k.a. DeMorgan's Law.
if (_not)
term->Complement();
skipWhitespace(expr, 1);
return term;
}
Equation<QueryPolicy>* equation
= new(std::nothrow) Equation<QueryPolicy>(expr);
if (equation == NULL || equation->InitCheck() < B_OK) {
delete equation;
return NULL;
}
return equation;
}
template<typename QueryPolicy>
Term<QueryPolicy>*
Expression<QueryPolicy>::ParseAnd(char** expr)
{
Term<QueryPolicy>* left = ParseEquation(expr);
if (left == NULL)
return NULL;
while (IsOperator(expr, '&')) {
Term<QueryPolicy>* right = ParseAnd(expr);
Term<QueryPolicy>* newParent = NULL;
if (right == NULL
|| (newParent = new(std::nothrow) Operator<QueryPolicy>(left,
OP_AND, right)) == NULL) {
delete left;
delete right;
return NULL;
}
left = newParent;
}
return left;
}
template<typename QueryPolicy>
Term<QueryPolicy>*
Expression<QueryPolicy>::ParseOr(char** expr)
{
Term<QueryPolicy>* left = ParseAnd(expr);
if (left == NULL)
return NULL;
while (IsOperator(expr, '|')) {
Term<QueryPolicy>* right = ParseAnd(expr);
Term<QueryPolicy>* newParent = NULL;
if (right == NULL
|| (newParent = new(std::nothrow) Operator<QueryPolicy>(left, OP_OR,
right)) == NULL) {
delete left;
delete right;
return NULL;
}
left = newParent;
}
return left;
}
template<typename QueryPolicy> template<typename QueryPolicy>
bool bool
Expression<QueryPolicy>::IsOperator(char** expr, char op) Expression<QueryPolicy>::IsOperator(const char** expr, char op)
{ {
char* string = *expr; const char* string = *expr;
if (*string == op && *(string + 1) == op) { if (*string == op && *(string + 1) == op) {
*expr += 2; *expr += 2;
@@ -1327,17 +1383,6 @@ Expression<QueryPolicy>::IsOperator(char** expr, char op)
} }
template<typename QueryPolicy>
status_t
Expression<QueryPolicy>::InitCheck()
{
if (fTerm == NULL)
return B_BAD_VALUE;
return B_OK;
}
// #pragma mark - // #pragma mark -
@@ -1384,16 +1429,18 @@ Query<QueryPolicy>::Create(Context* context, const char* queryString,
uint32 flags, port_id port, uint32 token, Query<QueryPolicy>*& _query) uint32 flags, port_id port, uint32 token, Query<QueryPolicy>*& _query)
{ {
Expression<QueryPolicy>* expression Expression<QueryPolicy>* expression
= new(std::nothrow) Expression<QueryPolicy>((char*)queryString); = new(std::nothrow) Expression<QueryPolicy>;
if (expression == NULL) if (expression == NULL)
QUERY_RETURN_ERROR(B_NO_MEMORY); QUERY_RETURN_ERROR(B_NO_MEMORY);
if (expression->InitCheck() != B_OK) { const char* position = NULL;
status_t status = expression->Init(queryString, &position);
if (status != B_OK) {
QUERY_INFORM("Could not parse query \"%s\", stopped at: \"%s\"\n", QUERY_INFORM("Could not parse query \"%s\", stopped at: \"%s\"\n",
queryString, expression->Position()); queryString, position);
delete expression; delete expression;
QUERY_RETURN_ERROR(B_BAD_VALUE); QUERY_RETURN_ERROR(status);
} }
Query<QueryPolicy>* query = new(std::nothrow) Query<QueryPolicy>(context, Query<QueryPolicy>* query = new(std::nothrow) Query<QueryPolicy>(context,
@@ -42,14 +42,14 @@ enum {
__BEGIN_DECLS __BEGIN_DECLS
void skipWhitespace(char** expr, int32 skip = 0); void skipWhitespace(const char** expr, int32 skip = 0);
void skipWhitespaceReverse(char** expr, char* stop); void skipWhitespaceReverse(const char** expr, const char* stop);
int compareKeys(uint32 type, const void* key1, size_t length1, int compareKeys(uint32 type, const void* key1, size_t length1,
const void* key2, size_t length2); const void* key2, size_t length2);
uint32 utf8ToUnicode(char** string); uint32 utf8ToUnicode(const char** string);
int32 getFirstPatternSymbol(char* string); int32 getFirstPatternSymbol(const char* string);
status_t isValidPattern(char* pattern); status_t isValidPattern(const char* pattern);
status_t matchString(char* pattern, char* string); status_t matchString(const char* pattern, const char* string);
__END_DECLS __END_DECLS
@@ -37,18 +37,18 @@ compare_integral(const Key& a, const Key& b)
void void
skipWhitespace(char** expr, int32 skip) skipWhitespace(const char** expr, int32 skip)
{ {
char* string = (*expr) + skip; const char* string = (*expr) + skip;
while (*string == ' ' || *string == '\t') string++; while (*string == ' ' || *string == '\t') string++;
*expr = string; *expr = string;
} }
void void
skipWhitespaceReverse(char** expr, char* stop) skipWhitespaceReverse(const char** expr, const char* stop)
{ {
char* string = *expr; const char* string = *expr;
while (string > stop && (*string == ' ' || *string == '\t')) while (string > stop && (*string == ' ' || *string == '\t'))
string--; string--;
*expr = string; *expr = string;
@@ -92,7 +92,7 @@ compareKeys(uint32 type, const void* key1, size_t length1, const void* key2,
uint32 uint32
utf8ToUnicode(char** string) utf8ToUnicode(const char** string)
{ {
uint8* bytes = (uint8*)*string; uint8* bytes = (uint8*)*string;
int32 length; int32 length;
@@ -132,7 +132,7 @@ utf8ToUnicode(char** string)
int32 int32
getFirstPatternSymbol(char* string) getFirstPatternSymbol(const char* string)
{ {
char c; char c;
@@ -145,7 +145,7 @@ getFirstPatternSymbol(char* string)
status_t status_t
isValidPattern(char* pattern) isValidPattern(const char* pattern)
{ {
while (*pattern) { while (*pattern) {
switch (*pattern++) { switch (*pattern++) {
@@ -181,9 +181,13 @@ isValidPattern(char* pattern)
/*! Matches the string against the given wildcard pattern. /*! Matches the string against the given wildcard pattern.
Returns either MATCH_OK, or NO_MATCH when everything went fine, or Returns either MATCH_OK, or NO_MATCH when everything went fine, or
values < 0 (see enum at the top of Query.cpp) if an error occurs. values < 0 (see enum at the top of Query.cpp) if an error occurs.
The pattern matching is roughly based on code originally written
by J. Kercheval, and on code written by Kenneth Almquist, though
it shares no code.
*/ */
status_t status_t
matchString(char* pattern, char* string) matchString(const char* pattern, const char* string)
{ {
while (*pattern) { while (*pattern) {
// end of string == valid end of pattern? // end of string == valid end of pattern?