[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
;
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

View File

@ -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 {

View File

@ -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}
*

View File

@ -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}.

View File

@ -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;

View File

@ -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));
}
}
}