mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-09 06:25:07 +00:00
* 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
|
: 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
|
||||||
|
@ -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 {
|
||||||
|
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>
|
* {@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}
|
||||||
*
|
*
|
||||||
|
@ -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}.
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user