Refactored Painless link nodes into expression nodes to simplify

load/store operations of variable/method chains.

Closes #19459
This commit is contained in:
Jack Conradson 2016-07-27 14:01:08 -07:00
parent fb45f6a8a8
commit 9d87314105
91 changed files with 4619 additions and 4543 deletions

View File

@ -95,80 +95,95 @@ delimiter
| EOF
;
// Note we return the boolean s. This is returned as true
// if secondaries (postfixes) are allowed, otherwise, false.
// This prevents illegal secondaries from being appended to
// expressions using precedence that aren't variable/method chains.
expression returns [boolean s = true]
: u = unary[false] { $s = $u.s; } # single
| expression ( MUL | DIV | REM ) expression { $s = false; } # binary
| expression ( ADD | SUB ) expression { $s = false; } # binary
| expression ( FIND | MATCH ) expression { $s = false; } # binary
| expression ( LSH | RSH | USH ) expression { $s = false; } # binary
| expression ( LT | LTE | GT | GTE ) expression { $s = false; } # comp
| expression INSTANCEOF decltype { $s = false; } # instanceof
| expression ( EQ | EQR | NE | NER ) expression { $s = false; } # comp
| expression BWAND expression { $s = false; } # binary
| expression XOR expression { $s = false; } # binary
| expression BWOR expression { $s = false; } # binary
| expression BOOLAND expression { $s = false; } # bool
| expression BOOLOR expression { $s = false; } # bool
| <assoc=right> expression COND e0 = expression COLON e1 = expression { $s = $e0.s && $e1.s; } # conditional
// TODO: Should we allow crazy syntax like (x = 5).call()?
// Other crazy syntaxes work, but this one requires
// a complete restructure of the rules as EChain isn't
// designed to handle more postfixes after an assignment.
| <assoc=right> chain[true] ( ASSIGN | AADD | ASUB | AMUL |
ADIV | AREM | AAND | AXOR |
AOR | ALSH | ARSH | AUSH ) expression { $s = false; } # assignment
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 ( ASSIGN | AADD | ASUB | AMUL |
ADIV | AREM | AAND | AXOR |
AOR | ALSH | ARSH | AUSH ) expression # assignment
;
// Note we take in the boolean c. This is used to indicate
// whether or not this rule was called when we are already
// processing a variable/method chain. This prevents the chain
// from being applied to rules where it wouldn't be allowed.
unary[boolean c] returns [boolean s = true]
: { !$c }? ( INCR | DECR ) chain[true] # pre
| { !$c }? chain[true] (INCR | DECR ) # post
| { !$c }? chain[false] # read
| { !$c }? ( OCTAL | HEX | INTEGER | DECIMAL ) { $s = false; } # numeric
| { !$c }? TRUE { $s = false; } # true
| { !$c }? FALSE { $s = false; } # false
| { !$c }? NULL { $s = false; } # null
| { !$c }? listinitializer { $s = false; } # listinit
| { !$c }? mapinitializer { $s = false; } # mapinit
| { !$c }? ( BOOLNOT | BWNOT | ADD | SUB ) unary[false] # operator
| LP decltype RP unary[$c] # cast
unary
: ( INCR | DECR ) chain # pre
| chain (INCR | DECR ) # post
| chain # read
| ( BOOLNOT | BWNOT | ADD | SUB ) unary # operator
| LP decltype RP unary # cast
;
chain[boolean c]
: p = primary[$c] secondary[$p.s]* # dynamic
| decltype dot secondary[true]* # static
| arrayinitializer # newarray
chain
: primary postfix* # dynamic
| decltype postdot postfix* # static
| arrayinitializer # newarray
;
primary[boolean c] returns [boolean s = true]
: { !$c }? LP e = expression RP { $s = $e.s; } # exprprec
| { $c }? LP unary[true] RP # chainprec
| STRING # string
| REGEX # regex
| ID # variable
| ID arguments # calllocal
| NEW TYPE arguments # newobject
primary
: LP expression RP # precedence
| ( OCTAL | HEX | INTEGER | DECIMAL ) # numeric
| TRUE # true
| FALSE # false
| NULL # null
| STRING # string
| REGEX # regex
| listinitializer # listinit
| mapinitializer # mapinit
| ID # variable
| ID arguments # calllocal
| NEW TYPE arguments # newobject
;
secondary[boolean s]
: { $s }? dot
| { $s }? brace
postfix
: callinvoke
| fieldaccess
| braceaccess
;
dot
: DOT DOTID arguments # callinvoke
| DOT ( DOTID | DOTINTEGER ) # fieldaccess
postdot
: callinvoke
| fieldaccess
;
brace
: LBRACE expression RBRACE # braceaccess
callinvoke
: DOT DOTID arguments
;
fieldaccess
: DOT ( DOTID | DOTINTEGER )
;
braceaccess
: LBRACE expression RBRACE
;
arrayinitializer
: NEW TYPE ( LBRACE expression RBRACE )+ ( postdot postfix* )? # newstandardarray
| NEW TYPE LBRACE RBRACE LBRACK ( expression ( COMMA expression )* )? SEMICOLON? RBRACK postfix* # newinitializedarray
;
listinitializer
: LBRACE expression ( COMMA expression)* RBRACE
| LBRACE RBRACE
;
mapinitializer
: LBRACE maptoken ( COMMA maptoken )* RBRACE
| LBRACE COLON RBRACE
;
maptoken
: expression COLON expression
;
arguments
@ -190,49 +205,10 @@ lamtype
;
funcref
: classFuncref
| constructorFuncref
| capturingFuncref
| localFuncref
: TYPE REF ID # classfuncref // reference to a static or instance method,
// e.g. ArrayList::size or Integer::compare
| decltype REF NEW # constructorfuncref // reference to a constructor, e.g. ArrayList::new
| ID REF ID # capturingfuncref // reference to an instance method, e.g. object::toString
// currently limited to capture of a simple variable (id).
| THIS REF ID # localfuncref // reference to a local function, e.g. this::myfunc
;
// reference to a static or instance method, e.g. ArrayList::size or Integer::compare
classFuncref
: TYPE REF ID
;
// reference to a constructor, e.g. ArrayList::new
// currently limited to simple non-array types
constructorFuncref
: decltype REF NEW
;
// reference to an instance method, e.g. object::toString
// currently limited to capture of a simple variable (id).
capturingFuncref
: ID REF ID
;
// reference to a local function, e.g. this::myfunc
localFuncref
: THIS REF ID
;
arrayinitializer
: NEW TYPE (LBRACE expression RBRACE)+ (dot secondary[true]*)? # newstandardarray
| NEW TYPE LBRACE RBRACE LBRACK ( expression ( COMMA expression )* )? SEMICOLON? RBRACK # newinitializedarray
;
listinitializer
: LBRACE expression ( COMMA expression)* RBRACE
| LBRACE RBRACE
;
mapinitializer
: LBRACE maptoken ( COMMA maptoken )* RBRACE
| LBRACE COLON RBRACE
;
maptoken
: expression COLON expression
;

View File

@ -91,43 +91,47 @@ public final class Definition {
public static final Type MATCHER_TYPE = getType("Matcher");
public enum Sort {
VOID( void.class , 0 , true , false , false , false ),
BOOL( boolean.class , 1 , true , true , false , true ),
BYTE( byte.class , 1 , true , false , true , true ),
SHORT( short.class , 1 , true , false , true , true ),
CHAR( char.class , 1 , true , false , true , true ),
INT( int.class , 1 , true , false , true , true ),
LONG( long.class , 2 , true , false , true , true ),
FLOAT( float.class , 1 , true , false , true , true ),
DOUBLE( double.class , 2 , true , false , true , true ),
VOID( void.class , Void.class , null , 0 , true , false , false , false ),
BOOL( boolean.class , Boolean.class , null , 1 , true , true , false , true ),
BYTE( byte.class , Byte.class , null , 1 , true , false , true , true ),
SHORT( short.class , Short.class , null , 1 , true , false , true , true ),
CHAR( char.class , Character.class , null , 1 , true , false , true , true ),
INT( int.class , Integer.class , null , 1 , true , false , true , true ),
LONG( long.class , Long.class , null , 2 , true , false , true , true ),
FLOAT( float.class , Float.class , null , 1 , true , false , true , true ),
DOUBLE( double.class , Double.class , null , 2 , true , false , true , true ),
VOID_OBJ( Void.class , 1 , true , false , false , false ),
BOOL_OBJ( Boolean.class , 1 , false , true , false , false ),
BYTE_OBJ( Byte.class , 1 , false , false , true , false ),
SHORT_OBJ( Short.class , 1 , false , false , true , false ),
CHAR_OBJ( Character.class , 1 , false , false , true , false ),
INT_OBJ( Integer.class , 1 , false , false , true , false ),
LONG_OBJ( Long.class , 1 , false , false , true , false ),
FLOAT_OBJ( Float.class , 1 , false , false , true , false ),
DOUBLE_OBJ( Double.class , 1 , false , false , true , false ),
VOID_OBJ( Void.class , null , void.class , 1 , true , false , false , false ),
BOOL_OBJ( Boolean.class , null , boolean.class , 1 , false , true , false , false ),
BYTE_OBJ( Byte.class , null , byte.class , 1 , false , false , true , false ),
SHORT_OBJ( Short.class , null , short.class , 1 , false , false , true , false ),
CHAR_OBJ( Character.class , null , char.class , 1 , false , false , true , false ),
INT_OBJ( Integer.class , null , int.class , 1 , false , false , true , false ),
LONG_OBJ( Long.class , null , long.class , 1 , false , false , true , false ),
FLOAT_OBJ( Float.class , null , float.class , 1 , false , false , true , false ),
DOUBLE_OBJ( Double.class , null , double.class , 1 , false , false , true , false ),
NUMBER( Number.class , 1 , false , false , false , false ),
STRING( String.class , 1 , false , false , false , true ),
NUMBER( Number.class , null , null , 1 , false , false , false , false ),
STRING( String.class , null , null , 1 , false , false , false , true ),
OBJECT( null , 1 , false , false , false , false ),
DEF( null , 1 , false , false , false , false ),
ARRAY( null , 1 , false , false , false , false );
OBJECT( null , null , null , 1 , false , false , false , false ),
DEF( null , null , null , 1 , false , false , false , false ),
ARRAY( null , null , null , 1 , false , false , false , false );
public final Class<?> clazz;
public final Class<?> boxed;
public final Class<?> unboxed;
public final int size;
public final boolean primitive;
public final boolean bool;
public final boolean numeric;
public final boolean constant;
Sort(final Class<?> clazz, final int size, final boolean primitive,
final boolean bool, final boolean numeric, final boolean constant) {
Sort(final Class<?> clazz, final Class<?> boxed, final Class<?> unboxed, final int size,
final boolean primitive, final boolean bool, final boolean numeric, final boolean constant) {
this.clazz = clazz;
this.boxed = boxed;
this.unboxed = unboxed;
this.size = size;
this.bool = bool;
this.primitive = primitive;
@ -204,8 +208,8 @@ public final class Definition {
this.modifiers = modifiers;
this.handle = handle;
}
/**
/**
* Returns MethodType for this method.
* <p>
* This works even for user-defined Methods (where the MethodHandle is null).
@ -252,7 +256,7 @@ public final class Definition {
}
return MethodType.methodType(returnValue, params);
}
public void write(MethodWriter writer) {
final org.objectweb.asm.Type type;
if (augmentation) {
@ -803,7 +807,7 @@ public final class Definition {
final Class<?> implClass;
final Class<?>[] params;
if (augmentation == false) {
implClass = owner.clazz;
params = new Class<?>[args.length];
@ -818,7 +822,7 @@ public final class Definition {
params[count+1] = args[count].clazz;
}
}
final java.lang.reflect.Method reflect;
try {

View File

@ -1,49 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.antlr;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.LexerNoViableAltException;
import org.antlr.v4.runtime.misc.Interval;
import org.elasticsearch.painless.Location;
/**
* A lexer that will override the default error behavior to fail on the first error.
*/
final class ErrorHandlingLexer extends PainlessLexer {
final String sourceName;
ErrorHandlingLexer(CharStream charStream, String sourceName) {
super(charStream);
this.sourceName = sourceName;
// Replace the TokenFactory with a stashing wrapper so we can do token-level lookbehind for regex detection
_factory = new StashingTokenFactory<>(_factory);
}
@Override
public void recover(final LexerNoViableAltException lnvae) {
final CharStream charStream = lnvae.getInputStream();
final int startIndex = lnvae.getStartIndex();
final String text = charStream.getText(Interval.of(startIndex, charStream.index()));
Location location = new Location(sourceName, _tokenStartCharIndex);
throw location.createError(new IllegalArgumentException("unexpected character [" + getErrorDisplay(text) + "].", lnvae));
}
}

View File

@ -263,48 +263,6 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitRead(PainlessParser.ReadContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitNumeric(PainlessParser.NumericContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitTrue(PainlessParser.TrueContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitFalse(PainlessParser.FalseContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitNull(PainlessParser.NullContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitListinit(PainlessParser.ListinitContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitMapinit(PainlessParser.MapinitContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
@ -346,14 +304,35 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitExprprec(PainlessParser.ExprprecContext ctx) { return visitChildren(ctx); }
@Override public T visitPrecedence(PainlessParser.PrecedenceContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitChainprec(PainlessParser.ChainprecContext ctx) { return visitChildren(ctx); }
@Override public T visitNumeric(PainlessParser.NumericContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitTrue(PainlessParser.TrueContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitFalse(PainlessParser.FalseContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitNull(PainlessParser.NullContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
@ -368,6 +347,20 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitRegex(PainlessParser.RegexContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitListinit(PainlessParser.ListinitContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitMapinit(PainlessParser.MapinitContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
@ -395,7 +388,14 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitSecondary(PainlessParser.SecondaryContext ctx) { return visitChildren(ctx); }
@Override public T visitPostfix(PainlessParser.PostfixContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitPostdot(PainlessParser.PostdotContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
@ -417,69 +417,6 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitBraceaccess(PainlessParser.BraceaccessContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitArguments(PainlessParser.ArgumentsContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitArgument(PainlessParser.ArgumentContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLambda(PainlessParser.LambdaContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLamtype(PainlessParser.LamtypeContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitFuncref(PainlessParser.FuncrefContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitClassFuncref(PainlessParser.ClassFuncrefContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitConstructorFuncref(PainlessParser.ConstructorFuncrefContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitCapturingFuncref(PainlessParser.CapturingFuncrefContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLocalFuncref(PainlessParser.LocalFuncrefContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
@ -515,4 +452,60 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitMaptoken(PainlessParser.MaptokenContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitArguments(PainlessParser.ArgumentsContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitArgument(PainlessParser.ArgumentContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLambda(PainlessParser.LambdaContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLamtype(PainlessParser.LamtypeContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitClassfuncref(PainlessParser.ClassfuncrefContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitConstructorfuncref(PainlessParser.ConstructorfuncrefContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitCapturingfuncref(PainlessParser.CapturingfuncrefContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLocalfuncref(PainlessParser.LocalfuncrefContext ctx) { return visitChildren(ctx); }
}

View File

@ -249,48 +249,6 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitRead(PainlessParser.ReadContext ctx);
/**
* Visit a parse tree produced by the {@code numeric}
* labeled alternative in {@link PainlessParser#unary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitNumeric(PainlessParser.NumericContext ctx);
/**
* Visit a parse tree produced by the {@code true}
* labeled alternative in {@link PainlessParser#unary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitTrue(PainlessParser.TrueContext ctx);
/**
* Visit a parse tree produced by the {@code false}
* labeled alternative in {@link PainlessParser#unary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitFalse(PainlessParser.FalseContext ctx);
/**
* Visit a parse tree produced by the {@code null}
* labeled alternative in {@link PainlessParser#unary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitNull(PainlessParser.NullContext ctx);
/**
* Visit a parse tree produced by the {@code listinit}
* labeled alternative in {@link PainlessParser#unary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitListinit(PainlessParser.ListinitContext ctx);
/**
* Visit a parse tree produced by the {@code mapinit}
* labeled alternative in {@link PainlessParser#unary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitMapinit(PainlessParser.MapinitContext ctx);
/**
* Visit a parse tree produced by the {@code operator}
* labeled alternative in {@link PainlessParser#unary}.
@ -327,19 +285,40 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
*/
T visitNewarray(PainlessParser.NewarrayContext ctx);
/**
* Visit a parse tree produced by the {@code exprprec}
* Visit a parse tree produced by the {@code precedence}
* labeled alternative in {@link PainlessParser#primary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitExprprec(PainlessParser.ExprprecContext ctx);
T visitPrecedence(PainlessParser.PrecedenceContext ctx);
/**
* Visit a parse tree produced by the {@code chainprec}
* Visit a parse tree produced by the {@code numeric}
* labeled alternative in {@link PainlessParser#primary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitChainprec(PainlessParser.ChainprecContext ctx);
T visitNumeric(PainlessParser.NumericContext ctx);
/**
* Visit a parse tree produced by the {@code true}
* labeled alternative in {@link PainlessParser#primary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitTrue(PainlessParser.TrueContext ctx);
/**
* Visit a parse tree produced by the {@code false}
* labeled alternative in {@link PainlessParser#primary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitFalse(PainlessParser.FalseContext ctx);
/**
* Visit a parse tree produced by the {@code null}
* labeled alternative in {@link PainlessParser#primary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitNull(PainlessParser.NullContext ctx);
/**
* Visit a parse tree produced by the {@code string}
* labeled alternative in {@link PainlessParser#primary}.
@ -354,6 +333,20 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitRegex(PainlessParser.RegexContext ctx);
/**
* Visit a parse tree produced by the {@code listinit}
* labeled alternative in {@link PainlessParser#primary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitListinit(PainlessParser.ListinitContext ctx);
/**
* Visit a parse tree produced by the {@code mapinit}
* labeled alternative in {@link PainlessParser#primary}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitMapinit(PainlessParser.MapinitContext ctx);
/**
* Visit a parse tree produced by the {@code variable}
* labeled alternative in {@link PainlessParser#primary}.
@ -376,86 +369,35 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
*/
T visitNewobject(PainlessParser.NewobjectContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#secondary}.
* Visit a parse tree produced by {@link PainlessParser#postfix}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitSecondary(PainlessParser.SecondaryContext ctx);
T visitPostfix(PainlessParser.PostfixContext ctx);
/**
* Visit a parse tree produced by the {@code callinvoke}
* labeled alternative in {@link PainlessParser#dot}.
* Visit a parse tree produced by {@link PainlessParser#postdot}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitPostdot(PainlessParser.PostdotContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#callinvoke}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitCallinvoke(PainlessParser.CallinvokeContext ctx);
/**
* Visit a parse tree produced by the {@code fieldaccess}
* labeled alternative in {@link PainlessParser#dot}.
* Visit a parse tree produced by {@link PainlessParser#fieldaccess}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitFieldaccess(PainlessParser.FieldaccessContext ctx);
/**
* Visit a parse tree produced by the {@code braceaccess}
* labeled alternative in {@link PainlessParser#brace}.
* Visit a parse tree produced by {@link PainlessParser#braceaccess}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitBraceaccess(PainlessParser.BraceaccessContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#arguments}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitArguments(PainlessParser.ArgumentsContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#argument}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitArgument(PainlessParser.ArgumentContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#lambda}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitLambda(PainlessParser.LambdaContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#lamtype}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitLamtype(PainlessParser.LamtypeContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#funcref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitFuncref(PainlessParser.FuncrefContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#classFuncref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitClassFuncref(PainlessParser.ClassFuncrefContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#constructorFuncref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitConstructorFuncref(PainlessParser.ConstructorFuncrefContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#capturingFuncref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitCapturingFuncref(PainlessParser.CapturingFuncrefContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#localFuncref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitLocalFuncref(PainlessParser.LocalFuncrefContext ctx);
/**
* Visit a parse tree produced by the {@code newstandardarray}
* labeled alternative in {@link PainlessParser#arrayinitializer}.
@ -488,4 +430,56 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitMaptoken(PainlessParser.MaptokenContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#arguments}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitArguments(PainlessParser.ArgumentsContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#argument}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitArgument(PainlessParser.ArgumentContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#lambda}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitLambda(PainlessParser.LambdaContext ctx);
/**
* Visit a parse tree produced by {@link PainlessParser#lamtype}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitLamtype(PainlessParser.LamtypeContext ctx);
/**
* Visit a parse tree produced by the {@code classfuncref}
* labeled alternative in {@link PainlessParser#funcref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitClassfuncref(PainlessParser.ClassfuncrefContext ctx);
/**
* Visit a parse tree produced by the {@code constructorfuncref}
* labeled alternative in {@link PainlessParser#funcref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitConstructorfuncref(PainlessParser.ConstructorfuncrefContext ctx);
/**
* Visit a parse tree produced by the {@code capturingfuncref}
* labeled alternative in {@link PainlessParser#funcref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitCapturingfuncref(PainlessParser.CapturingfuncrefContext ctx);
/**
* Visit a parse tree produced by the {@code localfuncref}
* labeled alternative in {@link PainlessParser#funcref}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitLocalfuncref(PainlessParser.LocalfuncrefContext ctx);
}

View File

@ -19,39 +19,47 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Location;
import java.util.Objects;
/**
* The superclass for all E* (expression) nodes.
* The superclass for all E* (expression) and P* (postfix) nodes.
*/
public abstract class AExpression extends ANode {
/**
* Prefix is the predecessor to this node in a variable chain.
* This is used to analyze and write variable chains in a
* more natural order since the parent node of a variable
* chain will want the data from the final postfix to be
* analyzed.
*/
AExpression prefix;
/**
* Set to false when an expression will not be read from such as
* a basic assignment. Note this variable is always set by the parent
* as input.
*/
protected boolean read = true;
boolean read = true;
/**
* Set to true when an expression can be considered a stand alone
* statement. Used to prevent extraneous bytecode. This is always
* set by the node as output.
*/
protected boolean statement = false;
boolean statement = false;
/**
* Set to the expected type this node needs to be. Note this variable
* is always set by the parent as input and should never be read from.
*/
protected Type expected = null;
Type expected = null;
/**
* Set to the actual type this node is. Note this variable is always
@ -59,19 +67,19 @@ public abstract class AExpression extends ANode {
* node itself. <b>Also, actual can always be read after a cast is
* called on this node to get the type of the node after the cast.</b>
*/
protected Type actual = null;
Type actual = null;
/**
* Set by {@link EExplicit} if a cast made on an expression node should be
* explicit.
*/
protected boolean explicit = false;
boolean explicit = false;
/**
* Set to true if a cast is allowed to boxed/unboxed. This is used
* for method arguments because casting may be required.
*/
protected boolean internal = false;
boolean internal = false;
/**
* Set to the value of the constant this expression node represents if
@ -79,40 +87,30 @@ public abstract class AExpression extends ANode {
* this node will be replaced by an {@link EConstant} during casting
* if it's not already one.
*/
protected Object constant = null;
Object constant = null;
/**
* Set to true by {@link ENull} to represent a null value.
*/
protected boolean isNull = false;
boolean isNull = false;
/**
* If an expression represents a branch statement, represents the jump should
* the expression evaluate to a true value. It should always be the case that only
* one of tru and fals are non-null or both are null. Only used during the writing phase.
* Standard constructor with location used for error tracking.
*/
protected Label tru = null;
/**
* If an expression represents a branch statement, represents the jump should
* the expression evaluate to a false value. It should always be the case that only
* one of tru and fals are non-null or both are null. Only used during the writing phase.
*/
protected Label fals = null;
public AExpression(Location location) {
AExpression(Location location) {
super(location);
prefix = null;
}
/**
* Checks for errors and collects data for the writing phase.
* This constructor is used by variable/method chains when postfixes are specified.
*/
abstract void analyze(Locals locals);
AExpression(Location location, AExpression prefix) {
super(location);
/**
* Writes ASM based on the data collected during the analysis phase.
*/
abstract void write(MethodWriter writer, Globals globals);
this.prefix = Objects.requireNonNull(prefix);
}
/**
* Inserts {@link ECast} nodes into the tree for implicit casts. Also replaces
@ -120,7 +118,7 @@ public abstract class AExpression extends ANode {
* @return The new child node for the parent node calling this method.
*/
AExpression cast(Locals locals) {
final Cast cast = AnalyzerCaster.getLegalCast(location, actual, expected, explicit, internal);
Cast cast = AnalyzerCaster.getLegalCast(location, actual, expected, explicit, internal);
if (cast == null) {
if (constant == null || this instanceof EConstant) {

View File

@ -1,121 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
* The superclass for all L* (link) nodes.
*/
public abstract class ALink extends ANode {
/**
* Size is set to a value based on this link's size on the stack. This is
* used during the writing phase to dup stack values from this link as
* necessary during certain store operations.
*/
final int size;
/**
* Set to false only if the link is not going to be read from.
*/
boolean load = true;
/**
* Set to true only if the link is going to be written to and
* is the final link in a chain.
*/
boolean store = false;
/**
* Set to true if this link represents a statik type to be accessed.
*/
boolean statik = false;
/**
* Set by the parent chain to type of the previous link or null if
* there was no previous link.
*/
Type before = null;
/**
* Set by the link to be the type after the link has been loaded/stored.
*/
Type after = null;
/**
* Set to true if this link could be a stand-alone statement.
*/
boolean statement = false;
/**
* Used by {@link LString} to set the value of the String constant. Also
* used by shortcuts to represent a constant key.
*/
String string = null;
ALink(Location location, int size) {
super(location);
this.size = size;
}
/**
* Checks for errors and collects data for the writing phase.
* @return Possibly returns a different {@link ALink} node if a type is
* def or a shortcut is used. Otherwise, returns itself. This will be
* updated into the {@link EChain} node's list of links.
*/
abstract ALink analyze(Locals locals);
/**
* Write values before a load/store occurs such as an array index.
*/
abstract void write(MethodWriter writer, Globals globals);
/**
* Write a load for the specific link type.
*/
abstract void load(MethodWriter writer, Globals globals);
/**
* Write a store for the specific link type.
*/
abstract void store(MethodWriter writer, Globals globals);
/**
* Used to copy link data from one to another during analysis in the case of replacement.
*/
final ALink copy(ALink link) {
load = link.load;
store = link.store;
statik = link.statik;
before = link.before;
after = link.after;
statement = link.statement;
string = link.string;
return this;
}
}

View File

@ -19,24 +19,31 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
import java.util.Set;
/**
* The superclass for all other nodes.
* The superclass for all nodes.
*/
public abstract class ANode {
/**
* The identifier of the script and character offset used for debugging and errors.
*/
final Location location;
/**
* Standard constructor with location used for error tracking.
*/
ANode(Location location) {
this.location = Objects.requireNonNull(location);
}
/**
* Adds all variable names referenced to the variable set.
* <p>
@ -44,8 +51,18 @@ public abstract class ANode {
* @param variables set of variables referenced (any scope)
*/
abstract void extractVariables(Set<String> variables);
public RuntimeException createError(RuntimeException exception) {
/**
* Checks for errors and collects data for the writing phase.
*/
abstract void analyze(Locals locals);
/**
* Writes ASM based on the data collected during the analysis phase.
*/
abstract void write(MethodWriter writer, Globals globals);
RuntimeException createError(RuntimeException exception) {
return location.createError(exception);
}
}

View File

@ -19,12 +19,9 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
/**
* The superclass for all S* (statement) nodes.
@ -110,17 +107,10 @@ public abstract class AStatement extends ANode {
*/
Label brake = null;
/**
* Standard constructor with location used for error tracking.
*/
AStatement(Location location) {
super(location);
}
/**
* Checks for errors and collects data for the writing phase.
*/
abstract void analyze(Locals locals);
/**
* Writes ASM based on the data collected during the analysis phase.
*/
abstract void write(MethodWriter writer, Globals globals);
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
/**
* The super class for an expression that can store a value in local memory.
*/
abstract class AStoreable extends AExpression {
/**
* Set to true when this node is an lhs-expression and will be storing
* a value from an rhs-expression.
*/
boolean write = false;
/**
* Standard constructor with location used for error tracking.
*/
AStoreable(Location location) {
super(location);
prefix = null;
}
/**
* This constructor is used by variable/method chains when postfixes are specified.
*/
AStoreable(Location location, AExpression prefix) {
super(location);
this.prefix = Objects.requireNonNull(prefix);
}
/**
* Returns a value based on the number of elements previously placed on the
* stack to load/store a certain piece of a variable/method chain. This is
* used during the writing phase to dup stack values from this storeable as
* necessary during certain store operations.
* <p>
* Examples:
* {@link EVariable} returns 0 because it requires nothing extra to perform
* a load/store
* {@link PSubField} returns 1 because it requires the name of the field as
* an index on the stack to perform a load/store
* {@link PSubBrace} returns 2 because it requires both the variable slot and
* an index into the array on the stack to perform a
* load/store
*/
abstract int accessElementCount();
/**
* Returns true if this node or a sub-node of this node can be optimized with
* rhs actual type to avoid an unnecessary cast.
*/
abstract boolean isDefOptimized();
/**
* If this node or a sub-node of this node uses dynamic calls then
* actual will be set to this value. This is used for an optimization
* during assignment to def type targets.
*/
abstract void updateActual(Type actual);
/**
* Called before a storeable node is loaded or stored. Used to load prefixes and
* push load/store constants onto the stack if necessary.
*/
abstract void setup(MethodWriter writer, Globals globals);
/**
* Called to load a storable used for compound assignments.
*/
abstract void load(MethodWriter writer, Globals globals);
/**
* Called to store a storabable to local memory.
*/
abstract void store(MethodWriter writer, Globals globals);
}

View File

@ -0,0 +1,325 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Operation;
import java.util.Objects;
import java.util.Set;
/**
* Represents an assignment with the lhs and rhs as child nodes.
*/
public final class EAssignment extends AExpression {
private AExpression lhs;
private AExpression rhs;
private final boolean pre;
private final boolean post;
private Operation operation;
private boolean cat = false;
private Type promote = null;
private Type shiftDistance; // for shifts, the RHS is promoted independently
private Cast there = null;
private Cast back = null;
public EAssignment(Location location, AExpression lhs, AExpression rhs, boolean pre, boolean post, Operation operation) {
super(location);
this.lhs = Objects.requireNonNull(lhs);
this.rhs = rhs;
this.pre = pre;
this.post = post;
this.operation = operation;
}
@Override
void extractVariables(Set<String> variables) {
lhs.extractVariables(variables);
rhs.extractVariables(variables);
}
@Override
void analyze(Locals locals) {
analyzeLHS(locals);
analyzeIncrDecr();
if (operation != null) {
analyzeCompound(locals);
} else if (rhs != null) {
analyzeSimple(locals);
} else {
throw new IllegalStateException("Illegal tree structure.");
}
}
private void analyzeLHS(Locals locals) {
if (lhs instanceof AStoreable) {
AStoreable lhs = (AStoreable)this.lhs;
lhs.read = read;
lhs.write = true;
lhs.analyze(locals);
} else {
throw new IllegalArgumentException("Left-hand side cannot be assigned a value.");
}
}
private void analyzeIncrDecr() {
if (pre && post) {
throw createError(new IllegalStateException("Illegal tree structure."));
} else if (pre || post) {
if (rhs != null) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
Sort sort = lhs.actual.sort;
if (operation == Operation.INCR) {
if (sort == Sort.DOUBLE) {
rhs = new EConstant(location, 1D);
} else if (sort == Sort.FLOAT) {
rhs = new EConstant(location, 1F);
} else if (sort == Sort.LONG) {
rhs = new EConstant(location, 1L);
} else {
rhs = new EConstant(location, 1);
}
operation = Operation.ADD;
} else if (operation == Operation.DECR) {
if (sort == Sort.DOUBLE) {
rhs = new EConstant(location, 1D);
} else if (sort == Sort.FLOAT) {
rhs = new EConstant(location, 1F);
} else if (sort == Sort.LONG) {
rhs = new EConstant(location, 1L);
} else {
rhs = new EConstant(location, 1);
}
operation = Operation.SUB;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeCompound(Locals locals) {
rhs.analyze(locals);
boolean shift = false;
if (operation == Operation.MUL) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
} else if (operation == Operation.DIV) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
} else if (operation == Operation.REM) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
} else if (operation == Operation.ADD) {
promote = AnalyzerCaster.promoteAdd(lhs.actual, rhs.actual);
} else if (operation == Operation.SUB) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
} else if (operation == Operation.LSH) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
shift = true;
} else if (operation == Operation.RSH) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
shift = true;
} else if (operation == Operation.USH) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
shift = true;
} else if (operation == Operation.BWAND) {
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
} else if (operation == Operation.XOR) {
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
} else if (operation == Operation.BWOR) {
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
if (promote == null || (shift && shiftDistance == null)) {
throw createError(new ClassCastException("Cannot apply compound assignment " +
"[" + operation.symbol + "=] to types [" + lhs.actual + "] and [" + rhs.actual + "]."));
}
cat = operation == Operation.ADD && promote.sort == Sort.STRING;
if (cat) {
if (rhs instanceof EBinary && ((EBinary)rhs).operation == Operation.ADD && rhs.actual.sort == Sort.STRING) {
((EBinary)rhs).cat = true;
}
rhs.expected = rhs.actual;
} else if (shift) {
if (promote.sort == Sort.DEF) {
// shifts are promoted independently, but for the def type, we need object.
rhs.expected = promote;
} else if (shiftDistance.sort == Sort.LONG) {
rhs.expected = Definition.INT_TYPE;
rhs.explicit = true;
} else {
rhs.expected = shiftDistance;
}
} else {
rhs.expected = promote;
}
rhs = rhs.cast(locals);
there = AnalyzerCaster.getLegalCast(location, lhs.actual, promote, false, false);
back = AnalyzerCaster.getLegalCast(location, promote, lhs.actual, true, false);
this.statement = true;
this.actual = read ? lhs.actual : Definition.VOID_TYPE;
}
private void analyzeSimple(Locals locals) {
AStoreable lhs = (AStoreable)this.lhs;
// If the lhs node is a def optimized node we update the actual type to remove the need for a cast.
if (lhs.isDefOptimized()) {
rhs.analyze(locals);
rhs.expected = rhs.actual;
lhs.updateActual(rhs.actual);
// Otherwise, we must adapt the rhs type to the lhs type with a cast.
} else {
rhs.expected = lhs.actual;
rhs.analyze(locals);
}
rhs = rhs.cast(locals);
this.statement = true;
this.actual = read ? lhs.actual : Definition.VOID_TYPE;
}
/**
* Handles writing byte code for variable/method chains for all given possibilities
* including String concatenation, compound assignment, regular assignment, and simple
* reads. Includes proper duplication for chained assignments and assignments that are
* also read from.
*/
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
// For the case where the assignment represents a String concatenation
// we must, depending on the Java version, write a StringBuilder or
// track types going onto the stack. This must be done before the
// lhs is read because we need the StringBuilder to be placed on the
// stack ahead of any potential concatenation arguments.
int catElementStackSize = 0;
if (cat) {
catElementStackSize = writer.writeNewStrings();
}
// Cast the lhs to a storeable to perform the necessary operations to store the rhs.
AStoreable lhs = (AStoreable)this.lhs;
lhs.setup(writer, globals); // call the setup method on the lhs to prepare for a load/store operation
if (cat) {
// Handle the case where we are doing a compound assignment
// representing a String concatenation.
writer.writeDup(lhs.accessElementCount(), catElementStackSize); // dup the top element and insert it
// before concat helper on stack
lhs.load(writer, globals); // read the current lhs's value
writer.writeAppendStrings(lhs.actual); // append the lhs's value using the StringBuilder
rhs.write(writer, globals); // write the bytecode for the rhs
if (!(rhs instanceof EBinary) || ((EBinary)rhs).cat) {
writer.writeAppendStrings(rhs.actual); // append the rhs's value unless it's also a concatenation
}
writer.writeToStrings(); // put the value for string concat onto the stack
writer.writeCast(back); // if necessary, cast the String to the lhs actual type
if (lhs.read) {
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // if this lhs is also read
// from dup the value onto the stack
}
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
} else if (operation != null) {
// Handle the case where we are doing a compound assignment that
// does not represent a String concatenation.
writer.writeDup(lhs.accessElementCount(), 0); // if necessary, dup the previous lhs's value
// to be both loaded from and stored to
lhs.load(writer, globals); // load the current lhs's value
if (lhs.read && post) {
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also
// read from and is a post increment
}
writer.writeCast(there); // if necessary cast the current lhs's value
// to the promotion type between the lhs and rhs types
rhs.write(writer, globals); // write the bytecode for the rhs
// XXX: fix these types, but first we need def compound assignment tests.
// its tricky here as there are possibly explicit casts, too.
// write the operation instruction for compound assignment
if (promote.sort == Sort.DEF) {
writer.writeDynamicBinaryInstruction(location, promote,
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
} else {
writer.writeBinaryInstruction(location, promote, operation);
}
writer.writeCast(back); // if necessary cast the promotion type value back to the lhs's type
if (lhs.read && !post) {
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also
// read from and is not a post increment
}
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
} else {
// Handle the case for a simple write.
rhs.write(writer, globals); // write the bytecode for the rhs rhs
if (lhs.read) {
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also read from
}
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
}
}
}

View File

@ -22,18 +22,17 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.WriterConstants;
import org.elasticsearch.painless.Locals;
import java.util.Objects;
import java.util.Set;
/**
* Represents a binary math expression.
@ -41,13 +40,13 @@ import org.elasticsearch.painless.Locals;
public final class EBinary extends AExpression {
final Operation operation;
AExpression left;
AExpression right;
Type promote; // promoted type
Type shiftDistance; // for shifts, the RHS is promoted independently
private AExpression left;
private AExpression right;
private Type promote = null; // promoted type
private Type shiftDistance = null; // for shifts, the rhs is promoted independently
boolean cat = false;
boolean originallyExplicit = false; // record whether there was originally an explicit cast
private boolean originallyExplicit = false; // record whether there was originally an explicit cast
public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
super(location);
@ -56,7 +55,7 @@ public final class EBinary extends AExpression {
this.left = Objects.requireNonNull(left);
this.right = Objects.requireNonNull(right);
}
@Override
void extractVariables(Set<String> variables) {
left.extractVariables(variables);
@ -66,6 +65,7 @@ public final class EBinary extends AExpression {
@Override
void analyze(Locals locals) {
originallyExplicit = explicit;
if (operation == Operation.MUL) {
analyzeMul(locals);
} else if (operation == Operation.DIV) {
@ -153,9 +153,11 @@ public final class EBinary extends AExpression {
}
actual = promote;
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
@ -182,8 +184,8 @@ public final class EBinary extends AExpression {
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
} catch (ArithmeticException e) {
throw createError(e);
} catch (ArithmeticException exception) {
throw createError(exception);
}
}
}
@ -204,6 +206,7 @@ public final class EBinary extends AExpression {
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
@ -230,8 +233,8 @@ public final class EBinary extends AExpression {
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
} catch (ArithmeticException e) {
throw createError(e);
} catch (ArithmeticException exception) {
throw createError(exception);
}
}
}
@ -266,6 +269,7 @@ public final class EBinary extends AExpression {
} else if (sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
@ -311,6 +315,7 @@ public final class EBinary extends AExpression {
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
@ -349,7 +354,6 @@ public final class EBinary extends AExpression {
left = left.cast(variables);
right = right.cast(variables);
// It'd be nice to be able to do constant folding here but we can't because constants aren't flowing through EChain
promote = Definition.BOOLEAN_TYPE;
actual = Definition.BOOLEAN_TYPE;
}
@ -372,11 +376,13 @@ public final class EBinary extends AExpression {
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = lhspromote;
if (rhspromote.sort == Sort.LONG) {
right.expected = Definition.INT_TYPE;
right.explicit = true;
@ -419,12 +425,14 @@ public final class EBinary extends AExpression {
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = lhspromote;
if (rhspromote.sort == Sort.LONG) {
if (rhspromote.sort == Sort.LONG) {
right.expected = Definition.INT_TYPE;
right.explicit = true;
} else {
@ -466,12 +474,14 @@ public final class EBinary extends AExpression {
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = lhspromote;
if (rhspromote.sort == Sort.LONG) {
if (rhspromote.sort == Sort.LONG) {
right.expected = Definition.INT_TYPE;
right.explicit = true;
} else {
@ -511,6 +521,7 @@ public final class EBinary extends AExpression {
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
@ -628,25 +639,31 @@ public final class EBinary extends AExpression {
left.write(writer, globals);
if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) {
if (!(left instanceof EBinary) || !((EBinary)left).cat) {
writer.writeAppendStrings(left.actual);
}
right.write(writer, globals);
if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) {
if (!(right instanceof EBinary) || !((EBinary)right).cat) {
writer.writeAppendStrings(right.actual);
}
if (!cat) {
writer.writeToStrings();
}
} else if (operation == Operation.FIND) {
writeBuildMatcher(writer, globals);
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND);
} else if (operation == Operation.MATCH) {
writeBuildMatcher(writer, globals);
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES);
} else if (operation == Operation.FIND || operation == Operation.MATCH) {
right.write(writer, globals);
left.write(writer, globals);
writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER);
if (operation == Operation.FIND) {
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND);
} else if (operation == Operation.MATCH) {
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES);
} else {
throw new IllegalStateException("Illegal tree structure.");
}
} else {
left.write(writer, globals);
right.write(writer, globals);
@ -663,13 +680,5 @@ public final class EBinary extends AExpression {
writer.writeBinaryInstruction(location, actual, operation);
}
}
writer.writeBranch(tru, fals);
}
private void writeBuildMatcher(MethodWriter writer, Globals globals) {
right.write(writer, globals);
left.write(writer, globals);
writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER);
}
}

View File

@ -30,15 +30,16 @@ import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Opcodes;
/**
* Represents a boolean expression.
*/
public final class EBool extends AExpression {
final Operation operation;
AExpression left;
AExpression right;
private final Operation operation;
private AExpression left;
private AExpression right;
public EBool(Location location, Operation operation, AExpression left, AExpression right) {
super(location);
@ -47,7 +48,7 @@ public final class EBool extends AExpression {
this.left = Objects.requireNonNull(left);
this.right = Objects.requireNonNull(right);
}
@Override
void extractVariables(Set<String> variables) {
left.extractVariables(variables);
@ -79,72 +80,38 @@ public final class EBool extends AExpression {
@Override
void write(MethodWriter writer, Globals globals) {
if (tru != null || fals != null) {
if (operation == Operation.AND) {
Label localfals = fals == null ? new Label() : fals;
if (operation == Operation.AND) {
Label fals = new Label();
Label end = new Label();
left.fals = localfals;
right.tru = tru;
right.fals = fals;
left.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, fals);
right.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, fals);
left.write(writer, globals);
right.write(writer, globals);
writer.push(true);
writer.goTo(end);
writer.mark(fals);
writer.push(false);
writer.mark(end);
} else if (operation == Operation.OR) {
Label tru = new Label();
Label fals = new Label();
Label end = new Label();
if (fals == null) {
writer.mark(localfals);
}
} else if (operation == Operation.OR) {
Label localtru = tru == null ? new Label() : tru;
left.write(writer, globals);
writer.ifZCmp(Opcodes.IFNE, tru);
right.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, fals);
left.tru = localtru;
right.tru = tru;
right.fals = fals;
left.write(writer, globals);
right.write(writer, globals);
if (tru == null) {
writer.mark(localtru);
}
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
writer.mark(tru);
writer.push(true);
writer.goTo(end);
writer.mark(fals);
writer.push(false);
writer.mark(end);
} else {
if (operation == Operation.AND) {
Label localfals = new Label();
Label end = new Label();
left.fals = localfals;
right.fals = localfals;
left.write(writer, globals);
right.write(writer, globals);
writer.push(true);
writer.goTo(end);
writer.mark(localfals);
writer.push(false);
writer.mark(end);
} else if (operation == Operation.OR) {
Label localtru = new Label();
Label localfals = new Label();
Label end = new Label();
left.tru = localtru;
right.fals = localfals;
left.write(writer, globals);
right.write(writer, globals);
writer.mark(localtru);
writer.push(true);
writer.goTo(end);
writer.mark(localfals);
writer.push(false);
writer.mark(end);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}

View File

@ -37,12 +37,18 @@ public final class EBoolean extends AExpression {
this.constant = constant;
}
@Override
void extractVariables(Set<String> variables) {}
void extractVariables(Set<String> variables) {
// Do nothing.
}
@Override
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("Must read from constant [" + constant + "]."));
}
actual = Definition.BOOLEAN_TYPE;
}

View File

@ -35,20 +35,20 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
/**
* Represents a user-defined call.
*/
public class LCallLocal extends ALink {
public final class ECallLocal extends AExpression {
final String name;
final List<AExpression> arguments;
private final String name;
private final List<AExpression> arguments;
Method method = null;
private Method method = null;
public LCallLocal(Location location, String name, List<AExpression> arguments) {
super(location, -1);
public ECallLocal(Location location, String name, List<AExpression> arguments) {
super(location);
this.name = Objects.requireNonNull(name);
this.arguments = Objects.requireNonNull(arguments);
}
@Override
void extractVariables(Set<String> variables) {
for (AExpression argument : arguments) {
@ -57,42 +57,29 @@ public class LCallLocal extends ALink {
}
@Override
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal call [" + name + "] against an existing target."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot assign a value to a call [" + name + "]."));
}
void analyze(Locals locals) {
MethodKey methodKey = new MethodKey(name, arguments.size());
method = locals.getMethod(methodKey);
if (method != null) {
for (int argument = 0; argument < arguments.size(); ++argument) {
AExpression expression = arguments.get(argument);
expression.expected = method.arguments.get(argument);
expression.internal = true;
expression.analyze(locals);
arguments.set(argument, expression.cast(locals));
}
statement = true;
after = method.rtn;
return this;
if (method == null) {
throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
}
throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
for (int argument = 0; argument < arguments.size(); ++argument) {
AExpression expression = arguments.get(argument);
expression.expected = method.arguments.get(argument);
expression.internal = true;
expression.analyze(locals);
arguments.set(argument, expression.cast(locals));
}
statement = true;
actual = method.rtn;
}
@Override
void write(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
for (AExpression argument : arguments) {
@ -101,9 +88,4 @@ public class LCallLocal extends ALink {
writer.invokeStatic(CLASS_TYPE, method.method);
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}

View File

@ -23,29 +23,29 @@ import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
import java.lang.invoke.LambdaMetafactory;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
/**
* Represents a capturing function reference.
*/
public class ECapturingFunctionRef extends AExpression implements ILambda {
public final String variable;
public final String call;
public final class ECapturingFunctionRef extends AExpression implements ILambda {
private final String variable;
private final String call;
private FunctionRef ref;
Variable captured;
String defPointer;
private Variable captured;
private String defPointer;
public ECapturingFunctionRef(Location location, String variable, String call) {
super(location);
@ -53,7 +53,7 @@ public class ECapturingFunctionRef extends AExpression implements ILambda {
this.variable = Objects.requireNonNull(variable);
this.call = Objects.requireNonNull(call);
}
@Override
void extractVariables(Set<String> variables) {
variables.add(variable);
@ -106,27 +106,27 @@ public class ECapturingFunctionRef extends AExpression implements ILambda {
Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
Type interfaceType = Type.getMethodType(ref.interfaceMethodType.toMethodDescriptorString());
if (ref.needsBridges()) {
writer.invokeDynamic(ref.invokedName,
invokedType,
LAMBDA_BOOTSTRAP_HANDLE,
samMethodType,
ref.implMethodASM,
samMethodType,
LambdaMetafactory.FLAG_BRIDGES,
1,
writer.invokeDynamic(ref.invokedName,
invokedType,
LAMBDA_BOOTSTRAP_HANDLE,
samMethodType,
ref.implMethodASM,
samMethodType,
LambdaMetafactory.FLAG_BRIDGES,
1,
interfaceType);
} else {
writer.invokeDynamic(ref.invokedName,
invokedType,
LAMBDA_BOOTSTRAP_HANDLE,
samMethodType,
ref.implMethodASM,
samMethodType,
writer.invokeDynamic(ref.invokedName,
invokedType,
LAMBDA_BOOTSTRAP_HANDLE,
samMethodType,
ref.implMethodASM,
samMethodType,
0);
}
}
}
@Override
public String getPointer() {
return defPointer;

View File

@ -30,27 +30,23 @@ import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents an implicit cast in most cases, though it will replace
* explicit casts in the tree for simplicity. (Internal only.)
* Represents a cast that is inserted into the tree replacing other casts. (Internal only.)
*/
final class ECast extends AExpression {
final String type;
AExpression child;
Cast cast = null;
private AExpression child;
private final Cast cast;
ECast(Location location, AExpression child, Cast cast) {
super(location);
this.type = null;
this.child = Objects.requireNonNull(child);
this.cast = Objects.requireNonNull(cast);
}
@Override
void extractVariables(Set<String> variables) {
child.extractVariables(variables);
throw new IllegalStateException("Illegal tree structure.");
}
@Override
@ -63,6 +59,5 @@ final class ECast extends AExpression {
child.write(writer, globals);
writer.writeDebugInfo(location);
writer.writeCast(cast);
writer.writeBranch(tru, fals);
}
}

View File

@ -1,405 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Represents the entirety of a variable/method chain for read/write operations.
*/
public final class EChain extends AExpression {
final List<ALink> links;
final boolean pre;
final boolean post;
Operation operation;
AExpression expression;
boolean cat = false;
Type promote = null;
Type shiftDistance; // for shifts, the RHS is promoted independently
Cast there = null;
Cast back = null;
/** Creates a new RHS-only EChain */
public EChain(Location location, ALink link) {
this(location, Arrays.asList(link), false, false, null, null);
}
public EChain(Location location, List<ALink> links,
boolean pre, boolean post, Operation operation, AExpression expression) {
super(location);
this.links = Objects.requireNonNull(links);
this.pre = pre;
this.post = post;
this.operation = operation;
this.expression = expression;
}
@Override
void extractVariables(Set<String> variables) {
for (ALink link : links) {
link.extractVariables(variables);
}
if (expression != null) {
expression.extractVariables(variables);
}
}
@Override
void analyze(Locals locals) {
analyzeLinks(locals);
analyzeIncrDecr();
if (operation != null) {
analyzeCompound(locals);
} else if (expression != null) {
analyzeWrite(locals);
} else {
analyzeRead();
}
}
private void analyzeLinks(Locals variables) {
ALink previous = null;
int index = 0;
while (index < links.size()) {
ALink current = links.get(index);
if (previous != null) {
current.before = previous.after;
if (index == 1) {
current.statik = previous.statik;
}
}
if (index == links.size() - 1) {
current.load = read;
current.store = expression != null || pre || post;
}
ALink analyzed = current.analyze(variables);
if (analyzed == null) {
links.remove(index);
} else {
if (analyzed != current) {
links.set(index, analyzed);
}
previous = analyzed;
++index;
}
}
if (links.get(0).statik) {
links.remove(0);
}
}
private void analyzeIncrDecr() {
ALink last = links.get(links.size() - 1);
if (pre && post) {
throw createError(new IllegalStateException("Illegal tree structure."));
} else if (pre || post) {
if (expression != null) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
Sort sort = last.after.sort;
if (operation == Operation.INCR) {
if (sort == Sort.DOUBLE) {
expression = new EConstant(location, 1D);
} else if (sort == Sort.FLOAT) {
expression = new EConstant(location, 1F);
} else if (sort == Sort.LONG) {
expression = new EConstant(location, 1L);
} else {
expression = new EConstant(location, 1);
}
operation = Operation.ADD;
} else if (operation == Operation.DECR) {
if (sort == Sort.DOUBLE) {
expression = new EConstant(location, 1D);
} else if (sort == Sort.FLOAT) {
expression = new EConstant(location, 1F);
} else if (sort == Sort.LONG) {
expression = new EConstant(location, 1L);
} else {
expression = new EConstant(location, 1);
}
operation = Operation.SUB;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeCompound(Locals variables) {
ALink last = links.get(links.size() - 1);
expression.analyze(variables);
boolean shift = false;
if (operation == Operation.MUL) {
promote = AnalyzerCaster.promoteNumeric(last.after, expression.actual, true);
} else if (operation == Operation.DIV) {
promote = AnalyzerCaster.promoteNumeric(last.after, expression.actual, true);
} else if (operation == Operation.REM) {
promote = AnalyzerCaster.promoteNumeric(last.after, expression.actual, true);
} else if (operation == Operation.ADD) {
promote = AnalyzerCaster.promoteAdd(last.after, expression.actual);
} else if (operation == Operation.SUB) {
promote = AnalyzerCaster.promoteNumeric(last.after, expression.actual, true);
} else if (operation == Operation.LSH) {
promote = AnalyzerCaster.promoteNumeric(last.after, false);
shiftDistance = AnalyzerCaster.promoteNumeric(expression.actual, false);
shift = true;
} else if (operation == Operation.RSH) {
promote = AnalyzerCaster.promoteNumeric(last.after, false);
shiftDistance = AnalyzerCaster.promoteNumeric(expression.actual, false);
shift = true;
} else if (operation == Operation.USH) {
promote = AnalyzerCaster.promoteNumeric(last.after, false);
shiftDistance = AnalyzerCaster.promoteNumeric(expression.actual, false);
shift = true;
} else if (operation == Operation.BWAND) {
promote = AnalyzerCaster.promoteXor(last.after, expression.actual);
} else if (operation == Operation.XOR) {
promote = AnalyzerCaster.promoteXor(last.after, expression.actual);
} else if (operation == Operation.BWOR) {
promote = AnalyzerCaster.promoteXor(last.after, expression.actual);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
if (promote == null || (shift && shiftDistance == null)) {
throw createError(new ClassCastException("Cannot apply compound assignment " +
"[" + operation.symbol + "=] to types [" + last.after + "] and [" + expression.actual + "]."));
}
cat = operation == Operation.ADD && promote.sort == Sort.STRING;
if (cat) {
if (expression instanceof EBinary && ((EBinary)expression).operation == Operation.ADD &&
expression.actual.sort == Sort.STRING) {
((EBinary)expression).cat = true;
}
expression.expected = expression.actual;
} else if (shift) {
if (promote.sort == Sort.DEF) {
// shifts are promoted independently, but for the def type, we need object.
expression.expected = promote;
} else if (shiftDistance.sort == Sort.LONG) {
expression.expected = Definition.INT_TYPE;
expression.explicit = true;
} else {
expression.expected = shiftDistance;
}
} else {
expression.expected = promote;
}
expression = expression.cast(variables);
there = AnalyzerCaster.getLegalCast(location, last.after, promote, false, false);
back = AnalyzerCaster.getLegalCast(location, promote, last.after, true, false);
this.statement = true;
this.actual = read ? last.after : Definition.VOID_TYPE;
}
private void analyzeWrite(Locals variables) {
ALink last = links.get(links.size() - 1);
// If the store node is a def node, we remove the cast to def from the expression
// and promote the real type to it:
if (last instanceof IDefLink) {
expression.analyze(variables);
last.after = expression.expected = expression.actual;
} else {
// otherwise we adapt the type of the expression to the store type
expression.expected = last.after;
expression.analyze(variables);
}
expression = expression.cast(variables);
this.statement = true;
this.actual = read ? last.after : Definition.VOID_TYPE;
}
private void analyzeRead() {
ALink last = links.get(links.size() - 1);
// If the load node is a def node, we adapt its after type to use _this_ expected output type:
if (last instanceof IDefLink && this.expected != null) {
last.after = this.expected;
}
constant = last.string;
statement = last.statement;
actual = last.after;
}
/**
* Handles writing byte code for variable/method chains for all given possibilities
* including String concatenation, compound assignment, regular assignment, and simple
* reads. Includes proper duplication for chained assignments and assignments that are
* also read from.
*
* Example given 'x[0] += 5;' where x is an array of shorts and x[0] is 1.
* Note this example has two links -- x (LVariable) and [0] (LBrace).
* The following steps occur:
* 1. call link{x}.write(...) -- no op [...]
* 2. call link{x}.load(...) -- loads the address of the x array onto the stack [..., address(x)]
* 3. call writer.dup(...) -- dup's the address of the x array onto the stack for later use with store [..., address(x), address(x)]
* 4. call link{[0]}.write(...) -- load the array index value of the constant int 0 onto the stack [..., address(x), address(x), int(0)]
* 5. call link{[0]}.load(...) -- load the short value from x[0] onto the stack [..., address(x), short(1)]
* 6. call writer.writeCast(there) -- casts the short on the stack to an int so it can be added with the rhs [..., address(x), int(1)]
* 7. call expression.write(...) -- puts the expression's value of the constant int 5 onto the stack [..., address(x), int(1), int(5)]
* 8. call writer.writeBinaryInstruction(operation) -- writes the int addition instruction [..., address(x), int(6)]
* 9. call writer.writeCast(back) -- convert the value on the stack back into a short [..., address(x), short(6)]
* 10. call link{[0]}.store(...) -- store the value on the stack into the 0th index of the array x [...]
*/
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
// For the case where the chain represents a String concatenation
// we must, depending on the Java version, write a StringBuilder or
// track types going onto the stack. This must be done before the
// links in the chain are read because we need the StringBuilder to
// be placed on the stack ahead of any potential concatenation arguments.
int catElementStackSize = 0;
if (cat) {
catElementStackSize = writer.writeNewStrings();
}
ALink last = links.get(links.size() - 1);
// Go through all the links in the chain first calling write
// and then load, except for the final link which may be a store.
// See individual links for more information on what each of the
// write, load, and store methods do.
for (ALink link : links) {
link.write(writer, globals); // call the write method on the link to prepare for a load/store operation
if (link == last && link.store) {
if (cat) {
// Handle the case where we are doing a compound assignment
// representing a String concatenation.
writer.writeDup(link.size, catElementStackSize); // dup the top element and insert it before concat helper on stack
link.load(writer, globals); // read the current link's value
writer.writeAppendStrings(link.after); // append the link's value using the StringBuilder
expression.write(writer, globals); // write the bytecode for the rhs expression
if (!(expression instanceof EBinary) ||
((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) {
writer.writeAppendStrings(expression.actual); // append the expression's value unless it's also a concatenation
}
writer.writeToStrings(); // put the value for string concat onto the stack
writer.writeCast(back); // if necessary, cast the String to the lhs actual type
if (link.load) {
writer.writeDup(link.after.sort.size, link.size); // if this link is also read from dup the value onto the stack
}
link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
} else if (operation != null) {
// Handle the case where we are doing a compound assignment that
// does not represent a String concatenation.
writer.writeDup(link.size, 0); // if necessary, dup the previous link's value to be both loaded from and stored to
link.load(writer, globals); // load the current link's value
if (link.load && post) {
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also
// read from and is a post increment
}
writer.writeCast(there); // if necessary cast the current link's value
// to the promotion type between the lhs and rhs types
expression.write(writer, globals); // write the bytecode for the rhs expression
// XXX: fix these types, but first we need def compound assignment tests.
// its tricky here as there are possibly explicit casts, too.
// write the operation instruction for compound assignment
if (promote.sort == Sort.DEF) {
writer.writeDynamicBinaryInstruction(location, promote,
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
} else {
writer.writeBinaryInstruction(location, promote, operation);
}
writer.writeCast(back); // if necessary cast the promotion type value back to the link's type
if (link.load && !post) {
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also
// read from and is not a post increment
}
link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
} else {
// Handle the case for a simple write.
expression.write(writer, globals); // write the bytecode for the rhs expression
if (link.load) {
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also read from
}
link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
}
} else {
// Handle the case for a simple read.
link.load(writer, globals); // read the link's value onto the stack
}
}
writer.writeBranch(tru, fals); // if this is a branch node, write the bytecode to make an appropiate jump
}
}

View File

@ -43,10 +43,11 @@ import static org.elasticsearch.painless.WriterConstants.EQUALS;
*/
public final class EComp extends AExpression {
final Operation operation;
AExpression left;
AExpression right;
Type promotedType;
private final Operation operation;
private AExpression left;
private AExpression right;
private Type promotedType;
public EComp(Location location, Operation operation, AExpression left, AExpression right) {
super(location);
@ -55,7 +56,7 @@ public final class EComp extends AExpression {
this.left = Objects.requireNonNull(left);
this.right = Objects.requireNonNull(right);
}
@Override
void extractVariables(Set<String> variables) {
left.extractVariables(variables);
@ -449,25 +450,21 @@ public final class EComp extends AExpression {
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
boolean branch = tru != null || fals != null;
left.write(writer, globals);
if (!right.isNull) {
right.write(writer, globals);
}
Label jump = tru != null ? tru : fals != null ? fals : new Label();
Label jump = new Label();
Label end = new Label();
boolean eq = (operation == Operation.EQ || operation == Operation.EQR) && (tru != null || fals == null) ||
(operation == Operation.NE || operation == Operation.NER) && fals != null;
boolean ne = (operation == Operation.NE || operation == Operation.NER) && (tru != null || fals == null) ||
(operation == Operation.EQ || operation == Operation.EQR) && fals != null;
boolean lt = operation == Operation.LT && (tru != null || fals == null) || operation == Operation.GTE && fals != null;
boolean lte = operation == Operation.LTE && (tru != null || fals == null) || operation == Operation.GT && fals != null;
boolean gt = operation == Operation.GT && (tru != null || fals == null) || operation == Operation.LTE && fals != null;
boolean gte = operation == Operation.GTE && (tru != null || fals == null) || operation == Operation.LT && fals != null;
boolean eq = (operation == Operation.EQ || operation == Operation.EQR);
boolean ne = (operation == Operation.NE || operation == Operation.NER);
boolean lt = operation == Operation.LT;
boolean lte = operation == Operation.LTE;
boolean gt = operation == Operation.GT;
boolean gte = operation == Operation.GTE;
boolean writejump = true;
@ -478,8 +475,8 @@ public final class EComp extends AExpression {
case CHAR:
throw createError(new IllegalStateException("Illegal tree structure."));
case BOOL:
if (eq) writer.ifZCmp(MethodWriter.EQ, jump);
else if (ne) writer.ifZCmp(MethodWriter.NE, jump);
if (eq) writer.ifCmp(promotedType.type, MethodWriter.EQ, jump);
else if (ne) writer.ifCmp(promotedType.type, MethodWriter.NE, jump);
else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@ -503,10 +500,11 @@ public final class EComp extends AExpression {
case DEF:
org.objectweb.asm.Type booleanType = org.objectweb.asm.Type.getType(boolean.class);
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(booleanType, left.actual.type, right.actual.type);
if (eq) {
if (right.isNull) {
writer.ifNull(jump);
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
} else if (!left.isNull && operation == Operation.EQ) {
writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
writejump = false;
} else {
@ -515,7 +513,7 @@ public final class EComp extends AExpression {
} else if (ne) {
if (right.isNull) {
writer.ifNonNull(jump);
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
} else if (!left.isNull && operation == Operation.NE) {
writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
writer.ifZCmp(MethodWriter.EQ, jump);
} else {
@ -537,22 +535,13 @@ public final class EComp extends AExpression {
throw createError(new IllegalStateException("Illegal tree structure."));
}
if (branch && !writejump) {
writer.ifZCmp(MethodWriter.NE, jump);
}
break;
default:
if (eq) {
if (right.isNull) {
writer.ifNull(jump);
} else if (operation == Operation.EQ || operation == Operation.NE) {
} else if (operation == Operation.EQ) {
writer.invokeStatic(OBJECTS_TYPE, EQUALS);
if (branch) {
writer.ifZCmp(MethodWriter.NE, jump);
}
writejump = false;
} else {
writer.ifCmp(promotedType.type, MethodWriter.EQ, jump);
@ -560,7 +549,7 @@ public final class EComp extends AExpression {
} else if (ne) {
if (right.isNull) {
writer.ifNonNull(jump);
} else if (operation == Operation.EQ || operation == Operation.NE) {
} else if (operation == Operation.NE) {
writer.invokeStatic(OBJECTS_TYPE, EQUALS);
writer.ifZCmp(MethodWriter.EQ, jump);
} else {
@ -571,7 +560,7 @@ public final class EComp extends AExpression {
}
}
if (!branch && writejump) {
if (writejump) {
writer.push(false);
writer.goTo(end);
writer.mark(jump);

View File

@ -31,15 +31,16 @@ import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Opcodes;
/**
* Respresents a conditional expression.
*/
public final class EConditional extends AExpression {
AExpression condition;
AExpression left;
AExpression right;
private AExpression condition;
private AExpression left;
private AExpression right;
public EConditional(Location location, AExpression condition, AExpression left, AExpression right) {
super(location);
@ -48,7 +49,7 @@ public final class EConditional extends AExpression {
this.left = Objects.requireNonNull(left);
this.right = Objects.requireNonNull(right);
}
@Override
void extractVariables(Set<String> variables) {
condition.extractVariables(variables);
@ -93,17 +94,15 @@ public final class EConditional extends AExpression {
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
Label localfals = new Label();
Label fals = new Label();
Label end = new Label();
condition.fals = localfals;
left.tru = right.tru = tru;
left.fals = right.fals = fals;
condition.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, fals);
left.write(writer, globals);
writer.goTo(end);
writer.mark(localfals);
writer.mark(fals);
right.write(writer, globals);
writer.mark(end);
}

View File

@ -30,8 +30,8 @@ import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents a constant. Note this replaces any other expression
* node with a constant value set during a cast. (Internal only.)
* Represents a constant inserted into the tree replacing
* other constants during constant folding. (Internal only.)
*/
final class EConstant extends AExpression {
@ -40,9 +40,11 @@ final class EConstant extends AExpression {
this.constant = constant;
}
@Override
void extractVariables(Set<String> variables) {}
void extractVariables(Set<String> variables) {
throw new IllegalStateException("Illegal tree structure.");
}
@Override
void analyze(Locals locals) {
@ -82,22 +84,9 @@ final class EConstant extends AExpression {
case CHAR: writer.push((char)constant); break;
case SHORT: writer.push((short)constant); break;
case BYTE: writer.push((byte)constant); break;
case BOOL:
if (tru != null && (boolean)constant) {
writer.goTo(tru);
} else if (fals != null && !(boolean)constant) {
writer.goTo(fals);
} else if (tru == null && fals == null) {
writer.push((boolean)constant);
}
break;
case BOOL: writer.push((boolean)constant); break;
default:
throw createError(new IllegalStateException("Illegal tree structure."));
}
if (sort != Sort.BOOL) {
writer.writeBranch(tru, fals);
}
}
}

View File

@ -33,19 +33,23 @@ import java.util.Set;
*/
public final class EDecimal extends AExpression {
final String value;
private final String value;
public EDecimal(Location location, String value) {
super(location);
this.value = Objects.requireNonNull(value);
}
@Override
void extractVariables(Set<String> variables) {}
@Override
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("Must read from constant [" + value + "]."));
}
if (value.endsWith("f") || value.endsWith("F")) {
try {
constant = Float.parseFloat(value.substring(0, value.length() - 1));

View File

@ -33,8 +33,8 @@ import java.util.Set;
*/
public final class EExplicit extends AExpression {
final String type;
AExpression child;
private final String type;
private AExpression child;
public EExplicit(Location location, String type, AExpression child) {
super(location);
@ -42,7 +42,7 @@ public final class EExplicit extends AExpression {
this.type = Objects.requireNonNull(type);
this.child = Objects.requireNonNull(child);
}
@Override
void extractVariables(Set<String> variables) {
child.extractVariables(variables);

View File

@ -20,30 +20,30 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Type;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
import java.lang.invoke.LambdaMetafactory;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
/**
* Represents a function reference.
*/
public class EFunctionRef extends AExpression implements ILambda {
public final String type;
public final String call;
public final class EFunctionRef extends AExpression implements ILambda {
private final String type;
private final String call;
private FunctionRef ref;
String defPointer;
private String defPointer;
public EFunctionRef(Location location, String type, String call) {
super(location);
@ -51,7 +51,7 @@ public class EFunctionRef extends AExpression implements ILambda {
this.type = Objects.requireNonNull(type);
this.call = Objects.requireNonNull(call);
}
@Override
void extractVariables(Set<String> variables) {}

View File

@ -20,12 +20,12 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.Set;
@ -34,19 +34,20 @@ import java.util.Set;
* <p>
* Unlike java's, this works for primitive types too.
*/
public class EInstanceof extends AExpression {
AExpression expression;
final String type;
Class<?> resolvedType;
Class<?> expressionType;
boolean primitiveExpression;
public final class EInstanceof extends AExpression {
private AExpression expression;
private final String type;
private Class<?> resolvedType;
private Class<?> expressionType;
private boolean primitiveExpression;
public EInstanceof(Location location, AExpression expression, String type) {
super(location);
this.expression = Objects.requireNonNull(expression);
this.type = Objects.requireNonNull(type);
}
@Override
void extractVariables(Set<String> variables) {
expression.extractVariables(variables);
@ -54,20 +55,29 @@ public class EInstanceof extends AExpression {
@Override
void analyze(Locals locals) {
Definition.Type raw = Definition.getType(type);
// map to wrapped type for primitive types
resolvedType = MethodType.methodType(raw.clazz).wrap().returnType();
expression.analyze(locals);
actual = Definition.BOOLEAN_TYPE;
Definition.Type expressionRaw = expression.actual;
if (expressionRaw == null) {
expressionRaw = Definition.DEF_TYPE;
final Type type;
// ensure the specified type is part of the definition
try {
type = Definition.getType(this.type);
} catch (IllegalArgumentException exception) {
throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
}
// record if the expression returns a primitive
primitiveExpression = expressionRaw.clazz.isPrimitive();
// map to wrapped type for primitive types
expressionType = MethodType.methodType(expressionRaw.clazz).wrap().returnType();
resolvedType = type.sort.primitive ? type.sort.boxed : type.clazz;
// analyze and cast the expression
expression.analyze(locals);
expression.expected = expression.actual;
expression = expression.cast(locals);
// record if the expression returns a primitive
primitiveExpression = expression.actual.sort.primitive;
// map to wrapped type for primitive types
expressionType = expression.actual.sort.primitive ? expression.actual.sort.boxed : type.clazz;
actual = Definition.BOOLEAN_TYPE;
}
@Override

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
@ -29,7 +30,6 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.invoke.LambdaMetafactory;
import java.util.ArrayList;
@ -47,7 +47,7 @@ import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE
* This can currently only be the direct argument of a call (method/constructor).
* When the argument is of a known type, it uses
* <a href="http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html">
* Java's lambda translation</a>. However, if its a def call, then we don't have
* Java's lambda translation</a>. However, if its a def call, then we don't have
* enough information, and have to defer this until link time. In that case a placeholder
* and all captures are pushed onto the stack and folded into the signature of the parent call.
* <p>
@ -64,24 +64,25 @@ import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE
* <br>
* {@code sort(list, lambda$0(capture))}
*/
public class ELambda extends AExpression implements ILambda {
final String name;
final FunctionReserved reserved;
final List<String> paramTypeStrs;
final List<String> paramNameStrs;
final List<AStatement> statements;
public final class ELambda extends AExpression implements ILambda {
private final String name;
private final FunctionReserved reserved;
private final List<String> paramTypeStrs;
private final List<String> paramNameStrs;
private final List<AStatement> statements;
// desugared synthetic method (lambda body)
SFunction desugared;
private SFunction desugared;
// captured variables
List<Variable> captures;
private List<Variable> captures;
// static parent, static lambda
FunctionRef ref;
private FunctionRef ref;
// dynamic parent, deferred until link time
String defPointer;
private String defPointer;
public ELambda(String name, FunctionReserved reserved,
Location location, List<String> paramTypes, List<String> paramNames,
public ELambda(String name, FunctionReserved reserved,
Location location, List<String> paramTypes, List<String> paramNames,
List<AStatement> statements) {
super(location);
this.name = Objects.requireNonNull(name);
@ -90,7 +91,7 @@ public class ELambda extends AExpression implements ILambda {
this.paramNameStrs = Collections.unmodifiableList(paramNames);
this.statements = Collections.unmodifiableList(statements);
}
@Override
void extractVariables(Set<String> variables) {
for (AStatement statement : statements) {
@ -100,7 +101,7 @@ public class ELambda extends AExpression implements ILambda {
@Override
void analyze(Locals locals) {
final Definition.Type returnType;
final Type returnType;
final List<String> actualParamTypeStrs;
Method interfaceMethod;
// inspect the target first, set interface method if we know it.
@ -114,12 +115,12 @@ public class ELambda extends AExpression implements ILambda {
// we know the method statically, infer return type and any unknown/def types
interfaceMethod = expected.struct.getFunctionalMethod();
if (interfaceMethod == null) {
throw createError(new IllegalArgumentException("Cannot pass lambda to [" + expected.name +
throw createError(new IllegalArgumentException("Cannot pass lambda to [" + expected.name +
"], not a functional interface"));
}
// check arity before we manipulate parameters
if (interfaceMethod.arguments.size() != paramTypeStrs.size())
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name +
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name +
"] in [" + expected.clazz + "]");
// for method invocation, its allowed to ignore the return value
if (interfaceMethod.rtn == Definition.VOID_TYPE) {
@ -159,14 +160,14 @@ public class ELambda extends AExpression implements ILambda {
}
paramTypes.addAll(actualParamTypeStrs);
paramNames.addAll(paramNameStrs);
// desugar lambda body into a synthetic method
desugared = new SFunction(reserved, location, returnType.name, name,
desugared = new SFunction(reserved, location, returnType.name, name,
paramTypes, paramNames, statements, true);
desugared.generate();
desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), returnType, desugared.parameters,
desugared.generateSignature();
desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), returnType, desugared.parameters,
captures.size(), reserved.getMaxLoopCounter()));
// setup method reference to synthetic method
if (expected == null) {
ref = null;
@ -195,8 +196,10 @@ public class ELambda extends AExpression implements ILambda {
}
// convert MethodTypes to asm Type for the constant pool.
String invokedType = ref.invokedType.toMethodDescriptorString();
Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
Type interfaceType = Type.getMethodType(ref.interfaceMethodType.toMethodDescriptorString());
org.objectweb.asm.Type samMethodType =
org.objectweb.asm.Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
org.objectweb.asm.Type interfaceType =
org.objectweb.asm.Type.getMethodType(ref.interfaceMethodType.toMethodDescriptorString());
if (ref.needsBridges()) {
writer.invokeDynamic(ref.invokedName,
invokedType,
@ -235,8 +238,8 @@ public class ELambda extends AExpression implements ILambda {
}
@Override
public Type[] getCaptures() {
Type[] types = new Type[captures.size()];
public org.objectweb.asm.Type[] getCaptures() {
org.objectweb.asm.Type[] types = new org.objectweb.asm.Type[captures.size()];
for (int i = 0; i < types.length; i++) {
types[i] = captures.get(i).type.type;
}

View File

@ -33,11 +33,11 @@ import java.util.Set;
/**
* Represents a list initialization shortcut.
*/
public class EListInit extends AExpression {
final List<AExpression> values;
public final class EListInit extends AExpression {
private final List<AExpression> values;
Method constructor = null;
Method method = null;
private Method constructor = null;
private Method method = null;
public EListInit(Location location, List<AExpression> values) {
super(location);
@ -54,6 +54,10 @@ public class EListInit extends AExpression {
@Override
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("Must read from list initializer."));
}
try {
actual = Definition.getType("ArrayList");
} catch (IllegalArgumentException exception) {

View File

@ -33,12 +33,12 @@ import java.util.Set;
/**
* Represents a map initialization shortcut.
*/
public class EMapInit extends AExpression {
final List<AExpression> keys;
final List<AExpression> values;
public final class EMapInit extends AExpression {
private final List<AExpression> keys;
private final List<AExpression> values;
Method constructor = null;
Method method = null;
private Method constructor = null;
private Method method = null;
public EMapInit(Location location, List<AExpression> keys, List<AExpression> values) {
super(location);
@ -60,6 +60,10 @@ public class EMapInit extends AExpression {
@Override
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("Must read from map initializer."));
}
try {
actual = Definition.getType("HashMap");
} catch (IllegalArgumentException exception) {

View File

@ -33,14 +33,14 @@ import java.util.Set;
/**
* Represents an array instantiation.
*/
public final class LNewArray extends ALink {
public final class ENewArray extends AExpression {
final String type;
final List<AExpression> arguments;
final boolean initialize;
private final String type;
private final List<AExpression> arguments;
private final boolean initialize;
public LNewArray(Location location, String type, List<AExpression> arguments, boolean initialize) {
super(location, -1);
public ENewArray(Location location, String type, List<AExpression> arguments, boolean initialize) {
super(location);
this.type = Objects.requireNonNull(type);
this.arguments = Objects.requireNonNull(arguments);
@ -55,13 +55,9 @@ public final class LNewArray extends ALink {
}
@Override
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Cannot create a new array with a target already defined."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot assign a value to a new array."));
} else if (!load) {
throw createError(new IllegalArgumentException("A newly created array must be read."));
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("A newly created array must be read from."));
}
final Type type;
@ -81,23 +77,16 @@ public final class LNewArray extends ALink {
arguments.set(argument, expression.cast(locals));
}
after = Definition.getType(type.struct, initialize ? 1 : arguments.size());
return this;
actual = Definition.getType(type.struct, initialize ? 1 : arguments.size());
}
@Override
void write(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
if (initialize) {
writer.push(arguments.size());
writer.newArray(Definition.getType(after.struct, 0).type);
writer.newArray(Definition.getType(actual.struct, 0).type);
for (int index = 0; index < arguments.size(); ++index) {
AExpression argument = arguments.get(index);
@ -105,7 +94,7 @@ public final class LNewArray extends ALink {
writer.dup();
writer.push(index);
argument.write(writer, globals);
writer.arrayStore(Definition.getType(after.struct, 0).type);
writer.arrayStore(Definition.getType(actual.struct, 0).type);
}
} else {
for (AExpression argument : arguments) {
@ -113,15 +102,10 @@ public final class LNewArray extends ALink {
}
if (arguments.size() > 1) {
writer.visitMultiANewArrayInsn(after.type.getDescriptor(), after.type.getDimensions());
writer.visitMultiANewArrayInsn(actual.type.getDescriptor(), actual.type.getDimensions());
} else {
writer.newArray(Definition.getType(after.struct, 0).type);
writer.newArray(Definition.getType(actual.struct, 0).type);
}
}
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}

View File

@ -35,20 +35,20 @@ import java.util.Set;
/**
* Represents and object instantiation.
*/
public final class LNewObj extends ALink {
public final class ENewObj extends AExpression {
final String type;
final List<AExpression> arguments;
private final String type;
private final List<AExpression> arguments;
Method constructor;
private Method constructor;
public LNewObj(Location location, String type, List<AExpression> arguments) {
super(location, -1);
public ENewObj(Location location, String type, List<AExpression> arguments) {
super(location);
this.type = Objects.requireNonNull(type);
this.arguments = Objects.requireNonNull(arguments);
}
@Override
void extractVariables(Set<String> variables) {
for (AExpression argument : arguments) {
@ -57,13 +57,7 @@ public final class LNewObj extends ALink {
}
@Override
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal new call with a target already defined."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot assign a value to a new call."));
}
void analyze(Locals locals) {
final Type type;
try {
@ -94,25 +88,19 @@ public final class LNewObj extends ALink {
}
statement = true;
after = type;
actual = type;
} else {
throw createError(new IllegalArgumentException("Unknown new call on type [" + struct.name + "]."));
}
return this;
}
@Override
void write(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
writer.newInstance(after.type);
if (load) {
writer.newInstance(actual.type);
if (read) {
writer.dup();
}
@ -122,9 +110,4 @@ public final class LNewObj extends ALink {
writer.invokeConstructor(constructor.owner.type, constructor.method);
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}

View File

@ -37,12 +37,18 @@ public final class ENull extends AExpression {
public ENull(Location location) {
super(location);
}
@Override
void extractVariables(Set<String> variables) {}
void extractVariables(Set<String> variables) {
// Do nothing.
}
@Override
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("Must read from null constant."));
}
isNull = true;
if (expected != null) {

View File

@ -35,8 +35,8 @@ import org.elasticsearch.painless.MethodWriter;
*/
public final class ENumeric extends AExpression {
final String value;
int radix;
private final String value;
private int radix;
public ENumeric(Location location, String value, int radix) {
super(location);
@ -44,12 +44,18 @@ public final class ENumeric extends AExpression {
this.value = Objects.requireNonNull(value);
this.radix = radix;
}
@Override
void extractVariables(Set<String> variables) {}
void extractVariables(Set<String> variables) {
// Do nothing.
}
@Override
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("Must read from constant [" + value + "]."));
}
if (value.endsWith("d") || value.endsWith("D")) {
if (radix != 10) {
throw createError(new IllegalStateException("Illegal tree structure."));

View File

@ -35,61 +35,53 @@ import org.elasticsearch.painless.WriterConstants;
/**
* Represents a regex constant. All regexes are constants.
*/
public final class LRegex extends ALink {
public final class ERegex extends AExpression {
private final String pattern;
private final int flags;
private Constant constant;
public LRegex(Location location, String pattern, String flagsString) {
super(location, 1);
public ERegex(Location location, String pattern, String flagsString) {
super(location);
this.pattern = pattern;
int flags = 0;
for (int c = 0; c < flagsString.length(); c++) {
flags |= flagForChar(flagsString.charAt(c));
}
this.flags = flags;
try {
// Compile the pattern early after parsing so we can throw an error to the user with the location
Pattern.compile(pattern, flags);
} catch (PatternSyntaxException e) {
throw createError(e);
}
}
@Override
void extractVariables(Set<String> variables) {}
@Override
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal Regex constant [" + pattern + "]."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot write to Regex constant [" + pattern + "]."));
} else if (!load) {
throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "]."));
}
constant = new Constant(location, Definition.PATTERN_TYPE.type, "regexAt$" + location.getOffset(), this::initializeConstant);
after = Definition.PATTERN_TYPE;
return this;
}
@Override
void write(MethodWriter writer, Globals globals) {
void extractVariables(Set<String> variables) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type);
globals.addConstantInitializer(constant);
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "]."));
}
try {
Pattern.compile(pattern, flags);
} catch (PatternSyntaxException exception) {
throw createError(exception);
}
constant = new Constant(location, Definition.PATTERN_TYPE.type, "regexAt$" + location.getOffset(), this::initializeConstant);
actual = Definition.PATTERN_TYPE;
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type);
globals.addConstantInitializer(constant);
}
private void initializeConstant(MethodWriter writer) {
@ -100,15 +92,16 @@ public final class LRegex extends ALink {
private int flagForChar(char c) {
switch (c) {
case 'c': return Pattern.CANON_EQ;
case 'i': return Pattern.CASE_INSENSITIVE;
case 'l': return Pattern.LITERAL;
case 'm': return Pattern.MULTILINE;
case 's': return Pattern.DOTALL;
case 'U': return Pattern.UNICODE_CHARACTER_CLASS;
case 'u': return Pattern.UNICODE_CASE;
case 'x': return Pattern.COMMENTS;
default: throw new IllegalArgumentException("Unknown flag [" + c + "]");
case 'c': return Pattern.CANON_EQ;
case 'i': return Pattern.CASE_INSENSITIVE;
case 'l': return Pattern.LITERAL;
case 'm': return Pattern.MULTILINE;
case 's': return Pattern.DOTALL;
case 'U': return Pattern.UNICODE_CHARACTER_CLASS;
case 'u': return Pattern.UNICODE_CASE;
case 'x': return Pattern.COMMENTS;
default:
throw new IllegalArgumentException("Unknown flag [" + c + "]");
}
}
}

View File

@ -32,47 +32,32 @@ import org.elasticsearch.painless.Locals;
/**
* Represents a static type target.
*/
public final class LStatic extends ALink {
public final class EStatic extends AExpression {
final String type;
private final String type;
public LStatic(Location location, String type) {
super(location, 0);
public EStatic(Location location, String type) {
super(location);
this.type = Objects.requireNonNull(type);
}
@Override
void extractVariables(Set<String> variables) {}
@Override
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal static type [" + type + "] after target already defined."));
}
void extractVariables(Set<String> variables) {
// Do nothing.
}
@Override
void analyze(Locals locals) {
try {
after = Definition.getType(type);
statik = true;
actual = Definition.getType(type);
} catch (IllegalArgumentException exception) {
throw createError(new IllegalArgumentException("Not a type [" + type + "]."));
}
return this;
}
@Override
void write(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void load(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
// Do nothing.
}
}

View File

@ -31,44 +31,30 @@ import java.util.Set;
/**
* Represents a string constant.
*/
public final class LString extends ALink {
public final class EString extends AExpression {
public LString(Location location, String string) {
super(location, -1);
public EString(Location location, String string) {
super(location);
this.string = Objects.requireNonNull(string);
}
@Override
void extractVariables(Set<String> variables) {}
@Override
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal String constant [" + string + "]."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot write to read-only String constant [" + string + "]."));
} else if (!load) {
throw createError(new IllegalArgumentException("Must read String constant [" + string + "]."));
}
after = Definition.STRING_TYPE;
return this;
this.constant = Objects.requireNonNull(string);
}
@Override
void write(MethodWriter writer, Globals globals) {
void extractVariables(Set<String> variables) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.push(string);
void analyze(Locals locals) {
if (!read) {
throw createError(new IllegalArgumentException("Must read from constant [" + constant + "]."));
}
actual = Definition.STRING_TYPE;
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
void write(MethodWriter writer, Globals globals) {
throw new IllegalStateException("Illegal tree structure.");
}
}

View File

@ -34,16 +34,18 @@ import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Opcodes;
/**
* Represents a unary math expression.
*/
public final class EUnary extends AExpression {
final Operation operation;
AExpression child;
Type promote;
boolean originallyExplicit = false; // record whether there was originally an explicit cast
private final Operation operation;
private AExpression child;
private Type promote;
private boolean originallyExplicit = false; // record whether there was originally an explicit cast
public EUnary(Location location, Operation operation, AExpression child) {
super(location);
@ -51,7 +53,7 @@ public final class EUnary extends AExpression {
this.operation = Objects.requireNonNull(operation);
this.child = Objects.requireNonNull(child);
}
@Override
void extractVariables(Set<String> variables) {
child.extractVariables(variables);
@ -60,6 +62,7 @@ public final class EUnary extends AExpression {
@Override
void analyze(Locals locals) {
originallyExplicit = explicit;
if (operation == Operation.NOT) {
analyzeNot(locals);
} else if (operation == Operation.BWNOT) {
@ -191,33 +194,29 @@ public final class EUnary extends AExpression {
writer.writeDebugInfo(location);
if (operation == Operation.NOT) {
if (tru == null && fals == null) {
Label localfals = new Label();
Label end = new Label();
Label fals = new Label();
Label end = new Label();
child.fals = localfals;
child.write(writer, globals);
child.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, fals);
writer.push(false);
writer.goTo(end);
writer.mark(localfals);
writer.push(true);
writer.mark(end);
} else {
child.tru = fals;
child.fals = tru;
child.write(writer, globals);
}
writer.push(false);
writer.goTo(end);
writer.mark(fals);
writer.push(true);
writer.mark(end);
} else {
Sort sort = promote.sort;
child.write(writer, globals);
// def calls adopt the wanted return value. if there was a narrowing cast,
// we need to flag that so that its done at runtime.
// Def calls adopt the wanted return value. If there was a narrowing cast,
// we need to flag that so that it's done at runtime.
int defFlags = 0;
if (originallyExplicit) {
defFlags |= DefBootstrap.OPERATOR_EXPLICIT_CAST;
}
if (operation == Operation.BWNOT) {
if (sort == Sort.DEF) {
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
@ -244,12 +243,10 @@ public final class EUnary extends AExpression {
if (sort == Sort.DEF) {
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
writer.invokeDefCall("plus", descriptor, DefBootstrap.UNARY_OPERATOR, defFlags);
}
}
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
writer.writeBranch(tru, fals);
}
}
}

View File

@ -19,11 +19,12 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
@ -32,52 +33,66 @@ import java.util.Set;
/**
* Represents a variable load/store.
*/
public final class LVariable extends ALink {
public final class EVariable extends AStoreable {
final String name;
private final String name;
Variable variable;
private Variable variable = null;
public LVariable(Location location, String name) {
super(location, 0);
public EVariable(Location location, String name) {
super(location);
this.name = Objects.requireNonNull(name);
}
@Override
void extractVariables(Set<String> variables) {
variables.add(name);
}
@Override
ALink analyze(Locals locals) {
if (before != null) {
throw createError(new IllegalArgumentException("Illegal variable [" + name + "] access with target already defined."));
}
void analyze(Locals locals) {
variable = locals.getVariable(location, name);
if (store && variable.readonly) {
if (write && variable.readonly) {
throw createError(new IllegalArgumentException("Variable [" + variable.name + "] is read-only."));
}
after = variable.type;
return this;
actual = variable.type;
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.visitVarInsn(actual.type.getOpcode(Opcodes.ILOAD), variable.getSlot());
}
@Override
int accessElementCount() {
return 0;
}
@Override
boolean isDefOptimized() {
return false;
}
@Override
void updateActual(Type actual) {
throw new IllegalArgumentException("Illegal tree structure.");
}
@Override
void setup(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), variable.getSlot());
writer.visitVarInsn(actual.type.getOpcode(Opcodes.ILOAD), variable.getSlot());
}
@Override
void store(MethodWriter writer, Globals globals) {
writer.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
writer.visitVarInsn(actual.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
}
}

View File

@ -1,27 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
/**
* A marker interface applied to LDef* nodes allowing changes to {@link ALink#after} from outside,
* by default {@code after} is {@code DEF}.
*/
interface IDefLink {
}

View File

@ -1,95 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Represents an array load/store or defers to possible shortcuts.
*/
public final class LBrace extends ALink {
AExpression index;
public LBrace(Location location, AExpression index) {
super(location, 2);
this.index = Objects.requireNonNull(index);
}
@Override
void extractVariables(Set<String> variables) {
index.extractVariables(variables);
}
@Override
ALink analyze(Locals locals) {
if (before == null) {
throw createError(new IllegalArgumentException("Illegal array access made without target."));
}
Sort sort = before.sort;
if (sort == Sort.ARRAY) {
index.expected = Definition.INT_TYPE;
index.analyze(locals);
index = index.cast(locals);
after = Definition.getType(before.struct, before.dimensions - 1);
return this;
} else if (sort == Sort.DEF) {
return new LDefArray(location, index).copy(this).analyze(locals);
} else if (Map.class.isAssignableFrom(before.clazz)) {
return new LMapShortcut(location, index).copy(this).analyze(locals);
} else if (List.class.isAssignableFrom(before.clazz)) {
return new LListShortcut(location, index).copy(this).analyze(locals);
}
throw createError(new IllegalArgumentException("Illegal array access on type [" + before.name + "]."));
}
@Override
void write(MethodWriter writer, Globals globals) {
index.write(writer, globals);
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
writer.arrayLoad(after.type);
}
@Override
void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
writer.arrayStore(after.type);
}
}

View File

@ -1,133 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.lang.invoke.MethodType;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Represents a method call or defers to a def call.
*/
public final class LCallInvoke extends ALink {
final String name;
final List<AExpression> arguments;
Method method = null;
boolean box = false; // true for primitive types
public LCallInvoke(Location location, String name, List<AExpression> arguments) {
super(location, -1);
this.name = Objects.requireNonNull(name);
this.arguments = Objects.requireNonNull(arguments);
}
@Override
void extractVariables(Set<String> variables) {
for (AExpression argument : arguments) {
argument.extractVariables(variables);
}
}
@Override
ALink analyze(Locals locals) {
if (before == null) {
throw createError(new IllegalArgumentException("Illegal call [" + name + "] made without target."));
} else if (before.sort == Sort.ARRAY) {
throw createError(new IllegalArgumentException("Illegal call [" + name + "] on array type."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot assign a value to a call [" + name + "]."));
}
MethodKey methodKey = new MethodKey(name, arguments.size());
Struct struct = before.struct;
if (before.clazz.isPrimitive()) {
Class<?> wrapper = MethodType.methodType(before.clazz).wrap().returnType();
Type boxed = Definition.getType(wrapper.getSimpleName());
struct = boxed.struct;
box = true;
}
method = statik ? struct.staticMethods.get(methodKey) : struct.methods.get(methodKey);
if (method != null) {
for (int argument = 0; argument < arguments.size(); ++argument) {
AExpression expression = arguments.get(argument);
expression.expected = method.arguments.get(argument);
expression.internal = true;
expression.analyze(locals);
arguments.set(argument, expression.cast(locals));
}
statement = true;
after = method.rtn;
return this;
} else if (before.sort == Sort.DEF) {
ALink link = new LDefCall(location, name, arguments);
link.copy(this);
return link.analyze(locals);
}
throw createError(new IllegalArgumentException(
"Unknown call [" + name + "] with [" + arguments.size() + "] arguments on type [" + struct.name + "]."));
}
@Override
void write(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
if (box) {
writer.box(before.type);
}
for (AExpression argument : arguments) {
argument.write(writer, globals);
}
method.write(writer);
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}

View File

@ -1,134 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Field;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Represents a field load/store or defers to a possible shortcuts.
*/
public final class LField extends ALink {
final String value;
Field field;
public LField(Location location, String value) {
super(location, 1);
this.value = Objects.requireNonNull(value);
}
@Override
void extractVariables(Set<String> variables) {}
@Override
ALink analyze(Locals locals) {
if (before == null) {
throw createError(new IllegalArgumentException("Illegal field [" + value + "] access made without target."));
}
Sort sort = before.sort;
if (sort == Sort.ARRAY) {
return new LArrayLength(location, value).copy(this).analyze(locals);
} else if (sort == Sort.DEF) {
return new LDefField(location, value).copy(this).analyze(locals);
}
Struct struct = before.struct;
field = statik ? struct.staticMembers.get(value) : struct.members.get(value);
if (field != null) {
if (store && java.lang.reflect.Modifier.isFinal(field.modifiers)) {
throw createError(new IllegalArgumentException(
"Cannot write to read-only field [" + value + "] for type [" + struct.name + "]."));
}
after = field.type;
return this;
} else {
boolean shortcut =
struct.methods.containsKey(new Definition.MethodKey("get" +
Character.toUpperCase(value.charAt(0)) + value.substring(1), 0)) ||
struct.methods.containsKey(new Definition.MethodKey("is" +
Character.toUpperCase(value.charAt(0)) + value.substring(1), 0)) ||
struct.methods.containsKey(new Definition.MethodKey("set" +
Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
if (shortcut) {
return new LShortcut(location, value).copy(this).analyze(locals);
} else {
EConstant index = new EConstant(location, value);
index.analyze(locals);
if (Map.class.isAssignableFrom(before.clazz)) {
return new LMapShortcut(location, index).copy(this).analyze(locals);
}
if (List.class.isAssignableFrom(before.clazz)) {
return new LListShortcut(location, index).copy(this).analyze(locals);
}
}
}
throw createError(new IllegalArgumentException("Unknown field [" + value + "] for type [" + struct.name + "]."));
}
@Override
void write(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.getStatic(field.owner.type, field.javaName, field.type.type);
} else {
writer.getField(field.owner.type, field.javaName, field.type.type);
}
}
@Override
void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.putStatic(field.owner.type, field.javaName, field.type.type);
} else {
writer.putField(field.owner.type, field.javaName, field.type.type);
}
}
}

View File

@ -0,0 +1,120 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Represents an array load/store and defers to a child subnode.
*/
public final class PBrace extends AStoreable {
private AExpression index;
private AStoreable sub = null;
public PBrace(Location location, AExpression prefix, AExpression index) {
super(location, prefix);
this.index = Objects.requireNonNull(index);
}
@Override
void extractVariables(Set<String> variables) {
prefix.extractVariables(variables);
index.extractVariables(variables);
}
@Override
void analyze(Locals locals) {
prefix.analyze(locals);
prefix.expected = prefix.actual;
prefix = prefix.cast(locals);
Sort sort = prefix.actual.sort;
if (sort == Sort.ARRAY) {
sub = new PSubBrace(location, prefix.actual, index);
} else if (sort == Sort.DEF) {
sub = new PSubDefArray(location, index);
} else if (Map.class.isAssignableFrom(prefix.actual.clazz)) {
sub = new PSubMapShortcut(location, prefix.actual.struct, index);
} else if (List.class.isAssignableFrom(prefix.actual.clazz)) {
sub = new PSubListShortcut(location, prefix.actual.struct, index);
} else {
throw createError(new IllegalArgumentException("Illegal array access on type [" + prefix.actual.name + "]."));
}
sub.write = write;
sub.read = read;
sub.expected = expected;
sub.explicit = explicit;
sub.analyze(locals);
actual = sub.actual;
}
@Override
void write(MethodWriter writer, Globals globals) {
prefix.write(writer, globals);
sub.write(writer, globals);
}
@Override
boolean isDefOptimized() {
return sub.isDefOptimized();
}
@Override
void updateActual(Type actual) {
sub.updateActual(actual);
this.actual = actual;
}
@Override
int accessElementCount() {
return sub.accessElementCount();
}
@Override
void setup(MethodWriter writer, Globals globals) {
prefix.write(writer, globals);
sub.setup(writer, globals);
}
@Override
void load(MethodWriter writer, Globals globals) {
sub.load(writer, globals);
}
@Override
void store(MethodWriter writer, Globals globals) {
sub.store(writer, globals);
}
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Represents a method call and defers to a child subnode.
*/
public final class PCallInvoke extends AExpression {
private final String name;
private final List<AExpression> arguments;
private AExpression sub = null;
public PCallInvoke(Location location, AExpression prefix, String name, List<AExpression> arguments) {
super(location, prefix);
this.name = Objects.requireNonNull(name);
this.arguments = Objects.requireNonNull(arguments);
}
@Override
void extractVariables(Set<String> variables) {
prefix.extractVariables(variables);
for (AExpression argument : arguments) {
argument.extractVariables(variables);
}
}
@Override
void analyze(Locals locals) {
prefix.analyze(locals);
prefix.expected = prefix.actual;
prefix = prefix.cast(locals);
if (prefix.actual.sort == Sort.ARRAY) {
throw createError(new IllegalArgumentException("Illegal call [" + name + "] on array type."));
}
Struct struct = prefix.actual.struct;
if (prefix.actual.sort.primitive) {
struct = Definition.getType(prefix.actual.sort.boxed.getSimpleName()).struct;
}
MethodKey methodKey = new MethodKey(name, arguments.size());
Method method = prefix instanceof EStatic ? struct.staticMethods.get(methodKey) : struct.methods.get(methodKey);
if (method != null) {
sub = new PSubCallInvoke(location, method, prefix.actual, arguments);
} else if (prefix.actual.sort == Sort.DEF) {
sub = new PSubDefCall(location, name, arguments);
} else {
throw createError(new IllegalArgumentException(
"Unknown call [" + name + "] with [" + arguments.size() + "] arguments on type [" + struct.name + "]."));
}
sub.expected = expected;
sub.explicit = explicit;
sub.analyze(locals);
actual = sub.actual;
statement = true;
}
@Override
void write(MethodWriter writer, Globals globals) {
prefix.write(writer, globals);
sub.write(writer, globals);
}
}

View File

@ -0,0 +1,154 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Field;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Represents a field load/store and defers to a child subnode.
*/
public final class PField extends AStoreable {
private final String value;
private AStoreable sub = null;
public PField(Location location, AExpression prefix, String value) {
super(location, prefix);
this.value = Objects.requireNonNull(value);
}
@Override
void extractVariables(Set<String> variables) {
prefix.extractVariables(variables);
}
@Override
void analyze(Locals locals) {
prefix.analyze(locals);
prefix.expected = prefix.actual;
prefix = prefix.cast(locals);
Sort sort = prefix.actual.sort;
if (sort == Sort.ARRAY) {
sub = new PSubArrayLength(location,prefix.actual.name, value);
} else if (sort == Sort.DEF) {
sub = new PSubDefField(location, value);
} else {
Struct struct = prefix.actual.struct;
Field field = prefix instanceof EStatic ? struct.staticMembers.get(value) : struct.members.get(value);
if (field != null) {
sub = new PSubField(location, field);
} else {
Method getter = struct.methods.get(
new Definition.MethodKey("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));
if (getter == null) {
getter = struct.methods.get(
new Definition.MethodKey("is" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));
}
Method setter = struct.methods.get(
new Definition.MethodKey("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
if (getter != null || setter != null) {
sub = new PSubShortcut(location, value, prefix.actual.name, getter, setter);
} else {
EConstant index = new EConstant(location, value);
index.analyze(locals);
if (Map.class.isAssignableFrom(prefix.actual.clazz)) {
sub = new PSubMapShortcut(location, struct, index);
}
if (List.class.isAssignableFrom(prefix.actual.clazz)) {
sub = new PSubListShortcut(location, struct, index);
}
}
}
}
if (sub == null) {
throw createError(new IllegalArgumentException("Unknown field [" + value + "] for type [" + prefix.actual.name + "]."));
}
sub.write = write;
sub.read = read;
sub.expected = expected;
sub.explicit = explicit;
sub.analyze(locals);
actual = sub.actual;
}
@Override
void write(MethodWriter writer, Globals globals) {
prefix.write(writer, globals);
sub.write(writer, globals);
}
@Override
boolean isDefOptimized() {
return sub.isDefOptimized();
}
@Override
void updateActual(Type actual) {
sub.updateActual(actual);
this.actual = actual;
}
@Override
int accessElementCount() {
return sub.accessElementCount();
}
@Override
void setup(MethodWriter writer, Globals globals) {
prefix.write(writer, globals);
sub.setup(writer, globals);
}
@Override
void load(MethodWriter writer, Globals globals) {
sub.load(writer, globals);
}
@Override
void store(MethodWriter writer, Globals globals) {
sub.store(writer, globals);
}
}

View File

@ -20,9 +20,10 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
@ -31,49 +32,69 @@ import java.util.Set;
/**
* Represents an array length field load.
*/
public final class LArrayLength extends ALink {
final class PSubArrayLength extends AStoreable {
final String value;
private final String type;
private final String value;
LArrayLength(Location location, String value) {
super(location, -1);
PSubArrayLength(Location location, String type, String value) {
super(location);
this.type = Objects.requireNonNull(type);
this.value = Objects.requireNonNull(value);
}
@Override
void extractVariables(Set<String> variables) {}
@Override
ALink analyze(Locals locals) {
void extractVariables(Set<String> variables) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void analyze(Locals locals) {
if ("length".equals(value)) {
if (!load) {
throw createError(new IllegalArgumentException("Must read array field [length]."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot write to read-only array field [length]."));
if (write) {
throw createError(new IllegalArgumentException("Cannot write to read-only field [length] for an array."));
}
after = Definition.INT_TYPE;
actual = Definition.INT_TYPE;
} else {
throw createError(new IllegalArgumentException("Illegal field access [" + value + "]."));
throw createError(new IllegalArgumentException("Field [" + value + "] does not exist for type [" + type + "]."));
}
return this;
}
@Override
void write(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
writer.arrayLength();
}
@Override
int accessElementCount() {
throw new IllegalStateException("Illegal tree structure.");
}
@Override
boolean isDefOptimized() {
throw new IllegalStateException("Illegal tree structure.");
}
@Override
void updateActual(Type actual) {
throw new IllegalStateException("Illegal tree structure.");
}
@Override
void setup(MethodWriter writer, Globals globals) {
throw new IllegalStateException("Illegal tree structure.");
}
@Override
void load(MethodWriter writer, Globals globals) {
throw new IllegalStateException("Illegal tree structure.");
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
throw new IllegalStateException("Illegal tree structure.");
}
}

View File

@ -20,64 +20,81 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents an array load/store or shortcut on a def type. (Internal only.)
* Represents an array load/store.
*/
final class LDefArray extends ALink implements IDefLink {
final class PSubBrace extends AStoreable {
AExpression index;
private final Type type;
private AExpression index;
LDefArray(Location location, AExpression index) {
super(location, 2);
PSubBrace(Location location, Type type, AExpression index) {
super(location);
this.type = Objects.requireNonNull(type);
this.index = Objects.requireNonNull(index);
}
@Override
void extractVariables(Set<String> variables) {
index.extractVariables(variables);
}
@Override
ALink analyze(Locals locals) {
void analyze(Locals locals) {
index.expected = Definition.INT_TYPE;
index.analyze(locals);
index.expected = index.actual;
index = index.cast(locals);
after = Definition.DEF_TYPE;
return this;
actual = Definition.getType(type.struct, type.dimensions - 1);
}
@Override
void write(MethodWriter writer, Globals globals) {
if (!write) {
setup(writer, globals);
load(writer, globals);
}
}
@Override
int accessElementCount() {
return 2;
}
@Override
boolean isDefOptimized() {
return false;
}
@Override
void updateActual(Type actual) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void setup(MethodWriter writer, Globals globals) {
index.write(writer, globals);
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type, index.actual.type);
writer.invokeDefCall("arrayLoad", methodType, DefBootstrap.ARRAY_LOAD);
writer.arrayLoad(actual.type);
}
@Override
void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
writer.invokeDefCall("arrayStore", methodType, DefBootstrap.ARRAY_STORE);
writer.arrayStore(actual.type);
}
}

View File

@ -19,67 +19,66 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Cast;
import java.util.Set;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Represents a cast made in a variable/method chain.
* Represents a method call.
*/
public final class LCast extends ALink {
final class PSubCallInvoke extends AExpression {
final String type;
private final Method method;
private final Type box;
private final List<AExpression> arguments;
Cast cast = null;
public PSubCallInvoke(Location location, Method method, Type box, List<AExpression> arguments) {
super(location);
public LCast(Location location, String type) {
super(location, -1);
this.type = type;
this.method = Objects.requireNonNull(method);
this.box = box;
this.arguments = Objects.requireNonNull(arguments);
}
@Override
void extractVariables(Set<String> variables) {}
@Override
ALink analyze(Locals locals) {
if (before == null) {
throw createError(new IllegalStateException("Illegal cast without a target."));
} else if (store) {
throw createError(new IllegalArgumentException("Cannot assign a value to a cast."));
void extractVariables(Set<String> variables) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void analyze(Locals locals) {
for (int argument = 0; argument < arguments.size(); ++argument) {
AExpression expression = arguments.get(argument);
expression.expected = method.arguments.get(argument);
expression.internal = true;
expression.analyze(locals);
arguments.set(argument, expression.cast(locals));
}
try {
after = Definition.getType(type);
} catch (IllegalArgumentException exception) {
throw createError(new IllegalArgumentException("Not a type [" + type + "]."));
}
cast = AnalyzerCaster.getLegalCast(location, before, after, true, false);
return cast != null ? this : null;
statement = true;
actual = method.rtn;
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
writer.writeCast(cast);
}
@Override
void load(MethodWriter writer, Globals globals) {
// Do nothing.
}
if (box.sort.primitive) {
writer.box(box.type);
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
for (AExpression argument : arguments) {
argument.write(writer, globals);
}
method.write(writer);
}
}

View File

@ -0,0 +1,108 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
import java.util.Set;
/**
* Represents an array load/store or shortcut on a def type. (Internal only.)
*/
final class PSubDefArray extends AStoreable {
private AExpression index;
PSubDefArray(Location location, AExpression index) {
super(location);
this.index = Objects.requireNonNull(index);
}
@Override
void extractVariables(Set<String> variables) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void analyze(Locals locals) {
index.analyze(locals);
index.expected = index.actual;
index = index.cast(locals);
actual = expected == null || explicit ? Definition.DEF_TYPE : expected;
}
@Override
void write(MethodWriter writer, Globals globals) {
index.write(writer, globals);
writer.writeDebugInfo(location);
org.objectweb.asm.Type methodType =
org.objectweb.asm.Type.getMethodType(actual.type, Definition.DEF_TYPE.type, index.actual.type);
writer.invokeDefCall("arrayLoad", methodType, DefBootstrap.ARRAY_LOAD);
}
@Override
int accessElementCount() {
return 2;
}
@Override
boolean isDefOptimized() {
return true;
}
@Override
void updateActual(Type actual) {
this.actual = actual;
}
@Override
void setup(MethodWriter writer, Globals globals) {
index.write(writer, globals);
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
org.objectweb.asm.Type methodType =
org.objectweb.asm.Type.getMethodType(actual.type, Definition.DEF_TYPE.type, index.actual.type);
writer.invokeDefCall("arrayLoad", methodType, DefBootstrap.ARRAY_LOAD);
}
@Override
void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
org.objectweb.asm.Type methodType =
org.objectweb.asm.Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, actual.type);
writer.invokeDefCall("arrayStore", methodType, DefBootstrap.ARRAY_STORE);
}
}

View File

@ -19,11 +19,11 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Type;
@ -35,31 +35,31 @@ import java.util.Set;
/**
* Represents a method call made on a def type. (Internal only.)
*/
final class LDefCall extends ALink implements IDefLink {
final class PSubDefCall extends AExpression {
final String name;
final List<AExpression> arguments;
StringBuilder recipe;
List<String> pointers = new ArrayList<>();
private final String name;
private final List<AExpression> arguments;
LDefCall(Location location, String name, List<AExpression> arguments) {
super(location, -1);
private StringBuilder recipe = null;
private List<String> pointers = new ArrayList<>();
PSubDefCall(Location location, String name, List<AExpression> arguments) {
super(location);
this.name = Objects.requireNonNull(name);
this.arguments = Objects.requireNonNull(arguments);
}
@Override
void extractVariables(Set<String> variables) {
for (AExpression argument : arguments) {
argument.extractVariables(variables);
}
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
ALink analyze(Locals locals) {
void analyze(Locals locals) {
recipe = new StringBuilder();
int totalCaptures = 0;
for (int argument = 0; argument < arguments.size(); ++argument) {
AExpression expression = arguments.get(argument);
@ -79,19 +79,11 @@ final class LDefCall extends ALink implements IDefLink {
arguments.set(argument, expression.cast(locals));
}
statement = true;
after = Definition.DEF_TYPE;
return this;
actual = expected == null || explicit ? Definition.DEF_TYPE : expected;
}
@Override
void write(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
List<Type> parameterTypes = new ArrayList<>();
@ -102,26 +94,24 @@ final class LDefCall extends ALink implements IDefLink {
// append each argument
for (AExpression argument : arguments) {
parameterTypes.add(argument.actual.type);
if (argument instanceof ILambda) {
ILambda lambda = (ILambda) argument;
for (Type capture : lambda.getCaptures()) {
parameterTypes.add(capture);
}
}
argument.write(writer, globals);
}
// create method type from return value and arguments
Type methodType = Type.getMethodType(after.type, parameterTypes.toArray(new Type[0]));
Type methodType = Type.getMethodType(actual.type, parameterTypes.toArray(new Type[0]));
List<Object> args = new ArrayList<>();
args.add(recipe.toString());
args.addAll(pointers);
writer.invokeDefCall(name, methodType, DefBootstrap.METHOD_CALL, args.toArray());
}
@Override
void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}

View File

@ -19,43 +19,66 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents a field load/store or shortcut on a def type. (Internal only.)
*/
final class LDefField extends ALink implements IDefLink {
final class PSubDefField extends AStoreable {
final String value;
private final String value;
LDefField(Location location, String value) {
super(location, 1);
PSubDefField(Location location, String value) {
super(location);
this.value = Objects.requireNonNull(value);
}
@Override
void extractVariables(Set<String> variables) {}
void extractVariables(Set<String> variables) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
ALink analyze(Locals locals) {
after = Definition.DEF_TYPE;
return this;
void analyze(Locals locals) {
actual = expected == null || explicit ? Definition.DEF_TYPE : expected;
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
org.objectweb.asm.Type methodType =
org.objectweb.asm.Type.getMethodType(actual.type, Definition.DEF_TYPE.type);
writer.invokeDefCall(value, methodType, DefBootstrap.LOAD);
}
@Override
int accessElementCount() {
return 1;
}
@Override
boolean isDefOptimized() {
return true;
}
@Override
void updateActual(Type actual) {
this.actual = actual;
}
@Override
void setup(MethodWriter writer, Globals globals) {
// Do nothing.
}
@ -63,7 +86,8 @@ final class LDefField extends ALink implements IDefLink {
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type);
org.objectweb.asm.Type methodType =
org.objectweb.asm.Type.getMethodType(actual.type, Definition.DEF_TYPE.type);
writer.invokeDefCall(value, methodType, DefBootstrap.LOAD);
}
@ -71,7 +95,8 @@ final class LDefField extends ALink implements IDefLink {
void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
org.objectweb.asm.Type methodType =
org.objectweb.asm.Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, actual.type);
writer.invokeDefCall(value, methodType, DefBootstrap.STORE);
}
}

View File

@ -0,0 +1,113 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Field;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.lang.reflect.Modifier;
import java.util.Objects;
import java.util.Set;
/**
* Represents a field load/store.
*/
final class PSubField extends AStoreable {
private final Field field;
public PSubField(Location location, Field field) {
super(location);
this.field = Objects.requireNonNull(field);
}
@Override
void extractVariables(Set<String> variables) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void analyze(Locals locals) {
if (write && Modifier.isFinal(field.modifiers)) {
throw createError(new IllegalArgumentException(
"Cannot write to read-only field [" + field.name + "] for type [" + field.type.name + "]."));
}
actual = field.type;
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.getStatic(field.owner.type, field.javaName, field.type.type);
} else {
writer.getField(field.owner.type, field.javaName, field.type.type);
}
}
@Override
int accessElementCount() {
return 1;
}
@Override
boolean isDefOptimized() {
return false;
}
@Override
void updateActual(Type actual) {
throw new IllegalArgumentException("Illegal tree structure.");
}
@Override
void setup(MethodWriter writer, Globals globals) {
// Do nothing.
}
@Override
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.getStatic(field.owner.type, field.javaName, field.type.type);
} else {
writer.getField(field.owner.type, field.javaName, field.type.type);
}
}
@Override
void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.putStatic(field.owner.type, field.javaName, field.type.type);
} else {
writer.putField(field.owner.type, field.javaName, field.type.type);
}
}
}

View File

@ -20,49 +20,53 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents a list load/store shortcut. (Internal only.)
*/
final class LListShortcut extends ALink {
final class PSubListShortcut extends AStoreable {
AExpression index;
Method getter;
Method setter;
private final Struct struct;
private AExpression index;
LListShortcut(Location location, AExpression index) {
super(location, 2);
private Method getter;
private Method setter;
PSubListShortcut(Location location, Struct struct, AExpression index) {
super(location);
this.struct = Objects.requireNonNull(struct);
this.index = Objects.requireNonNull(index);
}
@Override
void extractVariables(Set<String> variables) {
index.extractVariables(variables);
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
ALink analyze(Locals locals) {
getter = before.struct.methods.get(new Definition.MethodKey("get", 1));
setter = before.struct.methods.get(new Definition.MethodKey("set", 2));
void analyze(Locals locals) {
getter = struct.methods.get(new Definition.MethodKey("get", 1));
setter = struct.methods.get(new Definition.MethodKey("set", 2));
if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
getter.arguments.get(0).sort != Sort.INT)) {
throw createError(new IllegalArgumentException("Illegal list get shortcut for type [" + before.name + "]."));
throw createError(new IllegalArgumentException("Illegal list get shortcut for type [" + struct.name + "]."));
}
if (setter != null && (setter.arguments.size() != 2 || setter.arguments.get(0).sort != Sort.INT)) {
throw createError(new IllegalArgumentException("Illegal list set shortcut for type [" + before.name + "]."));
throw createError(new IllegalArgumentException("Illegal list set shortcut for type [" + struct.name + "]."));
}
if (getter != null && setter != null && (!getter.arguments.get(0).equals(setter.arguments.get(0))
@ -70,22 +74,48 @@ final class LListShortcut extends ALink {
throw createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
if ((read || write) && (!read || getter != null) && (!write || setter != null)) {
index.expected = Definition.INT_TYPE;
index.analyze(locals);
index = index.cast(locals);
after = setter != null ? setter.arguments.get(1) : getter.rtn;
actual = setter != null ? setter.arguments.get(1) : getter.rtn;
} else {
throw createError(new IllegalArgumentException("Illegal list shortcut for type [" + before.name + "]."));
throw createError(new IllegalArgumentException("Illegal list shortcut for type [" + struct.name + "]."));
}
return this;
}
@Override
void write(MethodWriter writer, Globals globals) {
index.write(writer, globals);
writer.writeDebugInfo(location);
getter.write(writer);
if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
writer.checkCast(getter.rtn.type);
}
}
@Override
int accessElementCount() {
return 2;
}
@Override
boolean isDefOptimized() {
return false;
}
@Override
void updateActual(Type actual) {
throw new IllegalArgumentException("Illegal tree structure.");
}
@Override
void setup(MethodWriter writer, Globals globals) {
index.write(writer, globals);
}
@Override

View File

@ -20,6 +20,8 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method;
@ -34,34 +36,37 @@ import org.elasticsearch.painless.MethodWriter;
/**
* Represents a map load/store shortcut. (Internal only.)
*/
final class LMapShortcut extends ALink {
final class PSubMapShortcut extends AStoreable {
AExpression index;
Method getter;
Method setter;
private final Struct struct;
private AExpression index;
LMapShortcut(Location location, AExpression index) {
super(location, 2);
private Method getter;
private Method setter;
PSubMapShortcut(Location location, Struct struct, AExpression index) {
super(location);
this.struct = Objects.requireNonNull(struct);
this.index = Objects.requireNonNull(index);
}
@Override
void extractVariables(Set<String> variables) {
index.extractVariables(variables);
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
ALink analyze(Locals locals) {
getter = before.struct.methods.get(new Definition.MethodKey("get", 1));
setter = before.struct.methods.get(new Definition.MethodKey("put", 2));
void analyze(Locals locals) {
getter = struct.methods.get(new Definition.MethodKey("get", 1));
setter = struct.methods.get(new Definition.MethodKey("put", 2));
if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1)) {
throw createError(new IllegalArgumentException("Illegal map get shortcut for type [" + before.name + "]."));
throw createError(new IllegalArgumentException("Illegal map get shortcut for type [" + struct.name + "]."));
}
if (setter != null && setter.arguments.size() != 2) {
throw createError(new IllegalArgumentException("Illegal map set shortcut for type [" + before.name + "]."));
throw createError(new IllegalArgumentException("Illegal map set shortcut for type [" + struct.name + "]."));
}
if (getter != null && setter != null &&
@ -69,22 +74,48 @@ final class LMapShortcut extends ALink {
throw createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
if ((read || write) && (!read || getter != null) && (!write || setter != null)) {
index.expected = setter != null ? setter.arguments.get(0) : getter.arguments.get(0);
index.analyze(locals);
index = index.cast(locals);
after = setter != null ? setter.arguments.get(1) : getter.rtn;
actual = setter != null ? setter.arguments.get(1) : getter.rtn;
} else {
throw createError(new IllegalArgumentException("Illegal map shortcut for type [" + before.name + "]."));
throw createError(new IllegalArgumentException("Illegal map shortcut for type [" + struct.name + "]."));
}
return this;
}
@Override
void write(MethodWriter writer, Globals globals) {
index.write(writer, globals);
writer.writeDebugInfo(location);
getter.write(writer);
if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
writer.checkCast(getter.rtn.type);
}
}
@Override
int accessElementCount() {
return 2;
}
@Override
boolean isDefOptimized() {
return false;
}
@Override
void updateActual(Type actual) {
throw new IllegalArgumentException("Illegal tree structure.");
}
@Override
void setup(MethodWriter writer, Globals globals) {
index.write(writer, globals);
}
@Override

View File

@ -19,75 +19,91 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Struct;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Set;
/**
* Represents a field load/store shortcut. (Internal only.)
*/
final class LShortcut extends ALink {
final class PSubShortcut extends AStoreable {
final String value;
private final String value;
private final String type;
private final Method getter;
private final Method setter;
Method getter = null;
Method setter = null;
PSubShortcut(Location location, String value, String type, Method getter, Method setter) {
super(location);
LShortcut(Location location, String value) {
super(location, 1);
this.value = Objects.requireNonNull(value);
this.value = value;
this.type = type;
this.getter = getter;
this.setter = setter;
}
@Override
void extractVariables(Set<String> variables) {}
@Override
ALink analyze(Locals locals) {
Struct struct = before.struct;
getter = struct.methods.get(new Definition.MethodKey("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));
if (getter == null) {
getter = struct.methods.get(new Definition.MethodKey("is" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));
}
setter = struct.methods.get(new Definition.MethodKey("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
void extractVariables(Set<String> variables) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void analyze(Locals locals) {
if (getter != null && (getter.rtn.sort == Sort.VOID || !getter.arguments.isEmpty())) {
throw createError(new IllegalArgumentException(
"Illegal get shortcut on field [" + value + "] for type [" + struct.name + "]."));
"Illegal get shortcut on field [" + value + "] for type [" + type + "]."));
}
if (setter != null && (setter.rtn.sort != Sort.VOID || setter.arguments.size() != 1)) {
throw createError(new IllegalArgumentException(
"Illegal set shortcut on field [" + value + "] for type [" + struct.name + "]."));
"Illegal set shortcut on field [" + value + "] for type [" + type + "]."));
}
if (getter != null && setter != null && setter.arguments.get(0) != getter.rtn) {
throw createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((getter != null || setter != null) && (!load || getter != null) && (!store || setter != null)) {
after = setter != null ? setter.arguments.get(0) : getter.rtn;
if ((getter != null || setter != null) && (!read || getter != null) && (!write || setter != null)) {
actual = setter != null ? setter.arguments.get(0) : getter.rtn;
} else {
throw createError(new IllegalArgumentException("Illegal shortcut on field [" + value + "] for type [" + struct.name + "]."));
throw createError(new IllegalArgumentException("Illegal shortcut on field [" + value + "] for type [" + type + "]."));
}
return this;
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
getter.write(writer);
if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
writer.checkCast(getter.rtn.type);
}
}
@Override
int accessElementCount() {
return 1;
}
@Override
boolean isDefOptimized() {
return false;
}
@Override
void updateActual(Type actual) {
throw new IllegalArgumentException("Illegal tree structure.");
}
@Override
void setup(MethodWriter writer, Globals globals) {
// Do nothing.
}

View File

@ -33,14 +33,14 @@ import java.util.Set;
*/
public final class SBlock extends AStatement {
final List<AStatement> statements;
private final List<AStatement> statements;
public SBlock(Location location, List<AStatement> statements) {
super(location);
this.statements = Collections.unmodifiableList(statements);
}
@Override
void extractVariables(Set<String> variables) {
for (AStatement statement : statements) {

View File

@ -34,9 +34,11 @@ public final class SBreak extends AStatement {
public SBreak(Location location) {
super(location);
}
@Override
void extractVariables(Set<String> variables) {}
void extractVariables(Set<String> variables) {
// Do nothing.
}
@Override
void analyze(Locals locals) {

View File

@ -20,33 +20,32 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents a catch block as part of a try-catch block.
*/
public final class SCatch extends AStatement {
final String type;
final String name;
final SBlock block;
private final String type;
private final String name;
private final SBlock block;
Variable variable;
private Variable variable = null;
Label begin;
Label end;
Label exception;
Label begin = null;
Label end = null;
Label exception = null;
public SCatch(Location location, String type, String name, SBlock block) {
super(location);
@ -55,10 +54,11 @@ public final class SCatch extends AStatement {
this.name = Objects.requireNonNull(name);
this.block = block;
}
@Override
void extractVariables(Set<String> variables) {
variables.add(name);
if (block != null) {
block.extractVariables(variables);
}

View File

@ -34,9 +34,11 @@ public final class SContinue extends AStatement {
public SContinue(Location location) {
super(location);
}
@Override
void extractVariables(Set<String> variables) {}
void extractVariables(Set<String> variables) {
// Do nothing.
}
@Override
void analyze(Locals locals) {

View File

@ -33,14 +33,14 @@ import java.util.Set;
*/
public final class SDeclBlock extends AStatement {
final List<SDeclaration> declarations;
private final List<SDeclaration> declarations;
public SDeclBlock(Location location, List<SDeclaration> declarations) {
super(location);
this.declarations = Collections.unmodifiableList(declarations);
}
@Override
void extractVariables(Set<String> variables) {
for (SDeclaration declaration : declarations) {

View File

@ -20,28 +20,27 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents a single variable declaration.
*/
public final class SDeclaration extends AStatement {
final String type;
final String name;
AExpression expression;
private final String type;
private final String name;
private AExpression expression;
Variable variable;
private Variable variable = null;
public SDeclaration(Location location, String type, String name, AExpression expression) {
super(location);
@ -50,10 +49,11 @@ public final class SDeclaration extends AStatement {
this.name = Objects.requireNonNull(name);
this.expression = expression;
}
@Override
void extractVariables(Set<String> variables) {
variables.add(name);
if (expression != null) {
expression.extractVariables(variables);
}

View File

@ -21,22 +21,24 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents a do-while loop.
*/
public final class SDo extends AStatement {
final SBlock block;
AExpression condition;
private final SBlock block;
private AExpression condition;
private boolean continuous = false;
public SDo(Location location, SBlock block, AExpression condition) {
super(location);
@ -44,10 +46,11 @@ public final class SDo extends AStatement {
this.condition = Objects.requireNonNull(condition);
this.block = block;
}
@Override
void extractVariables(Set<String> variables) {
condition.extractVariables(variables);
if (block != null) {
block.extractVariables(variables);
}
@ -75,7 +78,7 @@ public final class SDo extends AStatement {
condition = condition.cast(locals);
if (condition.constant != null) {
final boolean continuous = (boolean)condition.constant;
continuous = (boolean)condition.constant;
if (!continuous) {
throw createError(new IllegalArgumentException("Extraneous do while loop."));
@ -110,8 +113,10 @@ public final class SDo extends AStatement {
writer.mark(begin);
condition.fals = end;
condition.write(writer, globals);
if (!continuous) {
condition.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, end);
}
if (loopCounter != null) {
writer.writeLoopCounter(loopCounter.getSlot(), Math.max(1, block.statementCount), location);

View File

@ -19,51 +19,29 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
/**
* Represents a for-each loop shortcut for iterables. Defers to other S-nodes for non-iterable types.
* Represents a for-each loop and defers to subnodes depending on type.
*/
public class SEach extends AStatement {
final String type;
final String name;
AExpression expression;
final SBlock block;
private final String type;
private final String name;
private AExpression expression;
private final SBlock block;
// Members for all cases.
Variable variable = null;
Cast cast = null;
// Members for the array case.
Variable array = null;
Variable index = null;
Type indexed = null;
// Members for the iterable case.
Variable iterator = null;
Method method = null;
private AStatement sub = null;
public SEach(Location location, String type, String name, AExpression expression, SBlock block) {
super(location);
@ -73,11 +51,13 @@ public class SEach extends AStatement {
this.expression = Objects.requireNonNull(expression);
this.block = block;
}
@Override
void extractVariables(Set<String> variables) {
variables.add(name);
expression.extractVariables(variables);
if (block != null) {
block.extractVariables(variables);
}
@ -98,17 +78,18 @@ public class SEach extends AStatement {
}
locals = Locals.newLocalScope(locals);
variable = locals.addVariable(location, type, name, true);
Variable variable = locals.addVariable(location, type, name, true);
if (expression.actual.sort == Sort.ARRAY) {
analyzeArray(locals, type);
sub = new SSubEachArray(location, variable, expression, block);
} else if (expression.actual.sort == Sort.DEF || Iterable.class.isAssignableFrom(expression.actual.clazz)) {
analyzeIterable(locals, type);
sub = new SSubEachIterable(location, variable, expression, block);
} else {
throw createError(new IllegalArgumentException("Illegal for each type [" + expression.actual.name + "]."));
}
sub.analyze(locals);
if (block == null) {
throw createError(new IllegalArgumentException("Extraneous for each loop."));
}
@ -125,110 +106,12 @@ public class SEach extends AStatement {
statementCount = 1;
if (locals.hasVariable(Locals.LOOP)) {
loopCounter = locals.getVariable(location, Locals.LOOP);
sub.loopCounter = locals.getVariable(location, Locals.LOOP);
}
}
void analyzeArray(Locals variables, Type type) {
// We must store the array and index as variables for securing slots on the stack, and
// also add the location offset to make the names unique in case of nested for each loops.
array = variables.addVariable(location, expression.actual, "#array" + location.getOffset(), true);
index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true);
indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1);
cast = AnalyzerCaster.getLegalCast(location, indexed, type, true, true);
}
void analyzeIterable(Locals variables, Type type) {
// We must store the iterator as a variable for securing a slot on the stack, and
// also add the location offset to make the name unique in case of nested for each loops.
iterator = variables.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true);
if (expression.actual.sort == Sort.DEF) {
method = null;
} else {
method = expression.actual.struct.methods.get(new MethodKey("iterator", 0));
if (method == null) {
throw createError(new IllegalArgumentException(
"Unable to create iterator for the type [" + expression.actual.name + "]."));
}
}
cast = AnalyzerCaster.getLegalCast(location, Definition.DEF_TYPE, type, true, true);
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location);
if (array != null) {
writeArray(writer, globals);
} else if (iterator != null) {
writeIterable(writer, globals);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
void writeArray(MethodWriter writer, Globals globals) {
expression.write(writer, globals);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.getSlot());
writer.push(-1);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.getSlot());
Label begin = new Label();
Label end = new Label();
writer.mark(begin);
writer.visitIincInsn(index.getSlot(), 1);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
writer.arrayLength();
writer.ifICmp(MethodWriter.GE, end);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
writer.arrayLoad(indexed.type);
writer.writeCast(cast);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
block.write(writer, globals);
writer.goTo(begin);
writer.mark(end);
}
void writeIterable(MethodWriter writer, Globals globals) {
expression.write(writer, globals);
if (method == null) {
Type itr = Definition.getType("Iterator");
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type);
writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR);
} else {
method.write(writer);
}
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.getSlot());
Label begin = new Label();
Label end = new Label();
writer.mark(begin);
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT);
writer.ifZCmp(MethodWriter.EQ, end);
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
writer.writeCast(cast);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
block.write(writer, globals);
writer.goTo(begin);
writer.mark(end);
sub.write(writer, globals);
}
}

View File

@ -19,30 +19,29 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents the top-level node for an expression as a statement.
*/
public final class SExpression extends AStatement {
AExpression expression;
private AExpression expression;
public SExpression(Location location, AExpression expression) {
super(location);
this.expression = Objects.requireNonNull(expression);
}
@Override
void extractVariables(Set<String> variables) {
expression.extractVariables(variables);

View File

@ -21,23 +21,25 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents a for loop.
*/
public final class SFor extends AStatement {
ANode initializer;
AExpression condition;
AExpression afterthought;
final SBlock block;
private ANode initializer;
private AExpression condition;
private AExpression afterthought;
private final SBlock block;
private boolean continuous = false;
public SFor(Location location, ANode initializer, AExpression condition, AExpression afterthought, SBlock block) {
super(location);
@ -47,18 +49,21 @@ public final class SFor extends AStatement {
this.afterthought = afterthought;
this.block = block;
}
@Override
void extractVariables(Set<String> variables) {
if (initializer != null) {
initializer.extractVariables(variables);
}
if (condition != null) {
condition.extractVariables(variables);
}
if (afterthought != null) {
afterthought.extractVariables(variables);
}
if (block != null) {
block.extractVariables(variables);
}
@ -68,11 +73,9 @@ public final class SFor extends AStatement {
void analyze(Locals locals) {
locals = Locals.newLocalScope(locals);
boolean continuous = false;
if (initializer != null) {
if (initializer instanceof AStatement) {
((AStatement)initializer).analyze(locals);
initializer.analyze(locals);
} else if (initializer instanceof AExpression) {
AExpression initializer = (AExpression)this.initializer;
@ -150,7 +153,7 @@ public final class SFor extends AStatement {
Label end = new Label();
if (initializer instanceof SDeclBlock) {
((SDeclBlock)initializer).write(writer, globals);
initializer.write(writer, globals);
} else if (initializer instanceof AExpression) {
AExpression initializer = (AExpression)this.initializer;
@ -160,9 +163,9 @@ public final class SFor extends AStatement {
writer.mark(start);
if (condition != null) {
condition.fals = end;
if (condition != null && !continuous) {
condition.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, end);
}
boolean allEscape = false;
@ -179,6 +182,7 @@ public final class SFor extends AStatement {
if (loopCounter != null) {
writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location);
}
block.write(writer, globals);
} else {
if (loopCounter != null) {

View File

@ -23,16 +23,17 @@ import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.Constant;
import org.elasticsearch.painless.Def;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Parameter;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.WriterConstants;
import org.elasticsearch.painless.node.SSource.Reserved;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
@ -50,24 +51,49 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
/**
* Represents a user-defined function.
*/
public class SFunction extends AStatement {
public final class SFunction extends AStatement {
public static final class FunctionReserved implements Reserved {
public static final String THIS = "#this";
public static final String LOOP = "#loop";
private int maxLoopCounter = 0;
public void markReserved(String name) {
// Do nothing.
}
public boolean isReserved(String name) {
return name.equals(THIS) || name.equals(LOOP);
}
@Override
public void setMaxLoopCounter(int max) {
maxLoopCounter = max;
}
@Override
public int getMaxLoopCounter() {
return maxLoopCounter;
}
}
final FunctionReserved reserved;
final String rtnTypeStr;
private final String rtnTypeStr;
public final String name;
final List<String> paramTypeStrs;
final List<String> paramNameStrs;
final List<AStatement> statements;
private final List<String> paramTypeStrs;
private final List<String> paramNameStrs;
private final List<AStatement> statements;
public final boolean synthetic;
Type rtnType = null;
List<Parameter> parameters = new ArrayList<>();
Method method = null;
Variable loop = null;
private Variable loop = null;
public SFunction(FunctionReserved reserved, Location location,
String rtnType, String name, List<String> paramTypes,
List<String> paramNames, List<AStatement> statements, boolean synthetic) {
public SFunction(FunctionReserved reserved, Location location, String rtnType, String name,
List<String> paramTypes, List<String> paramNames, List<AStatement> statements,
boolean synthetic) {
super(location);
this.reserved = Objects.requireNonNull(reserved);
@ -78,14 +104,14 @@ public class SFunction extends AStatement {
this.statements = Collections.unmodifiableList(statements);
this.synthetic = synthetic;
}
@Override
void extractVariables(Set<String> variables) {
// we should never be extracting from a function, as functions are top-level!
throw new IllegalStateException("Illegal tree structure");
}
void generate() {
void generateSignature() {
try {
rtnType = Definition.getType(rtnTypeStr);
} catch (IllegalArgumentException exception) {
@ -150,7 +176,7 @@ public class SFunction extends AStatement {
loop = locals.getVariable(null, FunctionReserved.LOOP);
}
}
/** Writes the function to given ClassVisitor. */
void write (ClassVisitor writer, CompilerSettings settings, Globals globals) {
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
@ -185,7 +211,7 @@ public class SFunction extends AStatement {
}
String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size());
globals.addConstantInitializer(new Constant(location, WriterConstants.METHOD_HANDLE_TYPE,
globals.addConstantInitializer(new Constant(location, WriterConstants.METHOD_HANDLE_TYPE,
staticHandleFieldName, this::initializeConstant));
}
@ -197,43 +223,4 @@ public class SFunction extends AStatement {
false);
writer.push(handle);
}
/**
* Tracks reserved variables. Must be given to any source of input
* prior to beginning the analysis phase so that reserved variables
* are known ahead of time to assign appropriate slots without
* being wasteful.
*/
public interface Reserved {
void markReserved(String name);
boolean isReserved(String name);
void setMaxLoopCounter(int max);
int getMaxLoopCounter();
}
public static final class FunctionReserved implements Reserved {
public static final String THIS = "#this";
public static final String LOOP = "#loop";
private int maxLoopCounter = 0;
public void markReserved(String name) {
// Do nothing.
}
public boolean isReserved(String name) {
return name.equals(THIS) || name.equals(LOOP);
}
@Override
public void setMaxLoopCounter(int max) {
maxLoopCounter = max;
}
@Override
public int getMaxLoopCounter() {
return maxLoopCounter;
}
}
}

View File

@ -21,15 +21,15 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents an if block.
*/
@ -44,10 +44,11 @@ public final class SIf extends AStatement {
this.condition = Objects.requireNonNull(condition);
this.ifblock = ifblock;
}
@Override
void extractVariables(Set<String> variables) {
condition.extractVariables(variables);
if (ifblock != null) {
ifblock.extractVariables(variables);
}
@ -84,8 +85,8 @@ public final class SIf extends AStatement {
Label fals = new Label();
condition.fals = fals;
condition.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, fals);
ifblock.continu = continu;
ifblock.brake = brake;

View File

@ -21,23 +21,23 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents an if/else block.
*/
public final class SIfElse extends AStatement {
AExpression condition;
final SBlock ifblock;
final SBlock elseblock;
private AExpression condition;
private final SBlock ifblock;
private final SBlock elseblock;
public SIfElse(Location location, AExpression condition, SBlock ifblock, SBlock elseblock) {
super(location);
@ -46,13 +46,15 @@ public final class SIfElse extends AStatement {
this.ifblock = ifblock;
this.elseblock = elseblock;
}
@Override
void extractVariables(Set<String> variables) {
condition.extractVariables(variables);
if (ifblock != null) {
ifblock.extractVariables(variables);
}
if (elseblock != null) {
elseblock.extractVariables(variables);
}
@ -104,11 +106,11 @@ public final class SIfElse extends AStatement {
void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location);
Label fals = new Label();
Label end = new Label();
Label fals = elseblock != null ? new Label() : end;
condition.fals = fals;
condition.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, fals);
ifblock.continu = continu;
ifblock.brake = brake;

View File

@ -19,9 +19,9 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
@ -32,14 +32,14 @@ import java.util.Set;
*/
public final class SReturn extends AStatement {
AExpression expression;
private AExpression expression;
public SReturn(Location location, AExpression expression) {
super(location);
this.expression = Objects.requireNonNull(expression);
}
@Override
void extractVariables(Set<String> variables) {
expression.extractVariables(variables);

View File

@ -27,11 +27,10 @@ import org.elasticsearch.painless.Executable;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.node.SFunction.Reserved;
import org.elasticsearch.painless.WriterConstants;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.SimpleChecksAdapter;
import org.elasticsearch.painless.WriterConstants;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
@ -60,20 +59,72 @@ import static org.elasticsearch.painless.WriterConstants.MAP_TYPE;
*/
public final class SSource extends AStatement {
final String name;
final String source;
final Printer debugStream;
final CompilerSettings settings;
final MainMethodReserved reserved;
final List<SFunction> functions;
final Globals globals;
final List<AStatement> statements;
/**
* Tracks reserved variables. Must be given to any source of input
* prior to beginning the analysis phase so that reserved variables
* are known ahead of time to assign appropriate slots without
* being wasteful.
*/
public interface Reserved {
void markReserved(String name);
boolean isReserved(String name);
void setMaxLoopCounter(int max);
int getMaxLoopCounter();
}
public static final class MainMethodReserved implements Reserved {
private boolean score = false;
private boolean ctx = false;
private int maxLoopCounter = 0;
@Override
public void markReserved(String name) {
if (Locals.SCORE.equals(name)) {
score = true;
} else if (Locals.CTX.equals(name)) {
ctx = true;
}
}
@Override
public boolean isReserved(String name) {
return Locals.KEYWORDS.contains(name);
}
public boolean usesScore() {
return score;
}
public boolean usesCtx() {
return ctx;
}
@Override
public void setMaxLoopCounter(int max) {
maxLoopCounter = max;
}
@Override
public int getMaxLoopCounter() {
return maxLoopCounter;
}
}
private final CompilerSettings settings;
private final String name;
private final String source;
private final Printer debugStream;
private final MainMethodReserved reserved;
private final List<SFunction> functions;
private final Globals globals;
private final List<AStatement> statements;
private Locals mainMethod;
private byte[] bytes;
public SSource(CompilerSettings settings, String name, String source, Printer debugStream,
MainMethodReserved reserved, Location location,
public SSource(CompilerSettings settings, String name, String source, Printer debugStream,
MainMethodReserved reserved, Location location,
List<SFunction> functions, Globals globals, List<AStatement> statements) {
super(location);
this.settings = Objects.requireNonNull(settings);
@ -88,18 +139,18 @@ public final class SSource extends AStatement {
this.statements = Collections.unmodifiableList(statements);
this.globals = globals;
}
@Override
void extractVariables(Set<String> variables) {
// we should never be extracting from a function, as functions are top-level!
throw new IllegalStateException("Illegal tree structure");
throw new IllegalStateException("Illegal tree structure.");
}
public void analyze() {
Map<MethodKey, Method> methods = new HashMap<>();
for (SFunction function : functions) {
function.generate();
function.generateSignature();
MethodKey key = new MethodKey(function.name, function.parameters.size());
@ -114,7 +165,7 @@ public final class SSource extends AStatement {
@Override
void analyze(Locals program) {
for (SFunction function : functions) {
Locals functionLocals = Locals.newFunctionScope(program, function.rtnType, function.parameters,
Locals functionLocals = Locals.newFunctionScope(program, function.rtnType, function.parameters,
function.reserved.getMaxLoopCounter());
function.analyze(functionLocals);
}
@ -154,7 +205,7 @@ public final class SSource extends AStatement {
ClassWriter writer = new ClassWriter(classFrames);
ClassVisitor visitor = writer;
// if picky is enabled, turn on some checks. instead of VerifyError at the end, you get a helpful stacktrace.
if (settings.isPicky()) {
visitor = new SimpleChecksAdapter(visitor);
@ -180,12 +231,12 @@ public final class SSource extends AStatement {
execute.visitCode();
write(execute, globals);
execute.endMethod();
// Write all functions:
for (SFunction function : functions) {
function.write(visitor, settings, globals);
}
// Write all synthetic functions. Note that this process may add more :)
while (!globals.getSyntheticMethods().isEmpty()) {
List<SFunction> current = new ArrayList<>(globals.getSyntheticMethods().values());
@ -210,7 +261,7 @@ public final class SSource extends AStatement {
}
// Initialize the constants in a static initializer
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
WriterConstants.CLINIT, visitor, globals.getStatements(), settings);
clinit.visitCode();
for (Constant constant : inits) {
@ -220,13 +271,13 @@ public final class SSource extends AStatement {
clinit.returnValue();
clinit.endMethod();
}
// End writing the class and store the generated bytes.
visitor.visitEnd();
bytes = writer.toByteArray();
}
@Override
void write(MethodWriter writer, Globals globals) {
if (reserved.usesScore()) {
@ -281,43 +332,4 @@ public final class SSource extends AStatement {
public byte[] getBytes() {
return bytes;
}
public static final class MainMethodReserved implements Reserved {
private boolean score = false;
private boolean ctx = false;
private int maxLoopCounter = 0;
@Override
public void markReserved(String name) {
if (Locals.SCORE.equals(name)) {
score = true;
} else if (Locals.CTX.equals(name)) {
ctx = true;
}
}
@Override
public boolean isReserved(String name) {
return Locals.KEYWORDS.contains(name);
}
public boolean usesScore() {
return score;
}
public boolean usesCtx() {
return ctx;
}
@Override
public void setMaxLoopCounter(int max) {
maxLoopCounter = max;
}
@Override
public int getMaxLoopCounter() {
return maxLoopCounter;
}
}
}

View File

@ -0,0 +1,108 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
import java.util.Set;
/**
* Represents a for-each loop for arrays.
*/
final class SSubEachArray extends AStatement {
private final Variable variable;
private AExpression expression;
private final SBlock block;
private Cast cast = null;
private Variable array = null;
private Variable index = null;
private Type indexed = null;
public SSubEachArray(Location location, Variable variable, AExpression expression, SBlock block) {
super(location);
this.variable = Objects.requireNonNull(variable);
this.expression = Objects.requireNonNull(expression);
this.block = block;
}
@Override
void extractVariables(Set<String> variables) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void analyze(Locals locals) {
// We must store the array and index as variables for securing slots on the stack, and
// also add the location offset to make the names unique in case of nested for each loops.
array = locals.addVariable(location, expression.actual, "#array" + location.getOffset(), true);
index = locals.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true);
indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1);
cast = AnalyzerCaster.getLegalCast(location, indexed, variable.type, true, true);
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location);
expression.write(writer, globals);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.getSlot());
writer.push(-1);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.getSlot());
Label begin = new Label();
Label end = new Label();
writer.mark(begin);
writer.visitIincInsn(index.getSlot(), 1);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
writer.arrayLength();
writer.ifICmp(MethodWriter.GE, end);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
writer.arrayLoad(indexed.type);
writer.writeCast(cast);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
if (loopCounter != null) {
writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location);
}
block.write(writer, globals);
writer.goTo(begin);
writer.mark(end);
}
}

View File

@ -0,0 +1,130 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
/**
* Represents a for-each loop for iterables.
*/
final class SSubEachIterable extends AStatement {
private AExpression expression;
private final SBlock block;
private final Variable variable;
private Cast cast = null;
private Variable iterator = null;
private Method method = null;
public SSubEachIterable(Location location, Variable variable, AExpression expression, SBlock block) {
super(location);
this.variable = Objects.requireNonNull(variable);
this.expression = Objects.requireNonNull(expression);
this.block = block;
}
@Override
void extractVariables(Set<String> variables) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
@Override
void analyze(Locals locals) {
// We must store the iterator as a variable for securing a slot on the stack, and
// also add the location offset to make the name unique in case of nested for each loops.
iterator = locals.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true);
if (expression.actual.sort == Sort.DEF) {
method = null;
} else {
method = expression.actual.struct.methods.get(new MethodKey("iterator", 0));
if (method == null) {
throw createError(new IllegalArgumentException(
"Unable to create iterator for the type [" + expression.actual.name + "]."));
}
}
cast = AnalyzerCaster.getLegalCast(location, Definition.DEF_TYPE, variable.type, true, true);
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location);
expression.write(writer, globals);
if (method == null) {
Type itr = Definition.getType("Iterator");
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type);
writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR);
} else {
method.write(writer);
}
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.getSlot());
Label begin = new Label();
Label end = new Label();
writer.mark(begin);
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT);
writer.ifZCmp(MethodWriter.EQ, end);
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
writer.writeCast(cast);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
if (loopCounter != null) {
writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location);
}
block.write(writer, globals);
writer.goTo(begin);
writer.mark(end);
}
}

View File

@ -21,8 +21,8 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.Objects;
@ -33,14 +33,14 @@ import java.util.Set;
*/
public final class SThrow extends AStatement {
AExpression expression;
private AExpression expression;
public SThrow(Location location, AExpression expression) {
super(location);
this.expression = Objects.requireNonNull(expression);
}
@Override
void extractVariables(Set<String> variables) {
expression.extractVariables(variables);

View File

@ -21,9 +21,9 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import java.util.Collections;
import java.util.List;
@ -34,8 +34,8 @@ import java.util.Set;
*/
public final class STry extends AStatement {
final SBlock block;
final List<SCatch> catches;
private final SBlock block;
private final List<SCatch> catches;
public STry(Location location, SBlock block, List<SCatch> catches) {
super(location);
@ -43,7 +43,7 @@ public final class STry extends AStatement {
this.block = block;
this.catches = Collections.unmodifiableList(catches);
}
@Override
void extractVariables(Set<String> variables) {
if (block != null) {

View File

@ -21,22 +21,24 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.painless.MethodWriter;
/**
* Represents a while loop.
*/
public final class SWhile extends AStatement {
AExpression condition;
final SBlock block;
private AExpression condition;
private final SBlock block;
private boolean continuous = false;
public SWhile(Location location, AExpression condition, SBlock block) {
super(location);
@ -44,7 +46,7 @@ public final class SWhile extends AStatement {
this.condition = Objects.requireNonNull(condition);
this.block = block;
}
@Override
void extractVariables(Set<String> variables) {
condition.extractVariables(variables);
@ -61,8 +63,6 @@ public final class SWhile extends AStatement {
condition.analyze(locals);
condition = condition.cast(locals);
boolean continuous = false;
if (condition.constant != null) {
continuous = (boolean)condition.constant;
@ -109,8 +109,10 @@ public final class SWhile extends AStatement {
writer.mark(begin);
condition.fals = end;
condition.write(writer, globals);
if (!continuous) {
condition.write(writer, globals);
writer.ifZCmp(Opcodes.IFEQ, end);
}
if (block != null) {
if (loopCounter != null) {

View File

@ -22,52 +22,58 @@
* <p>
* The following are the types of nodes:
* A* (abstract) - These are the abstract nodes that are the superclasses for the other types.
* I* (interface) -- These are marker interfaces to denote a property of the node.
* S* (statement) - These are nodes that represent a statement in Painless. These are the highest level nodes.
* E* (expression) - These are nodes that represent an expression in Painless. These are the middle level nodes.
* L* (link) - These are nodes that represent a piece of a variable/method chain. The are the lowest level nodes.
* I* (interface) - These are marker interfaces to denote a property of the node.
* S* (statement) - These are nodes that represent a statement in Painless.
* E* (expression) - These are nodes that represent an expression in Painless.
* P* (postfix) - These are nodes that represent a postfix of a variable chain.
* E/P* (storeable) - These are nodes that are allowed to store a value to memory.
* *Sub* (sub) - These are partial nodes with a parent (S/E/P)* node used to split up logic into smaller pieces.
* <p>
* The following is a brief description of each node:
* {@link org.elasticsearch.painless.node.AExpression} - The superclass for all E* (expression) nodes.
* {@link org.elasticsearch.painless.node.ALink} - The superclass for all L* (link) nodes.
* {@link org.elasticsearch.painless.node.ANode} - The superclass for all other nodes.
* {@link org.elasticsearch.painless.node.AExpression} - The superclass for all E* (expression) and P* (postfix) nodes.
* {@link org.elasticsearch.painless.node.ANode} - The superclass for all nodes.
* {@link org.elasticsearch.painless.node.AStatement} - The superclass for all S* (statement) nodes.
* {@link org.elasticsearch.painless.node.AStoreable} - The super class for an expression that can store a value in local memory.
* {@link org.elasticsearch.painless.node.EAssignment} - Represents an assignment with the lhs and rhs as child nodes.
* {@link org.elasticsearch.painless.node.EBinary} - Represents a binary math expression.
* {@link org.elasticsearch.painless.node.EBool} - Represents a boolean expression.
* {@link org.elasticsearch.painless.node.EBoolean} - Represents a boolean constant.
* {@link org.elasticsearch.painless.node.ECallLocal} - Represents a user-defined call.
* {@link org.elasticsearch.painless.node.ECapturingFunctionRef} - Represents a function reference (capturing).
* {@link org.elasticsearch.painless.node.ECast} - Represents an implicit cast in most cases. (Internal only.)
* {@link org.elasticsearch.painless.node.EChain} - Represents the entirety of a variable/method chain for read/write operations.
* {@link org.elasticsearch.painless.node.ECast} - Represents a cast inserted into the tree replacing others. (Internal only.)
* {@link org.elasticsearch.painless.node.EComp} - Represents a comparison expression.
* {@link org.elasticsearch.painless.node.EConditional} - Represents a conditional expression.
* {@link org.elasticsearch.painless.node.EConstant} - Represents a constant. (Internal only.)
* {@link org.elasticsearch.painless.node.EConstant} - Represents a constant inserted into the tree replacing others. (Internal only.)
* {@link org.elasticsearch.painless.node.EDecimal} - Represents a decimal constant.
* {@link org.elasticsearch.painless.node.EExplicit} - Represents an explicit cast.
* {@link org.elasticsearch.painless.node.EFunctionRef} - Represents a function reference (non-capturing).
* {@link org.elasticsearch.painless.node.EInstanceof} - Represents an instanceof check.
* {@link org.elasticsearch.painless.node.ELambda} - Represents a lambda function.
* {@link org.elasticsearch.painless.node.EListInit} - Represents a list initialization shortcut.
* {@link org.elasticsearch.painless.node.EMapInit} - Represents a map initialization shortcut.
* {@link org.elasticsearch.painless.node.ENewArray} - Represents an array instantiation.
* {@link org.elasticsearch.painless.node.ENewObj} - Represents and object instantiation.
* {@link org.elasticsearch.painless.node.ENull} - Represents a null constant.
* {@link org.elasticsearch.painless.node.ENumeric} - Represents a non-decimal numeric constant.
* {@link org.elasticsearch.painless.node.ERegex} - Represents a regular expression constant.
* {@link org.elasticsearch.painless.node.EStatic} - Represents a static type target.
* {@link org.elasticsearch.painless.node.EString} - Represents a string constant.
* {@link org.elasticsearch.painless.node.EUnary} - Represents a unary math expression.
* {@link org.elasticsearch.painless.node.IDefLink} - A marker interface for all LDef* (link) nodes.
* {@link org.elasticsearch.painless.node.LArrayLength} - Represents an array length field load.
* {@link org.elasticsearch.painless.node.LBrace} - Represents an array load/store or defers to possible shortcuts.
* {@link org.elasticsearch.painless.node.LCallInvoke} - Represents a method call or defers to a def call.
* {@link org.elasticsearch.painless.node.LCallLocal} - Represents a user-defined call.
* {@link org.elasticsearch.painless.node.LCast} - Represents a cast made in a variable/method chain.
* {@link org.elasticsearch.painless.node.LDefArray} - Represents an array load/store or shortcut on a def type. (Internal only.)
* {@link org.elasticsearch.painless.node.LDefCall} - Represents a method call made on a def type. (Internal only.)
* {@link org.elasticsearch.painless.node.LDefField} - Represents a field load/store or shortcut on a def type. (Internal only.)
* {@link org.elasticsearch.painless.node.LField} - Represents a field load/store or defers to a possible shortcuts.
* {@link org.elasticsearch.painless.node.LListShortcut} - Represents a list load/store shortcut. (Internal only.)
* {@link org.elasticsearch.painless.node.LMapShortcut} - Represents a map load/store shortcut. (Internal only.)
* {@link org.elasticsearch.painless.node.LNewArray} - Represents an array instantiation.
* {@link org.elasticsearch.painless.node.LNewObj} - Represents and object instantiation.
* {@link org.elasticsearch.painless.node.LShortcut} - Represents a field load/store shortcut. (Internal only.)
* {@link org.elasticsearch.painless.node.LStatic} - Represents a static type target.
* {@link org.elasticsearch.painless.node.LString} - Represents a string constant.
* {@link org.elasticsearch.painless.node.LVariable} - Represents a variable load/store.
* {@link org.elasticsearch.painless.node.EVariable} - Represents a variable load/store.
* {@link org.elasticsearch.painless.node.ILambda} - Represents a marker to signify this node is a lambda function.
* {@link org.elasticsearch.painless.node.PBrace} - Represents an array load/store and defers to a child subnode.
* {@link org.elasticsearch.painless.node.PCallInvoke} - Represents a method call and defers to a child subnode.
* {@link org.elasticsearch.painless.node.PField} - Represents a field load/store and defers to a child subnode.
* {@link org.elasticsearch.painless.node.PSubArrayLength} - Represents an array length field load.
* {@link org.elasticsearch.painless.node.PSubBrace} - Represents an array load/store.
* {@link org.elasticsearch.painless.node.PSubCallInvoke} - Represents a method call.
* {@link org.elasticsearch.painless.node.PSubDefArray} - Represents an array load/store or shortcut on a def type. (Internal only.)
* {@link org.elasticsearch.painless.node.PSubDefCall} - Represents a method call made on a def type. (Internal only.)
* {@link org.elasticsearch.painless.node.PSubDefField} - Represents a field load/store or shortcut on a def type. (Internal only.)
* {@link org.elasticsearch.painless.node.PSubField} - Represents a field load/store.
* {@link org.elasticsearch.painless.node.PSubListShortcut} - Represents a list load/store shortcut. (Internal only.)
* {@link org.elasticsearch.painless.node.PSubMapShortcut} - Represents a map load/store shortcut. (Internal only.)
* {@link org.elasticsearch.painless.node.PSubShortcut} - Represents a field load/store shortcut. (Internal only.)
* {@link org.elasticsearch.painless.node.SBlock} - Represents a set of statements as a branch of control-flow.
* {@link org.elasticsearch.painless.node.SBreak} - Represents a break statement.
* {@link org.elasticsearch.painless.node.SCatch} - Represents a catch block as part of a try-catch block.
@ -75,7 +81,7 @@
* {@link org.elasticsearch.painless.node.SDeclaration} - Represents a single variable declaration.
* {@link org.elasticsearch.painless.node.SDeclBlock} - Represents a series of declarations.
* {@link org.elasticsearch.painless.node.SDo} - Represents a do-while loop.
* {@link org.elasticsearch.painless.node.SEach} - Represents a for each loop shortcut for iterables.
* {@link org.elasticsearch.painless.node.SEach} - Represents a for-each loop and defers to subnodes depending on type.
* {@link org.elasticsearch.painless.node.SExpression} - Represents the top-level node for an expression as a statement.
* {@link org.elasticsearch.painless.node.SFor} - Represents a for loop.
* {@link org.elasticsearch.painless.node.SFunction} - Represents a user-defined function.
@ -83,6 +89,8 @@
* {@link org.elasticsearch.painless.node.SIfElse} - Represents an if/else block.
* {@link org.elasticsearch.painless.node.SReturn} - Represents a return statement.
* {@link org.elasticsearch.painless.node.SSource} - The root of all Painless trees. Contains a series of statements.
* {@link org.elasticsearch.painless.node.SSubEachArray} - Represents a for-each loop for arrays.
* {@link org.elasticsearch.painless.node.SSubEachIterable} - Represents a for-each loop for iterables.
* {@link org.elasticsearch.painless.node.SThrow} - Represents a throw statement.
* {@link org.elasticsearch.painless.node.STry} - Represents the try block as part of a try-catch block.
* {@link org.elasticsearch.painless.node.SWhile} - Represents a while loop.
@ -92,14 +100,14 @@
* <p>
* All Painless trees must start with an SSource node at the root. Each node has a constructor that requires
* all of its values and children be passed in at the time of instantiation. This means that Painless trees
* are build bottom-up; however, this helps enforce tree structure to be correct and fits naturally with a
* are build bottom-up; however, this helps enforce tree structure correctness and fits naturally with a
* standard recursive-descent parser.
* <p>
* Generally, statement nodes have member data that evaluate legal control-flow during the analysis phase.
* The typical order for statement nodes is for each node to call analyze on it's children during the analysis phase
* and write on it's children during the writing phase.
* <p>
* Generally, expression nodes have member data that evaluate static types. The typical order for an expression node
* Generally, expression nodes have member data that evaluate static and def types. The typical order for an expression node
* during the analysis phase looks like the following:
* {@code
* For known expected types:
@ -127,16 +135,24 @@
* }
* Expression nodes just call each child during the writing phase.
* <p>
* Generally, link nodes have member data that help keep track of items necessary to do a
* load/store on a variable/field/method. Analysis of link nodes happens in a chain node
* where each link node will be analysed with the chain node acting as a bridge to pass the
* previous link's after type to the next link's before type. Upon analysis completion, a link
* will return either itself or another link node depending on if a shortcut or def type was found.
* Cast nodes as links will return null and be removed from the chain node if the cast is
* unnecessary. Link nodes have three methods for writing -- write, load, and store. The write
* method is always once called before a load/store to give links a chance to write any values
* such as array indices before the load/store happens. Load is called to read a link node, and
* store is called to write a link node. Note that store will only ever be called on the final
* link node in a chain, all previous links will be considered loads.
* Postfix nodes represent postfixes in a variable/method chain including braces, calls, or fields.
* Postfix nodes will always have a prefix node that is the prior piece of the variable/method chain.
* Analysis of a postfix node will cause a chain of analysis calls to happen where the prefix will
* be analyzed first and continue until the prefix is not a postfix node. Writing out a series of
* loads from a postfix node works in the same fashion. Stores work somewhat differently as
* described by later documentation.
* <p>
* Storebable nodes have three methods for writing -- setup, load, and store. These methods
* are used in conjuction with a parent node aware of the storeable node (lhs) that has a node
* representing a value to store (rhs). The setup method is always once called before a store
* to give storeable nodes a chance to write any prefixes they may have and any values such as
* array indices before the store happens. Load is called on a storeable node that must also
* be read from, and store is called to write a value to memory.
* <p>
* Sub nodes are partial nodes that require a parent to work correctly. These nodes can really
* represent anything the parent node would like to split up into logical pieces and don't really
* have any distinct set of rules. The currently existing subnodes all have ANode as a super class
* somewhere in their class heirachy so the parent node can defer some analysis and writing to
* the sub node.
*/
package org.elasticsearch.painless.node;

View File

@ -67,7 +67,7 @@ public class BasicAPITests extends ScriptTestCase {
ctx.put("_source", _source);
params.put("ctx", ctx);
assertEquals("testvalue", exec("ctx._source['load'].5 = ctx._source['load'].remove('load5')", params));
assertEquals("testvalue", exec("ctx._source['load'].5 = ctx._source['load'].remove('load5')", params, true));
}
/** Test loads and stores with a list */
@ -118,7 +118,7 @@ public class BasicAPITests extends ScriptTestCase {
assertEquals("{}", exec("Map map = new HashMap(); return map.toString();"));
assertEquals("{}", exec("def map = new HashMap(); return map.toString();"));
}
public void testPrimitivesHaveMethods() {
assertEquals(5, exec("int x = 5; return x.intValue();"));
assertEquals("5", exec("int x = 5; return x.toString();"));

View File

@ -68,14 +68,14 @@ public class BasicExpressionTests extends ScriptTestCase {
"((Map)y).put(2, 3);\n" +
"return x.get(2);\n"));
}
public void testIllegalDefCast() {
Exception exception = expectScriptThrows(ClassCastException.class, () -> {
Exception exception = expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 1.0; int y = x; return y;");
});
assertTrue(exception.getMessage().contains("cannot be cast"));
exception = expectScriptThrows(ClassCastException.class, () -> {
exception = expectScriptThrows(ClassCastException.class, () -> {
exec("def x = (short)1; byte y = x; return y;");
});
assertTrue(exception.getMessage().contains("cannot be cast"));
@ -112,11 +112,11 @@ public class BasicExpressionTests extends ScriptTestCase {
*/
public void testBoxing() {
// return
assertEquals(4, exec("return params.get(\"x\");", Collections.singletonMap("x", 4)));
assertEquals(4, exec("return params.get(\"x\");", Collections.singletonMap("x", 4), true));
// assignment
assertEquals(4, exec("int y = params.get(\"x\"); return y;", Collections.singletonMap("x", 4)));
assertEquals(4, exec("int y = params.get(\"x\"); return y;", Collections.singletonMap("x", 4), true));
// comparison
assertEquals(true, exec("return 5 > params.get(\"x\");", Collections.singletonMap("x", 4)));
assertEquals(true, exec("return 5 > params.get(\"x\");", Collections.singletonMap("x", 4), true));
}
public void testBool() {

View File

@ -251,25 +251,25 @@ public class BasicStatementTests extends ScriptTestCase {
assertEquals(10, exec("def i = 1; if (i == 1) {i = 2; return 10}"));
assertEquals(10, exec("def i = 1; if (i == 1) {i = 2; return 10} else {return 12}"));
}
public void testArrayLoopWithoutCounter() {
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
"for (int i = 0; i < array.length; i++) { sum += array[i] } return sum",
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
"for (int i = 0; i < array.length; i++) { sum += array[i] } return sum",
Collections.emptyMap(),
Collections.singletonMap(CompilerSettings.MAX_LOOP_COUNTER, "0"),
null
null, true
));
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
"int i = 0; while (i < array.length) { sum += array[i++] } return sum",
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
"int i = 0; while (i < array.length) { sum += array[i++] } return sum",
Collections.emptyMap(),
Collections.singletonMap(CompilerSettings.MAX_LOOP_COUNTER, "0"),
null
null, true
));
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
"int i = 0; do { sum += array[i++] } while (i < array.length); return sum",
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
"int i = 0; do { sum += array[i++] } while (i < array.length); return sum",
Collections.emptyMap(),
Collections.singletonMap(CompilerSettings.MAX_LOOP_COUNTER, "0"),
null
null, true
));
}
}

View File

@ -23,6 +23,7 @@ package org.elasticsearch.painless;
public class EqualsTests extends ScriptTestCase {
public void testTypesEquals() {
assertEquals(true, exec("return false === false;"));
assertEquals(false, exec("boolean x = false; boolean y = true; return x === y;"));
assertEquals(true, exec("boolean x = false; boolean y = false; return x === y;"));
assertEquals(false, exec("return (byte)3 === (byte)4;"));
assertEquals(true, exec("byte x = 3; byte y = 3; return x === y;"));
@ -40,6 +41,7 @@ public class EqualsTests extends ScriptTestCase {
assertEquals(true, exec("double x = 3; double y = 3; return x === y;"));
assertEquals(true, exec("return false == false;"));
assertEquals(false, exec("boolean x = false; boolean y = true; return x == y;"));
assertEquals(true, exec("boolean x = false; boolean y = false; return x == y;"));
assertEquals(false, exec("return (byte)3 == (byte)4;"));
assertEquals(true, exec("byte x = 3; byte y = 3; return x == y;"));
@ -59,6 +61,7 @@ public class EqualsTests extends ScriptTestCase {
public void testTypesNotEquals() {
assertEquals(false, exec("return true !== true;"));
assertEquals(true, exec("boolean x = true; boolean y = false; return x !== y;"));
assertEquals(false, exec("boolean x = false; boolean y = false; return x !== y;"));
assertEquals(true, exec("return (byte)3 !== (byte)4;"));
assertEquals(false, exec("byte x = 3; byte y = 3; return x !== y;"));
@ -76,6 +79,7 @@ public class EqualsTests extends ScriptTestCase {
assertEquals(false, exec("double x = 3; double y = 3; return x !== y;"));
assertEquals(false, exec("return true != true;"));
assertEquals(true, exec("boolean x = true; boolean y = false; return x != y;"));
assertEquals(false, exec("boolean x = false; boolean y = false; return x != y;"));
assertEquals(true, exec("return (byte)3 != (byte)4;"));
assertEquals(false, exec("byte x = 3; byte y = 3; return x != y;"));

View File

@ -64,7 +64,7 @@ public class NoSemiColonTests extends ScriptTestCase {
assertEquals(10, exec("10"));
assertEquals(10, exec("5 + 5"));
assertEquals(10, exec("5 + 5"));
assertEquals(10, exec("params.param == 'yes' ? 10 : 5", Collections.singletonMap("param", "yes")));
assertEquals(10, exec("params.param == 'yes' ? 10 : 5", Collections.singletonMap("param", "yes"), true));
}
@SuppressWarnings("rawtypes")

View File

@ -0,0 +1,64 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
public class PostfixTests extends ScriptTestCase {
public void testConstantPostfixes() {
assertEquals("2", exec("2.toString()"));
assertEquals(4, exec("[1, 2, 3, 4, 5][3]"));
assertEquals("4", exec("[1, 2, 3, 4, 5][3].toString()"));
assertEquals(3, exec("new int[] {1, 2, 3, 4, 5}[2]"));
assertEquals("4", exec("(2 + 2).toString()"));
}
public void testConditionalPostfixes() {
assertEquals("5", exec("boolean b = false; (b ? 4 : 5).toString()"));
assertEquals(3, exec(
"Map x = new HashMap(); x['test'] = 3;" +
"Map y = new HashMap(); y['test'] = 4;" +
"boolean b = true;" +
"return (int)(b ? x : y).get('test')")
);
}
public void testAssignmentPostfixes() {
assertEquals(true, exec("int x; '3' == (x = 3).toString()"));
assertEquals(-1, exec("int x; (x = 3).compareTo(4)"));
assertEquals(3L, exec("long[] x; (x = new long[1])[0] = 3; return x[0]"));
assertEquals(2, exec("int x; ((x)) = 2; return x;"));
}
public void testDefConditionalPostfixes() {
assertEquals("5", exec("def b = false; (b ? 4 : 5).toString()"));
assertEquals(3, exec(
"def x = new HashMap(); x['test'] = 3;" +
"def y = new HashMap(); y['test'] = 4;" +
"boolean b = true;" +
"return (b ? x : y).get('test')")
);
}
public void testDefAssignmentPostfixes() {
assertEquals(true, exec("def x; '3' == (x = 3).toString()"));
assertEquals(-1, exec("def x; (x = 3).compareTo(4)"));
assertEquals(3L, exec("def x; (x = new long[1])[0] = 3; return x[0]"));
assertEquals(2, exec("def x; ((x)) = 2; return x;"));
}
}

View File

@ -114,8 +114,8 @@ public class RegexTests extends ScriptTestCase {
}
public void testFindOnInput() {
assertEquals(true, exec("return params.s =~ /foo/", singletonMap("s", "fooasdfdf")));
assertEquals(false, exec("return params.s =~ /foo/", singletonMap("s", "11f2ooasdfdf")));
assertEquals(true, exec("return params.s =~ /foo/", singletonMap("s", "fooasdfdf"), true));
assertEquals(false, exec("return params.s =~ /foo/", singletonMap("s", "11f2ooasdfdf"), true));
}
public void testGroup() {
@ -183,7 +183,7 @@ public class RegexTests extends ScriptTestCase {
public void testReplaceAllMatchesCharSequence() {
CharSequence charSequence = CharBuffer.wrap("the quick brown fox");
assertEquals("thE qUIck brOwn fOx",
exec("params.a.replaceAll(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence)));
exec("params.a.replaceAll(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence), true));
}
public void testReplaceAllNoMatchString() {
@ -193,7 +193,7 @@ public class RegexTests extends ScriptTestCase {
public void testReplaceAllNoMatchCharSequence() {
CharSequence charSequence = CharBuffer.wrap("i am cat");
assertEquals("i am cat",
exec("params.a.replaceAll(/dolphin/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence)));
exec("params.a.replaceAll(/dolphin/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence), true));
}
public void testReplaceAllQuoteReplacement() {
@ -211,7 +211,7 @@ public class RegexTests extends ScriptTestCase {
public void testReplaceFirstMatchesCharSequence() {
CharSequence charSequence = CharBuffer.wrap("the quick brown fox");
assertEquals("thE quick brown fox",
exec("params.a.replaceFirst(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence)));
exec("params.a.replaceFirst(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence), true));
}
public void testReplaceFirstNoMatchString() {
@ -221,7 +221,7 @@ public class RegexTests extends ScriptTestCase {
public void testReplaceFirstNoMatchCharSequence() {
CharSequence charSequence = CharBuffer.wrap("i am cat");
assertEquals("i am cat",
exec("params.a.replaceFirst(/dolphin/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence)));
exec("params.a.replaceFirst(/dolphin/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence), true));
}
public void testReplaceFirstQuoteReplacement() {
@ -255,7 +255,7 @@ public class RegexTests extends ScriptTestCase {
public void testBogusRegexFlag() {
IllegalArgumentException e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("/asdf/b", emptyMap(), emptyMap(), null); // Not picky so we get a non-assertion error
exec("/asdf/b", false); // Not picky so we get a non-assertion error
});
assertEquals("unexpected token ['b'] was expecting one of [{<EOF>, ';'}].", e.getMessage());
}

View File

@ -75,7 +75,7 @@ public class ReservedWordTests extends ScriptTestCase {
/** check that we can modify its contents though */
public void testCtxStoreMap() {
assertEquals(5, exec("ctx.foo = 5; return ctx.foo;", Collections.singletonMap("ctx", new HashMap<String,Object>())));
assertEquals(5, exec("ctx.foo = 5; return ctx.foo;", Collections.singletonMap("ctx", new HashMap<String,Object>()), true));
}
/** check that we can't declare a variable of _value, its really reserved! */

View File

@ -53,7 +53,8 @@ public class ScoreTests extends ScriptTestCase {
public float score() throws IOException {
return 2.5f;
}
}));
},
true));
}
public void testScoreNotUsed() {
@ -63,7 +64,8 @@ public class ScoreTests extends ScriptTestCase {
public float score() throws IOException {
throw new AssertionError("score() should not be called");
}
}));
},
true));
}
public void testScoreCached() {
@ -77,6 +79,7 @@ public class ScoreTests extends ScriptTestCase {
}
throw new AssertionError("score() should not be called twice");
}
}));
},
true));
}
}

View File

@ -46,12 +46,12 @@ public class ScriptEngineTests extends ScriptTestCase {
obj1.put("l", Arrays.asList("2", "1"));
vars.put("obj1", obj1);
Object value = exec("return params['obj1'];", vars);
Object value = exec("return params['obj1'];", vars, true);
obj1 = (Map<String, Object>)value;
assertEquals("value1", obj1.get("prop1"));
assertEquals("value2", ((Map<String, Object>) obj1.get("obj2")).get("prop2"));
value = exec("return params.obj1.l.0;", vars);
value = exec("return params.obj1.l.0;", vars, true);
assertEquals("2", value);
}
@ -65,15 +65,15 @@ public class ScriptEngineTests extends ScriptTestCase {
obj1.put("obj2", obj2);
vars.put("l", Arrays.asList("1", "2", "3", obj1));
assertEquals(4, exec("return params.l.size();", vars));
assertEquals("1", exec("return params.l.0;", vars));
assertEquals(4, exec("return params.l.size();", vars, true));
assertEquals("1", exec("return params.l.0;", vars, true));
Object value = exec("return params.l.3;", vars);
Object value = exec("return params.l.3;", vars, true);
obj1 = (Map<String, Object>)value;
assertEquals("value1", obj1.get("prop1"));
assertEquals("value2", ((Map<String, Object>)obj1.get("obj2")).get("prop2"));
assertEquals("value1", exec("return params.l.3.prop1;", vars));
assertEquals("value1", exec("return params.l.3.prop1;", vars, true));
}
public void testChangingVarsCrossExecution1() {

View File

@ -22,6 +22,7 @@ package org.elasticsearch.painless;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.common.lucene.ScorerAware;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.painless.antlr.Walker;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptException;
@ -49,19 +50,30 @@ public abstract class ScriptTestCase extends ESTestCase {
/** Compiles and returns the result of {@code script} */
public Object exec(String script) {
return exec(script, null);
return exec(script, null, true);
}
/** Compiles and returns the result of {@code script} with access to {@code picky} */
public Object exec(String script, boolean picky) {
return exec(script, null, picky);
}
/** Compiles and returns the result of {@code script} with access to {@code vars} */
public Object exec(String script, Map<String, Object> vars) {
public Object exec(String script, Map<String, Object> vars, boolean picky) {
Map<String,String> compilerSettings = new HashMap<>();
compilerSettings.put(CompilerSettings.PICKY, "true");
compilerSettings.put(CompilerSettings.INITIAL_CALL_SITE_DEPTH, random().nextBoolean() ? "0" : "10");
return exec(script, vars, compilerSettings, null);
return exec(script, vars, compilerSettings, null, picky);
}
/** Compiles and returns the result of {@code script} with access to {@code vars} and compile-time parameters */
public Object exec(String script, Map<String, Object> vars, Map<String,String> compileParams, Scorer scorer) {
public Object exec(String script, Map<String, Object> vars, Map<String,String> compileParams, Scorer scorer, boolean picky) {
// test for ambiguity errors before running the actual script if picky is true
if (picky) {
CompilerSettings pickySettings = new CompilerSettings();
pickySettings.setPicky(true);
Walker.buildPainlessTree(getTestName(), script, pickySettings, null);
}
// test actual script execution
Object object = scriptEngine.compile(null, script, compileParams);
CompiledScript compiled = new CompiledScript(ScriptService.ScriptType.INLINE, getTestName(), "painless", object);
ExecutableScript executableScript = scriptEngine.executable(compiled, vars);
@ -79,7 +91,7 @@ public abstract class ScriptTestCase extends ESTestCase {
final String asm = Debugger.toString(script);
assertTrue("bytecode not found, got: \n" + asm , asm.contains(bytecode));
}
/**
* Uses the {@link Debugger} to get the bytecode output for a script and compare
* it against an expected bytecode pattern as a regular expression (please try to avoid!)
@ -88,7 +100,7 @@ public abstract class ScriptTestCase extends ESTestCase {
final String asm = Debugger.toString(script);
assertTrue("bytecode not found, got: \n" + asm , asm.matches(pattern));
}
/** Checks a specific exception class is thrown (boxed inside ScriptException) and returns it. */
public static <T extends Throwable> T expectScriptThrows(Class<T> expectedType, ThrowingRunnable runnable) {
try {
@ -104,7 +116,7 @@ public abstract class ScriptTestCase extends ESTestCase {
assertion.initCause(e);
throw assertion;
}
AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected "
AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected "
+ expectedType.getSimpleName());
assertion.initCause(e);
throw assertion;

View File

@ -31,27 +31,27 @@ public class TryCatchTests extends ScriptTestCase {
});
assertEquals("test", exception.getMessage());
}
/** catches the exact exception */
public void testCatch() {
assertEquals(1, exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
"catch (RuntimeException e) { return 1; } return 2;",
Collections.singletonMap("param", "true")));
assertEquals(1, exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
"catch (RuntimeException e) { return 1; } return 2;",
Collections.singletonMap("param", "true"), true));
}
/** catches superclass of the exception */
public void testCatchSuperclass() {
assertEquals(1, exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
"catch (Exception e) { return 1; } return 2;",
Collections.singletonMap("param", "true")));
assertEquals(1, exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
"catch (Exception e) { return 1; } return 2;",
Collections.singletonMap("param", "true"), true));
}
/** tries to catch a different type of exception */
public void testNoCatch() {
RuntimeException exception = expectScriptThrows(RuntimeException.class, () -> {
exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
"catch (ArithmeticException e) { return 1; } return 2;",
Collections.singletonMap("param", "true"));
exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
"catch (ArithmeticException e) { return 1; } return 2;",
Collections.singletonMap("param", "true"), true);
});
assertEquals("test", exception.getMessage());
}

View File

@ -85,7 +85,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
public void testBogusParameter() {
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
exec("return 5;", null, Collections.singletonMap("bogusParameterKey", "bogusParameterValue"), null);
exec("return 5;", null, Collections.singletonMap("bogusParameterKey", "bogusParameterValue"), null, true);
});
assertTrue(expected.getMessage().contains("Unrecognized compile-time parameter"));
}
@ -138,7 +138,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
"The maximum number of statements that can be executed in a loop has been reached."));
RuntimeException parseException = expectScriptThrows(RuntimeException.class, () -> {
exec("try { int x; } catch (PainlessError error) {}");
exec("try { int x; } catch (PainlessError error) {}", false);
fail("should have hit ParseException");
});
assertTrue(parseException.getMessage().contains("unexpected token ['PainlessError']"));
@ -208,9 +208,9 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
public void testRCurlyNotDelim() {
IllegalArgumentException e = expectScriptThrows(IllegalArgumentException.class, () -> {
// We don't want PICKY here so we get the normal error message
exec("def i = 1} return 1", emptyMap(), emptyMap(), null);
exec("def i = 1} return 1", emptyMap(), emptyMap(), null, false);
});
assertEquals("invalid sequence of tokens near ['}'].", e.getMessage());
assertEquals("unexpected token ['}'] was expecting one of [<EOF>].", e.getMessage());
}
public void testBadBoxingCast() {

View File

@ -1,94 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.antlr;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.DiagnosticErrorListener;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.elasticsearch.painless.antlr.PainlessParser.SourceContext;
import org.elasticsearch.painless.ScriptTestCase;
import java.text.ParseException;
public class ParserTests extends ScriptTestCase {
private static class TestException extends RuntimeException {
TestException(String msg) {
super(msg);
}
}
private SourceContext buildAntlrTree(String source) {
ANTLRInputStream stream = new ANTLRInputStream(source);
PainlessLexer lexer = new EnhancedPainlessLexer(stream, "testing");
PainlessParser parser = new PainlessParser(new CommonTokenStream(lexer));
ParserErrorStrategy strategy = new ParserErrorStrategy("testing");
lexer.removeErrorListeners();
parser.removeErrorListeners();
// Diagnostic listener invokes syntaxError on other listeners for ambiguity issues,
parser.addErrorListener(new DiagnosticErrorListener(true));
// a second listener to fail the test when the above happens.
parser.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol, final int line,
final int charPositionInLine, final String msg, final RecognitionException e) {
throw new TestException("line: " + line + ", offset: " + charPositionInLine +
", symbol:" + offendingSymbol + " " + msg);
}
});
// Enable exact ambiguity detection (costly). we enable exact since its the default for
// DiagnosticErrorListener, life is too short to think about what 'inexact ambiguity' might mean.
parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
parser.setErrorHandler(strategy);
return parser.source();
}
public void testIllegalSecondary() {
//TODO: Need way more corner case tests.
Exception exception = expectThrows(TestException.class, () -> buildAntlrTree("(x = 5).y"));
assertTrue(exception.getMessage().contains("no viable alternative"));
exception = expectThrows(TestException.class, () -> buildAntlrTree("((x = 5).y = 2).z;"));
assertTrue(exception.getMessage().contains("no viable alternative"));
exception = expectThrows(TestException.class, () -> buildAntlrTree("(2 + 2).z"));
assertTrue(exception.getMessage().contains("no viable alternative"));
exception = expectThrows(RuntimeException.class, () -> buildAntlrTree("((Map)x.-x)"));
assertTrue(exception.getMessage().contains("unexpected character"));
}
public void testLambdaSyntax() {
buildAntlrTree("call(p -> {p.doSomething();});");
buildAntlrTree("call(int p -> {p.doSomething();});");
buildAntlrTree("call((p, u, v) -> {p.doSomething(); blah = 1;});");
buildAntlrTree("call(1, (p, u, v) -> {p.doSomething(); blah = 1;}, 3);");
buildAntlrTree("call((p, u, v) -> {p.doSomething(); blah = 1;});");
buildAntlrTree("call(x, y, z, (int p, int u, int v) -> {p.doSomething(); blah = 1;});");
buildAntlrTree("call(x, y, z, (long p, List u, String v) -> {p.doSomething(); blah = 1;});");
buildAntlrTree("call(x, y, z, (int p, u, int v) -> {p.doSomething(); blah = 1;});");
buildAntlrTree("call(x, (int p, u, int v) -> {p.doSomething(); blah = 1;}, z," +
" (int p, u, int v) -> {p.doSomething(); blah = 1;}, 'test');");
}
}