[7.x] Improve Painless compilation performance for nested conditionals (#52056) (#52074)

* 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:
Przemko Robakowski 2020-02-07 21:13:25 +01:00 committed by GitHub
parent c827f6f440
commit 8cf47aca7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1124 additions and 1053 deletions

View File

@ -98,25 +98,29 @@ trap
: CATCH LP TYPE ID RP block : 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 expression
: unary # single : noncondexpression # nonconditional
| expression ( MUL | DIV | REM ) expression # binary | <assoc=right> noncondexpression COND expression COLON expression # conditional
| expression ( ADD | SUB ) expression # binary | <assoc=right> noncondexpression ( ASSIGN | AADD | ASUB | AMUL |
| expression ( FIND | MATCH ) expression # binary ADIV | AREM | AAND | AXOR |
| expression ( LSH | RSH | USH ) expression # binary AOR | ALSH | ARSH | AUSH ) expression # assignment
| 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
; ;
unary unary

View File

@ -1,17 +1,13 @@
// ANTLR GENERATED CODE: DO NOT EDIT // ANTLR GENERATED CODE: DO NOT EDIT
package org.elasticsearch.painless.antlr; package org.elasticsearch.painless.antlr;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.RuleContext; import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.RuntimeMetaData; import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.Vocabulary; import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.VocabularyImpl; import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.atn.ATN; import org.antlr.v4.runtime.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.dfa.DFA; import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.misc.*;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
abstract class PainlessLexer extends Lexer { abstract class PainlessLexer extends Lexer {

View File

@ -214,20 +214,6 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p> * {@link #visitChildren} on {@code ctx}.</p>
*/ */
@Override public T visitBool(PainlessParser.BoolContext ctx) { return visitChildren(ctx); } @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} * {@inheritDoc}
* *
@ -249,6 +235,27 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p> * {@link #visitChildren} on {@code ctx}.</p>
*/ */
@Override public T visitInstanceof(PainlessParser.InstanceofContext ctx) { return visitChildren(ctx); } @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} * {@inheritDoc}
* *

View File

@ -181,25 +181,53 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
T visitTrap(PainlessParser.TrapContext ctx); T visitTrap(PainlessParser.TrapContext ctx);
/** /**
* Visit a parse tree produced by the {@code single} * 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 * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
T visitSingle(PainlessParser.SingleContext ctx); T visitSingle(PainlessParser.SingleContext ctx);
/** /**
* Visit a parse tree produced by the {@code comp} * 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 * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
T visitComp(PainlessParser.CompContext ctx); T visitComp(PainlessParser.CompContext ctx);
/** /**
* Visit a parse tree produced by the {@code bool} * 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 * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
T visitBool(PainlessParser.BoolContext ctx); 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} * Visit a parse tree produced by the {@code conditional}
* labeled alternative in {@link PainlessParser#expression}. * labeled alternative in {@link PainlessParser#expression}.
@ -214,27 +242,6 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result * @return the visitor result
*/ */
T visitAssignment(PainlessParser.AssignmentContext ctx); 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} * Visit a parse tree produced by the {@code pre}
* labeled alternative in {@link PainlessParser#unary}. * labeled alternative in {@link PainlessParser#unary}.

View File

@ -519,8 +519,8 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
@Override @Override
public ANode visitBinary(BinaryContext ctx) { public ANode visitBinary(BinaryContext ctx) {
AExpression left = (AExpression)visit(ctx.expression(0)); AExpression left = (AExpression)visit(ctx.noncondexpression(0));
AExpression right = (AExpression)visit(ctx.expression(1)); AExpression right = (AExpression)visit(ctx.noncondexpression(1));
final Operation operation; final Operation operation;
if (ctx.MUL() != null) { if (ctx.MUL() != null) {
@ -558,8 +558,8 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
@Override @Override
public ANode visitComp(CompContext ctx) { public ANode visitComp(CompContext ctx) {
AExpression left = (AExpression)visit(ctx.expression(0)); AExpression left = (AExpression)visit(ctx.noncondexpression(0));
AExpression right = (AExpression)visit(ctx.expression(1)); AExpression right = (AExpression)visit(ctx.noncondexpression(1));
final Operation operation; final Operation operation;
if (ctx.LT() != null) { if (ctx.LT() != null) {
@ -587,7 +587,7 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
@Override @Override
public ANode visitInstanceof(InstanceofContext ctx) { public ANode visitInstanceof(InstanceofContext ctx) {
AExpression expr = (AExpression)visit(ctx.expression()); AExpression expr = (AExpression)visit(ctx.noncondexpression());
String type = ctx.decltype().getText(); String type = ctx.decltype().getText();
return new EInstanceof(location(ctx), expr, type); return new EInstanceof(location(ctx), expr, type);
@ -595,8 +595,8 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
@Override @Override
public ANode visitBool(BoolContext ctx) { public ANode visitBool(BoolContext ctx) {
AExpression left = (AExpression)visit(ctx.expression(0)); AExpression left = (AExpression)visit(ctx.noncondexpression(0));
AExpression right = (AExpression)visit(ctx.expression(1)); AExpression right = (AExpression)visit(ctx.noncondexpression(1));
final Operation operation; final Operation operation;
if (ctx.BOOLAND() != null) { if (ctx.BOOLAND() != null) {
@ -612,25 +612,25 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
@Override @Override
public ANode visitConditional(ConditionalContext ctx) { public ANode visitConditional(ConditionalContext ctx) {
AExpression condition = (AExpression)visit(ctx.expression(0)); AExpression condition = (AExpression)visit(ctx.noncondexpression());
AExpression left = (AExpression)visit(ctx.expression(1)); AExpression left = (AExpression)visit(ctx.expression(0));
AExpression right = (AExpression)visit(ctx.expression(2)); AExpression right = (AExpression)visit(ctx.expression(1));
return new EConditional(location(ctx), condition, left, right); return new EConditional(location(ctx), condition, left, right);
} }
@Override @Override
public ANode visitElvis(ElvisContext ctx) { public ANode visitElvis(ElvisContext ctx) {
AExpression left = (AExpression)visit(ctx.expression(0)); AExpression left = (AExpression)visit(ctx.noncondexpression(0));
AExpression right = (AExpression)visit(ctx.expression(1)); AExpression right = (AExpression)visit(ctx.noncondexpression(1));
return new EElvis(location(ctx), left, right); return new EElvis(location(ctx), left, right);
} }
@Override @Override
public ANode visitAssignment(AssignmentContext ctx) { public ANode visitAssignment(AssignmentContext ctx) {
AExpression lhs = (AExpression)visit(ctx.expression(0)); AExpression lhs = (AExpression)visit(ctx.noncondexpression());
AExpression rhs = (AExpression)visit(ctx.expression(1)); AExpression rhs = (AExpression)visit(ctx.expression());
final Operation operation; final Operation operation;

View File

@ -20,7 +20,11 @@
package org.elasticsearch.painless; package org.elasticsearch.painless;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.stream.IntStream;
import static java.util.stream.Collectors.joining;
public class ConditionalTests extends ScriptTestCase { public class ConditionalTests extends ScriptTestCase {
public void testBasic() { 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;"); 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));
}
}
} }