Skip to content

Commit fff8a08

Browse files
fix: exponential backtracking on deeply nested bracketed expressions
Remove allowComplexParsing gates from ExpressionList and ParenthesedExpressionList so ComplexExpressionList (full Expression()) is always used. This lets Condition() handle all parenthesized content including comparison and boolean operators, eliminating the expensive speculative LOOKAHEAD(Condition()) and XorExpression fallback in AndExpression. Replace syntactic LOOKAHEAD(NamedExpressionListExprFirst()) in SpecialStringFunctionWithNamedParameters with a lightweight isNamedExprListAhead() token scan. Fixes parsing of deeply nested `IF()` and `POSITION(... IN (CASE ...))`. - fixes #2422 Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> Signed-off-by: manticore-projects <andreas@manticore-projects.com>
1 parent c0e1d05 commit fff8a08

File tree

1 file changed

+44
-35
lines changed

1 file changed

+44
-35
lines changed

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,39 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
597597
}
598598
}
599599

600+
/**
601+
* Lightweight lookahead for SpecialStringFunctionWithNamedParameters:
602+
* scans forward from the current position (just past the opening '(')
603+
* looking for FROM, IN, or PLACING at bracket nesting depth 0.
604+
*
605+
* This replaces an expensive syntactic LOOKAHEAD(NamedExpressionListExprFirst())
606+
* that caused exponential backtracking with deeply nested expressions.
607+
*/
608+
protected boolean isNamedExprListAhead() {
609+
int depth = 0;
610+
for (int i = 1; ; i++) {
611+
Token t = getToken(i);
612+
if (t.kind == EOF) {
613+
return false;
614+
}
615+
if (t.kind == OPENING_BRACKET) {
616+
depth++;
617+
} else if (t.kind == CLOSING_BRACKET) {
618+
if (depth == 0) {
619+
return false;
620+
}
621+
depth--;
622+
} else if (depth == 0) {
623+
if (t.kind == K_FROM || t.kind == K_IN || t.kind == K_PLACING) {
624+
return true;
625+
}
626+
if (t.image.equals(",")) {
627+
return false;
628+
}
629+
}
630+
}
631+
}
632+
600633
/**
601634
* Checks if the next token can start a condition suffix
602635
* (comparison, IN, BETWEEN, LIKE, IS NULL, etc.)
@@ -6675,45 +6708,21 @@ Expression OrExpression():
66756708
Expression AndExpression() :
66766709
{
66776710
Expression left, right, result;
6678-
boolean not = false;
6679-
boolean exclamationMarkNot=false;
66806711
}
66816712
{
6682-
(
6683-
// Fast path: when NOT starting with ( or NOT/!, Condition() always succeeds
6684-
// (PrimaryExpression can always match an identifier, literal, CASE, etc.)
6685-
// No speculative parsing needed — go directly.
6686-
LOOKAHEAD({ getToken(1).kind != OPENING_BRACKET
6687-
&& getToken(1).kind != K_NOT
6688-
&& !getToken(1).image.equals("!") })
6689-
left=Condition()
6690-
|
6691-
// Slow path: ( or NOT might introduce a parenthesized boolean expression
6692-
// that Condition() can't handle (because ParenthesedExpressionList uses
6693-
// SimpleExpression which doesn't support LIKE/IN/BETWEEN/IS).
6694-
// Try Condition() first (handles "( a + b ) > 5"), fall back to
6695-
// "(" XorExpression() ")" (handles "( value LIKE '%x%' )").
6696-
LOOKAHEAD(Condition(), {!interrupted}) left=Condition()
6697-
|
6698-
[ <K_NOT> { not=true; } | "!" { not=true; exclamationMarkNot=true; } ]
6699-
"(" left=XorExpression() ")" {left = new ParenthesedExpressionList(left); if (not) { left = new NotExpression(left, exclamationMarkNot); not = false; } }
6700-
)
6713+
// ParenthesedExpressionList always delegates to ComplexExpressionList which
6714+
// uses full Expression(), so Condition() can handle ALL parenthesized content
6715+
// (including boolean operators like LIKE/IN/BETWEEN/IS inside parens).
6716+
// No speculative parsing or XorExpression fallback needed.
6717+
left=Condition()
67016718
{ result = left; }
67026719

67036720
( LOOKAHEAD(2)
67046721
{ boolean useOperator = false; }
67056722
(<K_AND> | <OP_DOUBLEAND> {useOperator=true;} )
6706-
(
6707-
LOOKAHEAD({ getToken(1).kind != OPENING_BRACKET
6708-
&& getToken(1).kind != K_NOT
6709-
&& !getToken(1).image.equals("!") })
6710-
right=Condition()
6711-
|
6712-
LOOKAHEAD(Condition(), {!interrupted}) right=Condition()
6713-
|
6714-
[ <K_NOT> { not=true; } | "!" { not=true; exclamationMarkNot=true; } ]
6715-
"(" right=XorExpression() ")" {right = new ParenthesedExpressionList(right); if (not) { right = new NotExpression(right, exclamationMarkNot); not = false; } }
6716-
)
6723+
6724+
right=Condition()
6725+
67176726
{
67186727
result = new AndExpression(left, right);
67196728
((AndExpression)result).setUseOperator(useOperator);
@@ -7164,7 +7173,7 @@ ExpressionList ExpressionList() #ExpressionList:
71647173
}
71657174
{
71667175
(
7167-
LOOKAHEAD(3, { getAsBoolean(Feature.allowComplexParsing) && !interrupted}) expressionList = ComplexExpressionList()
7176+
LOOKAHEAD(3, { !interrupted }) expressionList = ComplexExpressionList()
71687177
|
71697178
LOOKAHEAD(3) expressionList = SimpleExpressionList()
71707179
|
@@ -7202,7 +7211,7 @@ ParenthesedExpressionList ParenthesedExpressionList():
72027211
{
72037212
"("
72047213
(
7205-
LOOKAHEAD({ getAsBoolean(Feature.allowComplexParsing) && !interrupted}) expressions = ComplexExpressionList()
7214+
LOOKAHEAD({ !interrupted }) expressions = ComplexExpressionList()
72067215
|
72077216
expressions = SimpleExpressionList()
72087217
)?
@@ -9300,7 +9309,7 @@ Function SpecialStringFunctionWithNamedParameters() :
93009309

93019310
"("
93029311
(
9303-
LOOKAHEAD( NamedExpressionListExprFirst() , { getAsBoolean(Feature.allowComplexParsing) }) namedExpressionList = NamedExpressionListExprFirst()
9312+
LOOKAHEAD( { isNamedExprListAhead() } ) namedExpressionList = NamedExpressionListExprFirst()
93049313
|
93059314
expressionList=ExpressionList()
93069315
)

0 commit comments

Comments
 (0)