* Improve Painless compilation performance for nested conditionals (#52056) This PR changes how conditional expression is handled in `PainlessParser` in a way that avoids the need for backtracking, which led to exponential compilation times in case of nested conditionals. The test was added ensures that we can compile deeply nested conditionals. Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> * Fix Map.of in Java8 Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
c827f6f440
commit
8cf47aca7e
|
@ -98,25 +98,29 @@ trap
|
|||
: CATCH LP TYPE ID RP block
|
||||
;
|
||||
|
||||
noncondexpression
|
||||
: unary # single
|
||||
| noncondexpression ( MUL | DIV | REM ) noncondexpression # binary
|
||||
| noncondexpression ( ADD | SUB ) noncondexpression # binary
|
||||
| noncondexpression ( FIND | MATCH ) noncondexpression # binary
|
||||
| noncondexpression ( LSH | RSH | USH ) noncondexpression # binary
|
||||
| noncondexpression ( LT | LTE | GT | GTE ) noncondexpression # comp
|
||||
| noncondexpression INSTANCEOF decltype # instanceof
|
||||
| noncondexpression ( EQ | EQR | NE | NER ) noncondexpression # comp
|
||||
| noncondexpression BWAND noncondexpression # binary
|
||||
| noncondexpression XOR noncondexpression # binary
|
||||
| noncondexpression BWOR noncondexpression # binary
|
||||
| noncondexpression BOOLAND noncondexpression # bool
|
||||
| noncondexpression BOOLOR noncondexpression # bool
|
||||
| <assoc=right> noncondexpression ELVIS noncondexpression # elvis
|
||||
;
|
||||
|
||||
expression
|
||||
: unary # single
|
||||
| expression ( MUL | DIV | REM ) expression # binary
|
||||
| expression ( ADD | SUB ) expression # binary
|
||||
| expression ( FIND | MATCH ) expression # binary
|
||||
| expression ( LSH | RSH | USH ) expression # binary
|
||||
| expression ( LT | LTE | GT | GTE ) expression # comp
|
||||
| expression INSTANCEOF decltype # instanceof
|
||||
| expression ( EQ | EQR | NE | NER ) expression # comp
|
||||
| expression BWAND expression # binary
|
||||
| expression XOR expression # binary
|
||||
| expression BWOR expression # binary
|
||||
| expression BOOLAND expression # bool
|
||||
| expression BOOLOR expression # bool
|
||||
| <assoc=right> expression COND expression COLON expression # conditional
|
||||
| <assoc=right> expression ELVIS expression # elvis
|
||||
| <assoc=right> expression ( ASSIGN | AADD | ASUB | AMUL |
|
||||
ADIV | AREM | AAND | AXOR |
|
||||
AOR | ALSH | ARSH | AUSH ) expression # assignment
|
||||
: noncondexpression # nonconditional
|
||||
| <assoc=right> noncondexpression COND expression COLON expression # conditional
|
||||
| <assoc=right> noncondexpression ( ASSIGN | AADD | ASUB | AMUL |
|
||||
ADIV | AREM | AAND | AXOR |
|
||||
AOR | ALSH | ARSH | AUSH ) expression # assignment
|
||||
;
|
||||
|
||||
unary
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
// ANTLR GENERATED CODE: DO NOT EDIT
|
||||
package org.elasticsearch.painless.antlr;
|
||||
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.Lexer;
|
||||
import org.antlr.v4.runtime.RuleContext;
|
||||
import org.antlr.v4.runtime.RuntimeMetaData;
|
||||
import org.antlr.v4.runtime.Vocabulary;
|
||||
import org.antlr.v4.runtime.VocabularyImpl;
|
||||
import org.antlr.v4.runtime.atn.ATN;
|
||||
import org.antlr.v4.runtime.atn.ATNDeserializer;
|
||||
import org.antlr.v4.runtime.atn.LexerATNSimulator;
|
||||
import org.antlr.v4.runtime.atn.PredictionContextCache;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenStream;
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.atn.*;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.misc.*;
|
||||
|
||||
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
|
||||
abstract class PainlessLexer extends Lexer {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -214,20 +214,6 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
|
|||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitBool(PainlessParser.BoolContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitConditional(PainlessParser.ConditionalContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitAssignment(PainlessParser.AssignmentContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -249,6 +235,27 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
|
|||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitInstanceof(PainlessParser.InstanceofContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitNonconditional(PainlessParser.NonconditionalContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitConditional(PainlessParser.ConditionalContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitAssignment(PainlessParser.AssignmentContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
|
|
@ -181,25 +181,53 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
|
|||
T visitTrap(PainlessParser.TrapContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code single}
|
||||
* labeled alternative in {@link PainlessParser#expression}.
|
||||
* labeled alternative in {@link PainlessParser#noncondexpression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitSingle(PainlessParser.SingleContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code comp}
|
||||
* labeled alternative in {@link PainlessParser#expression}.
|
||||
* labeled alternative in {@link PainlessParser#noncondexpression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitComp(PainlessParser.CompContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code bool}
|
||||
* labeled alternative in {@link PainlessParser#expression}.
|
||||
* labeled alternative in {@link PainlessParser#noncondexpression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitBool(PainlessParser.BoolContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code binary}
|
||||
* labeled alternative in {@link PainlessParser#noncondexpression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitBinary(PainlessParser.BinaryContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code elvis}
|
||||
* labeled alternative in {@link PainlessParser#noncondexpression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitElvis(PainlessParser.ElvisContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code instanceof}
|
||||
* labeled alternative in {@link PainlessParser#noncondexpression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitInstanceof(PainlessParser.InstanceofContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code nonconditional}
|
||||
* labeled alternative in {@link PainlessParser#expression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitNonconditional(PainlessParser.NonconditionalContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code conditional}
|
||||
* labeled alternative in {@link PainlessParser#expression}.
|
||||
|
@ -214,27 +242,6 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
|
|||
* @return the visitor result
|
||||
*/
|
||||
T visitAssignment(PainlessParser.AssignmentContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code binary}
|
||||
* labeled alternative in {@link PainlessParser#expression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitBinary(PainlessParser.BinaryContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code elvis}
|
||||
* labeled alternative in {@link PainlessParser#expression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitElvis(PainlessParser.ElvisContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code instanceof}
|
||||
* labeled alternative in {@link PainlessParser#expression}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitInstanceof(PainlessParser.InstanceofContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code pre}
|
||||
* labeled alternative in {@link PainlessParser#unary}.
|
||||
|
|
|
@ -519,8 +519,8 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
|
|||
|
||||
@Override
|
||||
public ANode visitBinary(BinaryContext ctx) {
|
||||
AExpression left = (AExpression)visit(ctx.expression(0));
|
||||
AExpression right = (AExpression)visit(ctx.expression(1));
|
||||
AExpression left = (AExpression)visit(ctx.noncondexpression(0));
|
||||
AExpression right = (AExpression)visit(ctx.noncondexpression(1));
|
||||
final Operation operation;
|
||||
|
||||
if (ctx.MUL() != null) {
|
||||
|
@ -558,8 +558,8 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
|
|||
|
||||
@Override
|
||||
public ANode visitComp(CompContext ctx) {
|
||||
AExpression left = (AExpression)visit(ctx.expression(0));
|
||||
AExpression right = (AExpression)visit(ctx.expression(1));
|
||||
AExpression left = (AExpression)visit(ctx.noncondexpression(0));
|
||||
AExpression right = (AExpression)visit(ctx.noncondexpression(1));
|
||||
final Operation operation;
|
||||
|
||||
if (ctx.LT() != null) {
|
||||
|
@ -587,7 +587,7 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
|
|||
|
||||
@Override
|
||||
public ANode visitInstanceof(InstanceofContext ctx) {
|
||||
AExpression expr = (AExpression)visit(ctx.expression());
|
||||
AExpression expr = (AExpression)visit(ctx.noncondexpression());
|
||||
String type = ctx.decltype().getText();
|
||||
|
||||
return new EInstanceof(location(ctx), expr, type);
|
||||
|
@ -595,8 +595,8 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
|
|||
|
||||
@Override
|
||||
public ANode visitBool(BoolContext ctx) {
|
||||
AExpression left = (AExpression)visit(ctx.expression(0));
|
||||
AExpression right = (AExpression)visit(ctx.expression(1));
|
||||
AExpression left = (AExpression)visit(ctx.noncondexpression(0));
|
||||
AExpression right = (AExpression)visit(ctx.noncondexpression(1));
|
||||
final Operation operation;
|
||||
|
||||
if (ctx.BOOLAND() != null) {
|
||||
|
@ -612,25 +612,25 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
|
|||
|
||||
@Override
|
||||
public ANode visitConditional(ConditionalContext ctx) {
|
||||
AExpression condition = (AExpression)visit(ctx.expression(0));
|
||||
AExpression left = (AExpression)visit(ctx.expression(1));
|
||||
AExpression right = (AExpression)visit(ctx.expression(2));
|
||||
AExpression condition = (AExpression)visit(ctx.noncondexpression());
|
||||
AExpression left = (AExpression)visit(ctx.expression(0));
|
||||
AExpression right = (AExpression)visit(ctx.expression(1));
|
||||
|
||||
return new EConditional(location(ctx), condition, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ANode visitElvis(ElvisContext ctx) {
|
||||
AExpression left = (AExpression)visit(ctx.expression(0));
|
||||
AExpression right = (AExpression)visit(ctx.expression(1));
|
||||
AExpression left = (AExpression)visit(ctx.noncondexpression(0));
|
||||
AExpression right = (AExpression)visit(ctx.noncondexpression(1));
|
||||
|
||||
return new EElvis(location(ctx), left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ANode visitAssignment(AssignmentContext ctx) {
|
||||
AExpression lhs = (AExpression)visit(ctx.expression(0));
|
||||
AExpression rhs = (AExpression)visit(ctx.expression(1));
|
||||
AExpression lhs = (AExpression)visit(ctx.noncondexpression());
|
||||
AExpression rhs = (AExpression)visit(ctx.expression());
|
||||
|
||||
final Operation operation;
|
||||
|
||||
|
|
|
@ -20,7 +20,11 @@
|
|||
package org.elasticsearch.painless;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
public class ConditionalTests extends ScriptTestCase {
|
||||
public void testBasic() {
|
||||
|
@ -86,4 +90,16 @@ public class ConditionalTests extends ScriptTestCase {
|
|||
exec("boolean x = false; int y = 2; byte z = x ? y : 7; return z;");
|
||||
});
|
||||
}
|
||||
|
||||
public void testNested() {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
String scriptPart = IntStream.range(0, i).mapToObj(j -> "field == '" + j + "' ? '" + j + "' :").collect(joining("\n"));
|
||||
assertEquals("z", exec("def field = params.a;\n" +
|
||||
"\n" +
|
||||
"return (\n" +
|
||||
scriptPart +
|
||||
"field == '' ? 'unknown' :\n" +
|
||||
"field);", Collections.singletonMap("a", "z"), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue