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
/*! 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.
*/
/*! Query parsing and evaluation. */
// The parser has a very static design, but it will do what is required.
//
// ParseOr(), ParseAnd(), ParseEquation() are guarantying the operator
// precedence, that is =,!=,>,<,>=,<= .. && .. ||.
// Apparently, the "!" (not) can only be used with brackets.
// The operator precedence is =,!=,>,<,>=,<= .. && .. ||.
// Apparently, the "!" (not) can only be used with parentheses.
//
// 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.
@@ -48,8 +40,6 @@
# include <util/Stack.h>
# include <query_private.h>
# include <lock.h>
#endif // !FS_SHELL
#include <file_systems/QueryParserUtils.h>
@@ -242,13 +232,13 @@ public:
typedef typename QueryPolicy::Context Context;
public:
Equation(char** expression);
Equation(const char** expression);
virtual ~Equation();
virtual status_t InitCheck();
status_t ParseQuotedString(char** _start, char** _end);
char* CopyString(char* start, char* end);
status_t ParseQuotedString(const char** _start, const char** _end);
char* CopyString(const char* start, const char* end);
inline bool _IsEquationChar(char c) const;
inline bool _IsOperatorChar(char c) const;
@@ -349,26 +339,21 @@ public:
typedef typename QueryPolicy::Context Context;
public:
Expression(char* expr);
Expression();
~Expression();
status_t InitCheck();
const char* Position() const { return fPosition; }
status_t Init(const char* expr, const char** position);
Term<QueryPolicy>* Root() const { return fTerm; }
protected:
Term<QueryPolicy>* ParseOr(char** expr);
Term<QueryPolicy>* ParseAnd(char** expr);
Term<QueryPolicy>* ParseEquation(char** expr);
bool IsOperator(char** expr, char op);
bool IsOperator(const char** expr, char op);
private:
Expression(const Expression& other);
Expression& operator=(const Expression& other);
// no implementation
char* fPosition;
Term<QueryPolicy>* fTerm;
};
@@ -377,7 +362,7 @@ private:
template<typename QueryPolicy>
Equation<QueryPolicy>::Equation(char** expr)
Equation<QueryPolicy>::Equation(const char** expr)
:
Term<QueryPolicy>(OP_EQUATION),
fAttribute(NULL),
@@ -385,9 +370,9 @@ Equation<QueryPolicy>::Equation(char** expr)
fType(0),
fIsPattern(false)
{
char* string = *expr;
char* start = string;
char* end = NULL;
const char* string = *expr;
const char* start = string;
const char* end = NULL;
// Since the equation is the integral part of any query, we're just parsing
// the whole thing here.
@@ -530,11 +515,11 @@ Equation<QueryPolicy>::InitCheck()
template<typename QueryPolicy>
status_t
Equation<QueryPolicy>::ParseQuotedString(char** _start, char** _end)
Equation<QueryPolicy>::ParseQuotedString(const char** _start, const char** _end)
{
char* start = *_start;
char quote = *start++;
char* end = start;
const char* start = *_start;
const char quote = *start++;
const char* end = start;
for (; *end && *end != quote; end++) {
if (*end == '\\')
@@ -552,7 +537,7 @@ Equation<QueryPolicy>::ParseQuotedString(char** _start, char** _end)
template<typename QueryPolicy>
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
// 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
// here - if something is broken, and matchString() returns an error,
// 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
compare = compareKeys(fType, value, size, Value(), fSize);
@@ -703,7 +688,7 @@ template<typename QueryPolicy>
void
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");
return;
});
@@ -1178,27 +1163,202 @@ Equation<QueryPolicy>::PrintToStream()
template<typename QueryPolicy>
Expression<QueryPolicy>::Expression(char* expr)
Expression<QueryPolicy>::Expression()
:
fPosition(NULL),
fTerm(NULL)
{
if (expr == NULL)
return;
}
fTerm = ParseOr(&expr);
if (fTerm != NULL && fTerm->InitCheck() < B_OK) {
QUERY_FATAL("Corrupt tree in expression!\n");
delete fTerm;
fTerm = NULL;
template<typename QueryPolicy>
status_t
Expression<QueryPolicy>::Init(const char* expr, const char** position)
{
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) {
fTerm->PrintToStream();
QUERY_D(__out("\n"));
if (*expr != '\0')
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>
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) {
*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 -
@@ -1384,16 +1429,18 @@ Query<QueryPolicy>::Create(Context* context, const char* queryString,
uint32 flags, port_id port, uint32 token, Query<QueryPolicy>*& _query)
{
Expression<QueryPolicy>* expression
= new(std::nothrow) Expression<QueryPolicy>((char*)queryString);
= new(std::nothrow) Expression<QueryPolicy>;
if (expression == NULL)
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",
queryString, expression->Position());
queryString, position);
delete expression;
QUERY_RETURN_ERROR(B_BAD_VALUE);
QUERY_RETURN_ERROR(status);
}
Query<QueryPolicy>* query = new(std::nothrow) Query<QueryPolicy>(context,
@@ -42,14 +42,14 @@ enum {
__BEGIN_DECLS
void skipWhitespace(char** expr, int32 skip = 0);
void skipWhitespaceReverse(char** expr, char* stop);
void skipWhitespace(const char** expr, int32 skip = 0);
void skipWhitespaceReverse(const char** expr, const char* stop);
int compareKeys(uint32 type, const void* key1, size_t length1,
const void* key2, size_t length2);
uint32 utf8ToUnicode(char** string);
int32 getFirstPatternSymbol(char* string);
status_t isValidPattern(char* pattern);
status_t matchString(char* pattern, char* string);
uint32 utf8ToUnicode(const char** string);
int32 getFirstPatternSymbol(const char* string);
status_t isValidPattern(const char* pattern);
status_t matchString(const char* pattern, const char* string);
__END_DECLS
@@ -37,18 +37,18 @@ compare_integral(const Key& a, const Key& b)
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++;
*expr = string;
}
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'))
string--;
*expr = string;
@@ -92,7 +92,7 @@ compareKeys(uint32 type, const void* key1, size_t length1, const void* key2,
uint32
utf8ToUnicode(char** string)
utf8ToUnicode(const char** string)
{
uint8* bytes = (uint8*)*string;
int32 length;
@@ -132,7 +132,7 @@ utf8ToUnicode(char** string)
int32
getFirstPatternSymbol(char* string)
getFirstPatternSymbol(const char* string)
{
char c;
@@ -145,7 +145,7 @@ getFirstPatternSymbol(char* string)
status_t
isValidPattern(char* pattern)
isValidPattern(const char* pattern)
{
while (*pattern) {
switch (*pattern++) {
@@ -181,9 +181,13 @@ isValidPattern(char* pattern)
/*! Matches the string against the given wildcard pattern.
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.
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
matchString(char* pattern, char* string)
matchString(const char* pattern, const char* string)
{
while (*pattern) {
// end of string == valid end of pattern?