Fix explicit casts and improve tests.

This commit is contained in:
Robert Muir 2016-06-19 03:19:45 -04:00
parent 8f96dd53aa
commit 8d9fa7e0b5
23 changed files with 572 additions and 157 deletions

View File

@ -34,6 +34,11 @@ public final class CompilerSettings {
*/
public static final String PICKY = "picky";
/**
* For testing: do not use.
*/
public static final String INITIAL_CALL_SITE_DEPTH = "initialCallSiteDepth";
/**
* The maximum number of statements allowed to be run in a loop.
*/
@ -45,6 +50,11 @@ public final class CompilerSettings {
*/
private boolean picky = false;
/**
* For testing. Do not use.
*/
private int initialCallSiteDepth = 0;
/**
* Returns the value for the cumulative total number of statements that can be made in all loops
* in a script before an exception is thrown. This attempts to prevent infinite loops. Note if
@ -78,4 +88,20 @@ public final class CompilerSettings {
public void setPicky(boolean picky) {
this.picky = picky;
}
/**
* Returns initial call site depth. This means we pretend we've already seen N different types,
* to better exercise fallback code in tests.
*/
public int getInitialCallSiteDepth() {
return initialCallSiteDepth;
}
/**
* For testing megamorphic fallbacks. Do not use.
* @see #getInitialCallSiteDepth()
*/
public void setInitialCallSiteDepth(int depth) {
this.initialCallSiteDepth = depth;
}
}

View File

@ -280,6 +280,7 @@ public final class Def {
CallSite nested = DefBootstrap.bootstrap(lookup,
call,
nestedType,
0,
DefBootstrap.REFERENCE,
interfaceType.name);
filter = nested.dynamicInvoker();

View File

@ -27,6 +27,7 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.invoke.WrongMethodTypeException;
/**
* Painless invokedynamic bootstrap for the call site.
@ -80,10 +81,19 @@ public final class DefBootstrap {
/**
* static bootstrap parameter indicating the binary operator is part of compound assignment (e.g. +=).
*
* <p>
* may require {@link MethodHandles#explicitCastArguments}, or a dynamic cast
* to cast back to the receiver's type, depending on types seen.
*/
public static final int OPERATOR_COMPOUND_ASSIGNMENT = 1 << 1;
/**
* static bootstrap parameter indicating an explicit cast to the return type.
* <p>
* may require {@link MethodHandles#explicitCastArguments}, depending on types seen.
*/
public static final int OPERATOR_EXPLICIT_CAST = 1 << 2;
/**
* CallSite that implements the polymorphic inlining cache (PIC).
*/
@ -97,7 +107,7 @@ public final class DefBootstrap {
private final Object[] args;
int depth; // pkg-protected for testing
PIC(Lookup lookup, String name, MethodType type, int flavor, Object[] args) {
PIC(Lookup lookup, String name, MethodType type, int initialDepth, int flavor, Object[] args) {
super(type);
if (type.parameterType(0) != Object.class) {
throw new BootstrapMethodError("The receiver type (1st arg) of invokedynamic descriptor must be Object.");
@ -106,6 +116,7 @@ public final class DefBootstrap {
this.name = name;
this.flavor = flavor;
this.args = args;
this.depth = initialDepth;
MethodHandle fallback = FALLBACK.bindTo(this)
.asCollector(Object[].class, type.parameterCount())
@ -226,11 +237,14 @@ public final class DefBootstrap {
private final int flavor;
private final int flags;
MIC(String name, MethodType type, int flavor, int flags) {
MIC(String name, MethodType type, int initialDepth, int flavor, int flags) {
super(type);
this.name = name;
this.flavor = flavor;
this.flags = flags;
if (initialDepth > 0) {
initialized = true;
}
MethodHandle fallback = FALLBACK.bindTo(this)
.asCollector(Object[].class, type.parameterCount())
@ -248,7 +262,9 @@ public final class DefBootstrap {
case SHIFT_OPERATOR:
// shifts are treated as unary, as java allows long arguments without a cast (but bits are ignored)
MethodHandle unary = DefMath.lookupUnary(args[0].getClass(), name);
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
if ((flags & OPERATOR_EXPLICIT_CAST) != 0) {
unary = DefMath.cast(type().returnType(), unary);
} else if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
unary = DefMath.cast(args[0].getClass(), unary);
}
return unary;
@ -257,7 +273,9 @@ public final class DefBootstrap {
return lookupGeneric(); // can handle nulls, casts if supported
} else {
MethodHandle binary = DefMath.lookupBinary(args[0].getClass(), args[1].getClass(), name);
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
if ((flags & OPERATOR_EXPLICIT_CAST) != 0) {
binary = DefMath.cast(type().returnType(), binary);
} else if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
binary = DefMath.cast(args[0].getClass(), binary);
}
return binary;
@ -267,11 +285,15 @@ public final class DefBootstrap {
}
private MethodHandle lookupGeneric() {
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
return DefMath.lookupGenericWithCast(name);
} else {
return DefMath.lookupGeneric(name);
MethodHandle target = DefMath.lookupGeneric(name);
if ((flags & OPERATOR_EXPLICIT_CAST) != 0) {
// static cast to the return type
target = DefMath.dynamicCast(target, type().returnType());
} else if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
// dynamic cast to the receiver's type
target = DefMath.dynamicCast(target);
}
return target;
}
/**
@ -288,7 +310,15 @@ public final class DefBootstrap {
}
final MethodType type = type();
final MethodHandle target = lookup(args).asType(type);
MethodHandle target = lookup(args);
// for math operators: WrongMethodType can be confusing. convert into a ClassCastException if they screw up.
try {
target = target.asType(type);
} catch (WrongMethodTypeException e) {
Exception exc = new ClassCastException("Cannot cast from: " + target.type().returnType() + " to " + type.returnType());
exc.initCause(e);
throw exc;
}
final MethodHandle test;
if (flavor == BINARY_OPERATOR || flavor == SHIFT_OPERATOR) {
@ -384,12 +414,16 @@ public final class DefBootstrap {
/**
* invokeDynamic bootstrap method
* <p>
* In addition to ordinary parameters, we also take a static parameter {@code flavor} which
* tells us what type of dynamic call it is (and which part of whitelist to look at).
* In addition to ordinary parameters, we also take some static parameters:
* <ul>
* <li>{@code initialDepth}: initial call site depth. this is used to exercise megamorphic fallback.
* <li>{@code flavor}: type of dynamic call it is (and which part of whitelist to look at).
* <li>{@code args}: flavor-specific args.
* </ul>
* <p>
* see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
*/
public static CallSite bootstrap(Lookup lookup, String name, MethodType type, int flavor, Object... args) {
public static CallSite bootstrap(Lookup lookup, String name, MethodType type, int initialDepth, int flavor, Object... args) {
// validate arguments
switch(flavor) {
// "function-call" like things get a polymorphic cache
@ -408,7 +442,7 @@ public final class DefBootstrap {
if (args.length != numLambdas + 1) {
throw new BootstrapMethodError("Illegal number of parameters: expected " + numLambdas + " references");
}
return new PIC(lookup, name, type, flavor, args);
return new PIC(lookup, name, type, initialDepth, flavor, args);
case LOAD:
case STORE:
case ARRAY_LOAD:
@ -417,7 +451,7 @@ public final class DefBootstrap {
if (args.length > 0) {
throw new BootstrapMethodError("Illegal static bootstrap parameters for flavor: " + flavor);
}
return new PIC(lookup, name, type, flavor, args);
return new PIC(lookup, name, type, initialDepth, flavor, args);
case REFERENCE:
if (args.length != 1) {
throw new BootstrapMethodError("Invalid number of parameters for reference call");
@ -425,7 +459,7 @@ public final class DefBootstrap {
if (args[0] instanceof String == false) {
throw new BootstrapMethodError("Illegal parameter for reference call: " + args[0]);
}
return new PIC(lookup, name, type, flavor, args);
return new PIC(lookup, name, type, initialDepth, flavor, args);
// operators get monomorphic cache, with a generic impl for a fallback
case UNARY_OPERATOR:
@ -442,11 +476,11 @@ public final class DefBootstrap {
// we just don't need it anywhere else.
throw new BootstrapMethodError("This parameter is only supported for BINARY_OPERATORs");
}
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0 && flavor != BINARY_OPERATOR) {
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0 && flavor != BINARY_OPERATOR && flavor != SHIFT_OPERATOR) {
// we just don't need it anywhere else.
throw new BootstrapMethodError("This parameter is only supported for BINARY_OPERATORs");
throw new BootstrapMethodError("This parameter is only supported for BINARY/SHIFT_OPERATORs");
}
return new MIC(name, type, flavor, flags);
return new MIC(name, type, initialDepth, flavor, flags);
default:
throw new BootstrapMethodError("Illegal static bootstrap parameter for flavor: " + flavor);
}

View File

@ -25,6 +25,7 @@ import java.lang.invoke.MethodType;
import java.lang.invoke.MethodHandles.Lookup;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -1027,25 +1028,7 @@ public class DefMath {
* class if its not a boxed type.
*/
private static Class<?> unbox(Class<?> clazz) {
if (clazz == Boolean.class) {
return boolean.class;
} else if (clazz == Byte.class) {
return byte.class;
} else if (clazz == Short.class) {
return short.class;
} else if (clazz == Character.class) {
return char.class;
} else if (clazz == Integer.class) {
return int.class;
} else if (clazz == Long.class) {
return long.class;
} else if (clazz == Float.class) {
return float.class;
} else if (clazz == Double.class) {
return double.class;
} else {
return clazz;
}
return MethodType.methodType(clazz).unwrap().returnType();
}
/** Unary promotion. All Objects are promoted to Object. */
@ -1147,39 +1130,49 @@ public class DefMath {
return TYPE_OP_MAPPING.get(Object.class).get(name);
}
/**
* Slow dynamic cast: casts {@code returnValue} to the runtime type of {@code lhs}
* based upon inspection. If {@code lhs} is null, no cast takes place.
* This is used for the generic fallback case of compound assignment.
*/
static Object dynamicCast(Object returnValue, Object lhs) {
static Object dynamicReceiverCast(Object returnValue, Object lhs) {
if (lhs != null) {
Class<?> c = lhs.getClass();
if (c == returnValue.getClass()) {
return returnValue;
}
if (c == Integer.class) {
return getNumber(returnValue).intValue();
} else if (c == Long.class) {
return getNumber(returnValue).longValue();
} else if (c == Double.class) {
return getNumber(returnValue).doubleValue();
} else if (c == Float.class) {
return getNumber(returnValue).floatValue();
} else if (c == Short.class) {
return getNumber(returnValue).shortValue();
} else if (c == Byte.class) {
return getNumber(returnValue).byteValue();
} else if (c == Character.class) {
return (char) getNumber(returnValue).intValue();
}
return lhs.getClass().cast(returnValue);
return dynamicCast(lhs.getClass(), returnValue);
} else {
return returnValue;
}
}
/**
* Slow dynamic cast: casts {@code value} to an instance of {@code clazz}
* based upon inspection. If {@code lhs} is null, no cast takes place.
*/
static Object dynamicCast(Class<?> clazz, Object value) {
if (value != null) {
if (clazz == value.getClass()) {
return value;
}
if (clazz == Integer.class) {
return getNumber(value).intValue();
} else if (clazz == Long.class) {
return getNumber(value).longValue();
} else if (clazz == Double.class) {
return getNumber(value).doubleValue();
} else if (clazz == Float.class) {
return getNumber(value).floatValue();
} else if (clazz == Short.class) {
return getNumber(value).shortValue();
} else if (clazz == Byte.class) {
return getNumber(value).byteValue();
} else if (clazz == Character.class) {
return (char) getNumber(value).intValue();
}
return clazz.cast(value);
} else {
return value;
}
}
/** Slowly returns a Number for o. Just for supporting dynamicCast */
static Number getNumber(Object o) {
if (o instanceof Number) {
@ -1192,11 +1185,15 @@ public class DefMath {
}
private static final MethodHandle DYNAMIC_CAST;
private static final MethodHandle DYNAMIC_RECEIVER_CAST;
static {
final Lookup lookup = MethodHandles.lookup();
try {
DYNAMIC_CAST = lookup.findStatic(lookup.lookupClass(),
"dynamicCast",
MethodType.methodType(Object.class, Class.class, Object.class));
DYNAMIC_RECEIVER_CAST = lookup.findStatic(lookup.lookupClass(),
"dynamicReceiverCast",
MethodType.methodType(Object.class, Object.class, Object.class));
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
@ -1204,16 +1201,24 @@ public class DefMath {
}
/** Looks up generic method, with a dynamic cast to the receiver's type. (compound assignment) */
public static MethodHandle lookupGenericWithCast(String name) {
MethodHandle generic = lookupGeneric(name);
// adapt dynamic cast to the generic method
MethodHandle cast = DYNAMIC_CAST.asType(MethodType.methodType(generic.type().returnType(),
generic.type().returnType(),
generic.type().parameterType(0)));
public static MethodHandle dynamicCast(MethodHandle target) {
// adapt dynamic receiver cast to the generic method
MethodHandle cast = DYNAMIC_RECEIVER_CAST.asType(MethodType.methodType(target.type().returnType(),
target.type().returnType(),
target.type().parameterType(0)));
// drop the RHS parameter
cast = MethodHandles.dropArguments(cast, 2, generic.type().parameterType(1));
cast = MethodHandles.dropArguments(cast, 2, target.type().parameterType(1));
// combine: f(x,y) -> g(f(x,y), x, y);
return MethodHandles.foldArguments(cast, generic);
return MethodHandles.foldArguments(cast, target);
}
/** Looks up generic method, with a dynamic cast to the specified type. (explicit assignment) */
public static MethodHandle dynamicCast(MethodHandle target, Class<?> desired) {
// adapt dynamic cast to the generic method
desired = MethodType.methodType(desired).wrap().returnType();
// bind to the boxed type
MethodHandle cast = DYNAMIC_CAST.bindTo(desired);
return MethodHandles.filterReturnValue(target, cast);
}
/** Forces a cast to class A for target (only if types differ) */
@ -1221,12 +1226,18 @@ public class DefMath {
MethodType newType = MethodType.methodType(classA).unwrap();
MethodType targetType = MethodType.methodType(target.type().returnType()).unwrap();
// don't do a conversion if types are the same. explicitCastArguments has this opto,
// but we do it explicitly, to make the boolean check simpler
if (newType.returnType() == targetType.returnType()) {
return target; // no conversion
return target;
}
// this is safe for our uses of it here only, because we change just the return value,
// the original method itself does all the type checks correctly.
// we don't allow the to/from boolean conversions of explicitCastArguments
if (newType.returnType() == boolean.class || targetType.returnType() == boolean.class) {
throw new ClassCastException("Cannot cast " + targetType.returnType() + " to " + newType.returnType());
}
// null return values are not possible for our arguments.
return MethodHandles.explicitCastArguments(target, target.type().changeReturnType(newType.returnType()));
}
}

View File

@ -30,6 +30,7 @@ import org.objectweb.asm.commons.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Deque;
import java.util.List;
@ -78,15 +79,17 @@ import static org.elasticsearch.painless.WriterConstants.UTILITY_TYPE;
*/
public final class MethodWriter extends GeneratorAdapter {
private final BitSet statements;
private final CompilerSettings settings;
private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs =
(INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) ? null : new ArrayDeque<>();
public MethodWriter(int access, Method method, ClassVisitor cw, BitSet statements) {
public MethodWriter(int access, Method method, ClassVisitor cw, BitSet statements, CompilerSettings settings) {
super(Opcodes.ASM5, cw.visitMethod(access, method.getName(), method.getDescriptor(), null, null),
access, method.getName(), method.getDescriptor());
this.statements = statements;
this.settings = settings;
}
/**
@ -269,23 +272,18 @@ public final class MethodWriter extends GeneratorAdapter {
/** Writes a dynamic binary instruction: returnType, lhs, and rhs can be different */
public void writeDynamicBinaryInstruction(Location location, Type returnType, Type lhs, Type rhs,
Operation operation, boolean compoundAssignment) {
Operation operation, int flags) {
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(returnType.type, lhs.type, rhs.type);
String descriptor = methodType.getDescriptor();
int flags = 0;
if (compoundAssignment) {
flags |= DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT;
}
switch (operation) {
case MUL:
invokeDynamic("mul", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
invokeDefCall("mul", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case DIV:
invokeDynamic("div", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
invokeDefCall("div", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case REM:
invokeDynamic("rem", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
invokeDefCall("rem", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case ADD:
// if either side is primitive, then the + operator should always throw NPE on null,
@ -295,28 +293,28 @@ public final class MethodWriter extends GeneratorAdapter {
if (!hasPrimitiveArg) {
flags |= DefBootstrap.OPERATOR_ALLOWS_NULL;
}
invokeDynamic("add", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
invokeDefCall("add", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case SUB:
invokeDynamic("sub", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
invokeDefCall("sub", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case LSH:
invokeDynamic("lsh", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags);
invokeDefCall("lsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break;
case USH:
invokeDynamic("ush", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags);
invokeDefCall("ush", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break;
case RSH:
invokeDynamic("rsh", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags);
invokeDefCall("rsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break;
case BWAND:
invokeDynamic("and", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
invokeDefCall("and", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case XOR:
invokeDynamic("xor", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
invokeDefCall("xor", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case BWOR:
invokeDynamic("or", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
invokeDefCall("or", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
default:
throw location.createError(new IllegalStateException("Illegal tree structure."));
@ -391,4 +389,19 @@ public final class MethodWriter extends GeneratorAdapter {
public void visitEnd() {
throw new AssertionError("Should never call this method on MethodWriter, use endMethod() instead");
}
/**
* Writes a dynamic call for a def method.
* @param name method name
* @param methodType callsite signature
* @param flavor type of call
* @param params flavor-specific parameters
*/
public void invokeDefCall(String name, org.objectweb.asm.Type methodType, int flavor, Object... params) {
Object[] args = new Object[params.length + 2];
args[0] = settings.getInitialCallSiteDepth();
args[1] = flavor;
System.arraycopy(params, 0, args, 2, params.length);
invokeDynamic(name, methodType.getDescriptor(), DEF_BOOTSTRAP_HANDLE, args);
}
}

View File

@ -128,6 +128,12 @@ public final class PainlessScriptEngineService extends AbstractComponent impleme
compilerSettings.setPicky(Boolean.parseBoolean(value));
}
value = copy.remove(CompilerSettings.INITIAL_CALL_SITE_DEPTH);
if (value != null) {
compilerSettings.setInitialCallSiteDepth(Integer.parseInt(value));
}
if (!copy.isEmpty()) {
throw new IllegalArgumentException("Unrecognized compile-time parameter(s): " + copy);
}

View File

@ -85,9 +85,10 @@ public final class WriterConstants {
public final static Method MATCHER_FIND = getAsmMethod(boolean.class, "find");
/** dynamic callsite bootstrap signature */
public final static MethodType DEF_BOOTSTRAP_TYPE =
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class, Object[].class);
public final static Handle DEF_BOOTSTRAP_HANDLE =
final static MethodType DEF_BOOTSTRAP_TYPE =
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class,
int.class, int.class, Object[].class);
final static Handle DEF_BOOTSTRAP_HANDLE =
new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(DefBootstrap.class),
"bootstrap", DEF_BOOTSTRAP_TYPE.toMethodDescriptorString(), false);

View File

@ -241,7 +241,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
statements.add((AStatement)visit(statement));
}
return new SSource(sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(),
return new SSource(settings, sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(),
location(ctx), functions, globals, statements);
}

View File

@ -20,6 +20,7 @@
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;
@ -42,6 +43,7 @@ public final class EBinary extends AExpression {
Type shiftDistance; // for shifts, the RHS is promoted independently
boolean cat = false;
boolean originallyExplicit = false; // record whether there was originally an explicit cast
public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
super(location);
@ -53,6 +55,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) {
@ -639,7 +642,13 @@ public final class EBinary extends AExpression {
right.write(writer, globals);
if (promote.sort == Sort.DEF || (shiftDistance != null && shiftDistance.sort == Sort.DEF)) {
writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, false);
// def calls adopt the wanted return value. if there was a narrowing cast,
// we need to flag that so that its done at runtime.
int flags = 0;
if (originallyExplicit) {
flags |= DefBootstrap.OPERATOR_EXPLICIT_CAST;
}
writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, flags);
} else {
writer.writeBinaryInstruction(location, actual, operation);
}

View File

@ -30,7 +30,6 @@ import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
import java.lang.invoke.LambdaMetafactory;
@ -90,8 +89,8 @@ public class ECapturingFunctionRef extends AExpression implements ILambda {
} else if (ref == null) {
// typed interface, dynamic implementation
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
String descriptor = Type.getMethodType(expected.type, captured.type.type).getDescriptor();
writer.invokeDynamic(call, descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.REFERENCE, expected.name);
Type methodType = Type.getMethodType(expected.type, captured.type.type);
writer.invokeDefCall(call, methodType, DefBootstrap.REFERENCE, expected.name);
} else {
// typed interface, typed implementation
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());

View File

@ -26,6 +26,7 @@ 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;
@ -214,7 +215,10 @@ public final class EChain extends AExpression {
expression.expected = expression.actual;
} else if (shift) {
if (shiftDistance.sort == Sort.LONG) {
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 {
@ -353,7 +357,7 @@ public final class EChain extends AExpression {
// write the operation instruction for compound assignment
if (promote.sort == Sort.DEF) {
writer.writeDynamicBinaryInstruction(location, promote,
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, true);
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
} else {
writer.writeBinaryInstruction(location, promote, operation);
}

View File

@ -33,7 +33,6 @@ import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.OBJECTS_TYPE;
import static org.elasticsearch.painless.WriterConstants.EQUALS;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/**
* Represents a comparison expression.
@ -498,8 +497,7 @@ public final class EComp extends AExpression {
if (right.isNull) {
writer.ifNull(jump);
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
writer.invokeDynamic("eq", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR,
DefBootstrap.OPERATOR_ALLOWS_NULL);
writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
writejump = false;
} else {
writer.ifCmp(promotedType.type, MethodWriter.EQ, jump);
@ -508,23 +506,22 @@ public final class EComp extends AExpression {
if (right.isNull) {
writer.ifNonNull(jump);
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
writer.invokeDynamic("eq", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR,
DefBootstrap.OPERATOR_ALLOWS_NULL);
writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
writer.ifZCmp(MethodWriter.EQ, jump);
} else {
writer.ifCmp(promotedType.type, MethodWriter.NE, jump);
}
} else if (lt) {
writer.invokeDynamic("lt", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, 0);
writer.invokeDefCall("lt", descriptor, DefBootstrap.BINARY_OPERATOR, 0);
writejump = false;
} else if (lte) {
writer.invokeDynamic("lte", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, 0);
writer.invokeDefCall("lte", descriptor, DefBootstrap.BINARY_OPERATOR, 0);
writejump = false;
} else if (gt) {
writer.invokeDynamic("gt", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, 0);
writer.invokeDefCall("gt", descriptor, DefBootstrap.BINARY_OPERATOR, 0);
writejump = false;
} else if (gte) {
writer.invokeDynamic("gte", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, 0);
writer.invokeDefCall("gte", descriptor, DefBootstrap.BINARY_OPERATOR, 0);
writejump = false;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));

View File

@ -31,8 +31,6 @@ import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/**
* Represents a unary math expression.
*/
@ -41,6 +39,7 @@ public final class EUnary extends AExpression {
final Operation operation;
AExpression child;
Type promote;
boolean originallyExplicit = false; // record whether there was originally an explicit cast
public EUnary(Location location, Operation operation, AExpression child) {
super(location);
@ -51,6 +50,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) {
@ -203,10 +203,16 @@ public final class EUnary extends AExpression {
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.
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);
writer.invokeDynamic("not", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.UNARY_OPERATOR, 0);
writer.invokeDefCall("not", descriptor, DefBootstrap.UNARY_OPERATOR, defFlags);
} else {
if (sort == Sort.INT) {
writer.push(-1);
@ -221,14 +227,14 @@ public final class EUnary extends AExpression {
} else if (operation == Operation.SUB) {
if (sort == Sort.DEF) {
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
writer.invokeDynamic("neg", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.UNARY_OPERATOR, 0);
writer.invokeDefCall("neg", descriptor, DefBootstrap.UNARY_OPERATOR, defFlags);
} else {
writer.math(MethodWriter.NEG, actual.type);
}
} else if (operation == Operation.ADD) {
if (sort == Sort.DEF) {
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
writer.invokeDynamic("plus", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.UNARY_OPERATOR, 0);
writer.invokeDefCall("plus", descriptor, DefBootstrap.UNARY_OPERATOR, defFlags);
}
} else {
throw createError(new IllegalStateException("Illegal tree structure."));

View File

@ -27,8 +27,6 @@ import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Type;
import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/**
* Represents an array load/store or shortcut on a def type. (Internal only.)
*/
@ -62,15 +60,15 @@ final class LDefArray extends ALink implements IDefLink {
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type);
writer.invokeDynamic("arrayLoad", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ARRAY_LOAD);
Type methodType = Type.getMethodType(after.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);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
writer.invokeDynamic("arrayStore", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ARRAY_STORE);
Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
writer.invokeDefCall("arrayStore", methodType, DefBootstrap.ARRAY_STORE);
}
}

View File

@ -30,8 +30,6 @@ import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/**
* Represents a method call made on a def type. (Internal only.)
*/
@ -92,32 +90,30 @@ final class LDefCall extends ALink implements IDefLink {
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
StringBuilder signature = new StringBuilder();
List<Type> parameterTypes = new ArrayList<>();
signature.append('(');
// first parameter is the receiver, we never know its type: always Object
signature.append(Definition.DEF_TYPE.type.getDescriptor());
parameterTypes.add(Definition.DEF_TYPE.type);
// append each argument
for (AExpression argument : arguments) {
signature.append(argument.actual.type.getDescriptor());
parameterTypes.add(argument.actual.type);
if (argument instanceof ILambda) {
ILambda lambda = (ILambda) argument;
for (Type capture : lambda.getCaptures()) {
signature.append(capture.getDescriptor());
parameterTypes.add(capture);
}
}
argument.write(writer, globals);
}
signature.append(')');
// return value
signature.append(after.type.getDescriptor());
// create method type from return value and arguments
Type methodType = Type.getMethodType(after.type, parameterTypes.toArray(new Type[0]));
List<Object> args = new ArrayList<>();
args.add(DefBootstrap.METHOD_CALL);
args.add(recipe);
args.addAll(pointers);
writer.invokeDynamic(name, signature.toString(), DEF_BOOTSTRAP_HANDLE, args.toArray());
writer.invokeDefCall(name, methodType, DefBootstrap.METHOD_CALL, args.toArray());
}
@Override

View File

@ -27,8 +27,6 @@ import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Type;
import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/**
* Represents a field load/store or shortcut on a def type. (Internal only.)
*/
@ -59,15 +57,15 @@ final class LDefField extends ALink implements IDefLink {
void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type);
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.LOAD);
Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type);
writer.invokeDefCall(value, methodType, DefBootstrap.LOAD);
}
@Override
void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.STORE);
Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
writer.invokeDefCall(value, methodType, DefBootstrap.STORE);
}
}

View File

@ -35,7 +35,6 @@ import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
@ -193,8 +192,8 @@ public class SEach extends AStatement {
if (method == null) {
Type itr = Definition.getType("Iterator");
String desc = org.objectweb.asm.Type.getMethodDescriptor(itr.type, Definition.DEF_TYPE.type);
writer.invokeDynamic("iterator", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ITERATOR);
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type);
writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR);
} else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) {
writer.invokeInterface(method.owner.type, method.method);
} else {

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.Constant;
import org.elasticsearch.painless.Def;
import org.elasticsearch.painless.Definition;
@ -143,12 +144,12 @@ public class SFunction extends AStatement {
}
/** Writes the function to given ClassVisitor. */
void write (ClassVisitor writer, Globals globals) {
void write (ClassVisitor writer, CompilerSettings settings, Globals globals) {
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
if (synthetic) {
access |= Opcodes.ACC_SYNTHETIC;
}
final MethodWriter function = new MethodWriter(access, method.method, writer, globals.getStatements());
final MethodWriter function = new MethodWriter(access, method.method, writer, globals.getStatements(), settings);
write(function, globals);
function.endMethod();
}

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.Constant;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey;
@ -59,6 +60,7 @@ 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;
@ -67,10 +69,11 @@ public final class SSource extends AStatement {
private Locals mainMethod;
private byte[] bytes;
public SSource(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 = settings;
this.name = name;
this.source = source;
this.debugStream = debugStream;
@ -151,7 +154,7 @@ public final class SSource extends AStatement {
visitor.visitSource(Location.computeSourceName(name, source), null);
// Write the constructor:
MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements());
MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements(), settings);
constructor.visitCode();
constructor.loadThis();
constructor.loadArgs();
@ -160,14 +163,14 @@ public final class SSource extends AStatement {
constructor.endMethod();
// Write the execute method:
MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, globals.getStatements());
MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, globals.getStatements(), settings);
execute.visitCode();
write(execute, globals);
execute.endMethod();
// Write all functions:
for (SFunction function : functions) {
function.write(visitor, globals);
function.write(visitor, settings, globals);
}
// Write all synthetic functions. Note that this process may add more :)
@ -175,7 +178,7 @@ public final class SSource extends AStatement {
List<SFunction> current = new ArrayList<>(globals.getSyntheticMethods().values());
globals.getSyntheticMethods().clear();
for (SFunction function : current) {
function.write(visitor, globals);
function.write(visitor, settings, globals);
}
}
@ -195,7 +198,7 @@ public final class SSource extends AStatement {
// Initialize the constants in a static initializer
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
WriterConstants.CLINIT, visitor, globals.getStatements());
WriterConstants.CLINIT, visitor, globals.getStatements(), settings);
for (Constant constant : inits) {
constant.initializer.accept(clinit);
clinit.putStatic(CLASS_TYPE, constant.name, constant.type);

View File

@ -0,0 +1,298 @@
/*
* 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;
/** Tests for explicit casts */
public class CastTests extends ScriptTestCase {
/**
* Unary operator with explicit cast
*/
public void testUnaryOperator() {
assertEquals((byte)5, exec("long x = 5L; return (byte) (+x);"));
assertEquals((short)5, exec("long x = 5L; return (short) (+x);"));
assertEquals((char)5, exec("long x = 5L; return (char) (+x);"));
assertEquals(5, exec("long x = 5L; return (int) (+x);"));
assertEquals(5F, exec("long x = 5L; return (float) (+x);"));
assertEquals(5L, exec("long x = 5L; return (long) (+x);"));
assertEquals(5D, exec("long x = 5L; return (double) (+x);"));
}
/**
* Binary operators with explicit cast
*/
public void testBinaryOperator() {
assertEquals((byte)6, exec("long x = 5L; return (byte) (x + 1);"));
assertEquals((short)6, exec("long x = 5L; return (short) (x + 1);"));
assertEquals((char)6, exec("long x = 5L; return (char) (x + 1);"));
assertEquals(6, exec("long x = 5L; return (int) (x + 1);"));
assertEquals(6F, exec("long x = 5L; return (float) (x + 1);"));
assertEquals(6L, exec("long x = 5L; return (long) (x + 1);"));
assertEquals(6D, exec("long x = 5L; return (double) (x + 1);"));
}
/**
* Binary compound assignment with explicit cast
*/
public void testBinaryCompoundAssignment() {
assertEquals((byte)6, exec("long x = 5L; return (byte) (x += 1);"));
assertEquals((short)6, exec("long x = 5L; return (short) (x += 1);"));
assertEquals((char)6, exec("long x = 5L; return (char) (x += 1);"));
assertEquals(6, exec("long x = 5L; return (int) (x += 1);"));
assertEquals(6F, exec("long x = 5L; return (float) (x += 1);"));
assertEquals(6L, exec("long x = 5L; return (long) (x += 1);"));
assertEquals(6D, exec("long x = 5L; return (double) (x += 1);"));
}
/**
* Binary compound prefix with explicit cast
*/
public void testBinaryPrefix() {
assertEquals((byte)6, exec("long x = 5L; return (byte) (++x);"));
assertEquals((short)6, exec("long x = 5L; return (short) (++x);"));
assertEquals((char)6, exec("long x = 5L; return (char) (++x);"));
assertEquals(6, exec("long x = 5L; return (int) (++x);"));
assertEquals(6F, exec("long x = 5L; return (float) (++x);"));
assertEquals(6L, exec("long x = 5L; return (long) (++x);"));
assertEquals(6D, exec("long x = 5L; return (double) (++x);"));
}
/**
* Binary compound postifx with explicit cast
*/
public void testBinaryPostfix() {
assertEquals((byte)5, exec("long x = 5L; return (byte) (x++);"));
assertEquals((short)5, exec("long x = 5L; return (short) (x++);"));
assertEquals((char)5, exec("long x = 5L; return (char) (x++);"));
assertEquals(5, exec("long x = 5L; return (int) (x++);"));
assertEquals(5F, exec("long x = 5L; return (float) (x++);"));
assertEquals(5L, exec("long x = 5L; return (long) (x++);"));
assertEquals(5D, exec("long x = 5L; return (double) (x++);"));
}
/**
* Shift operators with explicit cast
*/
public void testShiftOperator() {
assertEquals((byte)10, exec("long x = 5L; return (byte) (x << 1);"));
assertEquals((short)10, exec("long x = 5L; return (short) (x << 1);"));
assertEquals((char)10, exec("long x = 5L; return (char) (x << 1);"));
assertEquals(10, exec("long x = 5L; return (int) (x << 1);"));
assertEquals(10F, exec("long x = 5L; return (float) (x << 1);"));
assertEquals(10L, exec("long x = 5L; return (long) (x << 1);"));
assertEquals(10D, exec("long x = 5L; return (double) (x << 1);"));
}
/**
* Shift compound assignment with explicit cast
*/
public void testShiftCompoundAssignment() {
assertEquals((byte)10, exec("long x = 5L; return (byte) (x <<= 1);"));
assertEquals((short)10, exec("long x = 5L; return (short) (x <<= 1);"));
assertEquals((char)10, exec("long x = 5L; return (char) (x <<= 1);"));
assertEquals(10, exec("long x = 5L; return (int) (x <<= 1);"));
assertEquals(10F, exec("long x = 5L; return (float) (x <<= 1);"));
assertEquals(10L, exec("long x = 5L; return (long) (x <<= 1);"));
assertEquals(10D, exec("long x = 5L; return (double) (x <<= 1);"));
}
/**
* Test that without a cast, we fail when conversions would narrow.
*/
public void testIllegalConversions() {
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; int y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; int y = (x + x); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("boolean x = true; int y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("boolean x = true; int y = (x ^ false); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; boolean y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; boolean y = (x + x); return y");
});
}
/**
* Test that even with a cast, some things aren't allowed.
*/
public void testIllegalExplicitConversions() {
expectScriptThrows(ClassCastException.class, () -> {
exec("boolean x = true; int y = (int) +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("boolean x = true; int y = (int) (x ^ false); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; boolean y = (boolean) +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; boolean y = (boolean) (x + x); return y");
});
}
/**
* Currently these do not adopt the return value, we issue a separate cast!
*/
public void testMethodCallDef() {
assertEquals(5, exec("def x = 5; return (int)x.longValue();"));
}
/**
* Unary operators adopt the return value
*/
public void testUnaryOperatorDef() {
assertEquals((byte)5, exec("def x = 5L; return (byte) (+x);"));
assertEquals((short)5, exec("def x = 5L; return (short) (+x);"));
assertEquals((char)5, exec("def x = 5L; return (char) (+x);"));
assertEquals(5, exec("def x = 5L; return (int) (+x);"));
assertEquals(5F, exec("def x = 5L; return (float) (+x);"));
assertEquals(5L, exec("def x = 5L; return (long) (+x);"));
assertEquals(5D, exec("def x = 5L; return (double) (+x);"));
}
/**
* Binary operators adopt the return value
*/
public void testBinaryOperatorDef() {
assertEquals((byte)6, exec("def x = 5L; return (byte) (x + 1);"));
assertEquals((short)6, exec("def x = 5L; return (short) (x + 1);"));
assertEquals((char)6, exec("def x = 5L; return (char) (x + 1);"));
assertEquals(6, exec("def x = 5L; return (int) (x + 1);"));
assertEquals(6F, exec("def x = 5L; return (float) (x + 1);"));
assertEquals(6L, exec("def x = 5L; return (long) (x + 1);"));
assertEquals(6D, exec("def x = 5L; return (double) (x + 1);"));
}
/**
* Binary operators don't yet adopt the return value with compound assignment
*/
public void testBinaryCompoundAssignmentDef() {
assertEquals((byte)6, exec("def x = 5L; return (byte) (x += 1);"));
assertEquals((short)6, exec("def x = 5L; return (short) (x += 1);"));
assertEquals((char)6, exec("def x = 5L; return (char) (x += 1);"));
assertEquals(6, exec("def x = 5L; return (int) (x += 1);"));
assertEquals(6F, exec("def x = 5L; return (float) (x += 1);"));
assertEquals(6L, exec("def x = 5L; return (long) (x += 1);"));
assertEquals(6D, exec("def x = 5L; return (double) (x += 1);"));
}
/**
* Binary operators don't yet adopt the return value with compound assignment
*/
public void testBinaryCompoundAssignmentPrefix() {
assertEquals((byte)6, exec("def x = 5L; return (byte) (++x);"));
assertEquals((short)6, exec("def x = 5L; return (short) (++x);"));
assertEquals((char)6, exec("def x = 5L; return (char) (++x);"));
assertEquals(6, exec("def x = 5L; return (int) (++x);"));
assertEquals(6F, exec("def x = 5L; return (float) (++x);"));
assertEquals(6L, exec("def x = 5L; return (long) (++x);"));
assertEquals(6D, exec("def x = 5L; return (double) (++x);"));
}
/**
* Binary operators don't yet adopt the return value with compound assignment
*/
public void testBinaryCompoundAssignmentPostfix() {
assertEquals((byte)5, exec("def x = 5L; return (byte) (x++);"));
assertEquals((short)5, exec("def x = 5L; return (short) (x++);"));
assertEquals((char)5, exec("def x = 5L; return (char) (x++);"));
assertEquals(5, exec("def x = 5L; return (int) (x++);"));
assertEquals(5F, exec("def x = 5L; return (float) (x++);"));
assertEquals(5L, exec("def x = 5L; return (long) (x++);"));
assertEquals(5D, exec("def x = 5L; return (double) (x++);"));
}
/**
* Shift operators adopt the return value
*/
public void testShiftOperatorDef() {
assertEquals((byte)10, exec("def x = 5L; return (byte) (x << 1);"));
assertEquals((short)10, exec("def x = 5L; return (short) (x << 1);"));
assertEquals((char)10, exec("def x = 5L; return (char) (x << 1);"));
assertEquals(10, exec("def x = 5L; return (int) (x << 1);"));
assertEquals(10F, exec("def x = 5L; return (float) (x << 1);"));
assertEquals(10L, exec("def x = 5L; return (long) (x << 1);"));
assertEquals(10D, exec("def x = 5L; return (double) (x << 1);"));
}
/**
* Shift operators don't yet adopt the return value with compound assignment
*/
public void testShiftCompoundAssignmentDef() {
assertEquals((byte)10, exec("def x = 5L; return (byte) (x <<= 1);"));
assertEquals((short)10, exec("def x = 5L; return (short) (x <<= 1);"));
assertEquals((char)10, exec("def x = 5L; return (char) (x <<= 1);"));
assertEquals(10, exec("def x = 5L; return (int) (x <<= 1);"));
assertEquals(10F, exec("def x = 5L; return (float) (x <<= 1);"));
assertEquals(10L, exec("def x = 5L; return (long) (x <<= 1);"));
assertEquals(10D, exec("def x = 5L; return (double) (x <<= 1);"));
}
/**
* Test that without a cast, we fail when conversions would narrow.
*/
public void testIllegalConversionsDef() {
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; int y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; int y = (x + x); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = true; int y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = true; int y = (x ^ false); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; boolean y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; boolean y = (x + x); return y");
});
}
/**
* Test that even with a cast, some things aren't allowed.
* (stuff that methodhandles explicitCastArguments would otherwise allow)
*/
public void testIllegalExplicitConversionsDef() {
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = true; int y = (int) +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = true; int y = (int) (x ^ false); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; boolean y = (boolean) +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; boolean y = (boolean) (x + x); return y");
});
}
}

View File

@ -36,6 +36,7 @@ public class DefBootstrapTests extends ESTestCase {
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"toString",
MethodType.methodType(String.class, Object.class),
0,
DefBootstrap.METHOD_CALL, 0L);
MethodHandle handle = site.dynamicInvoker();
assertDepthEquals(site, 0);
@ -53,6 +54,7 @@ public class DefBootstrapTests extends ESTestCase {
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"toString",
MethodType.methodType(String.class, Object.class),
0,
DefBootstrap.METHOD_CALL, 0L);
MethodHandle handle = site.dynamicInvoker();
assertDepthEquals(site, 0);
@ -75,6 +77,7 @@ public class DefBootstrapTests extends ESTestCase {
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"toString",
MethodType.methodType(String.class, Object.class),
0,
DefBootstrap.METHOD_CALL, 0L);
MethodHandle handle = site.dynamicInvoker();
assertDepthEquals(site, 0);
@ -98,6 +101,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"size",
MethodType.methodType(int.class, Object.class),
0,
DefBootstrap.METHOD_CALL, 0L);
site.depth = DefBootstrap.PIC.MAX_DEPTH; // mark megamorphic
MethodHandle handle = site.dynamicInvoker();
@ -127,6 +131,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"add",
MethodType.methodType(Object.class, Object.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
MethodHandle handle = site.dynamicInvoker();
assertEquals("nulltest", (Object)handle.invokeExact((Object)null, (Object)"test"));
@ -136,6 +141,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"add",
MethodType.methodType(Object.class, Object.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
MethodHandle handle = site.dynamicInvoker();
assertEquals(2, (Object)handle.invokeExact((Object)1, (Object)1));
@ -146,6 +152,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"eq",
MethodType.methodType(boolean.class, Object.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
MethodHandle handle = site.dynamicInvoker();
assertFalse((boolean) handle.invokeExact((Object)null, (Object)"test"));
@ -156,6 +163,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"eq",
MethodType.methodType(boolean.class, Object.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
MethodHandle handle = site.dynamicInvoker();
assertTrue((boolean) handle.invokeExact((Object)1, (Object)1));
@ -171,6 +179,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"add",
MethodType.methodType(Object.class, int.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, 0);
MethodHandle handle = site.dynamicInvoker();
expectThrows(NullPointerException.class, () -> {
@ -182,6 +191,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"add",
MethodType.methodType(Object.class, int.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, 0);
MethodHandle handle = site.dynamicInvoker();
assertEquals(2, (Object)handle.invokeExact(1, (Object)1));

View File

@ -243,18 +243,22 @@ public class DefOptimizationTests extends ScriptTestCase {
public void testAddOptNullGuards() {
// needs null guard
assertBytecodeHasPattern("def x = 1; def y = 2; return x + y",
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + DefBootstrap.BINARY_OPERATOR
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + "\\d+"
+ ",\\s+" + DefBootstrap.BINARY_OPERATOR
+ ",\\s+" + DefBootstrap.OPERATOR_ALLOWS_NULL + ".*");
// still needs null guard, NPE is the wrong thing!
assertBytecodeHasPattern("def x = 1; def y = 2; double z = x + y",
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + DefBootstrap.BINARY_OPERATOR
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + "\\d+"
+ ",\\s+" + DefBootstrap.BINARY_OPERATOR
+ ",\\s+" + DefBootstrap.OPERATOR_ALLOWS_NULL + ".*");
// a primitive argument is present: no null guard needed
assertBytecodeHasPattern("def x = 1; int y = 2; return x + y",
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + DefBootstrap.BINARY_OPERATOR
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + "\\d+"
+ ",\\s+" + DefBootstrap.BINARY_OPERATOR
+ ",\\s+" + 0 + ".*");
assertBytecodeHasPattern("int x = 1; def y = 2; return x + y",
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + DefBootstrap.BINARY_OPERATOR
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + "\\d+"
+ ",\\s+" + DefBootstrap.BINARY_OPERATOR
+ ",\\s+" + 0 + ".*");
}

View File

@ -56,6 +56,7 @@ public abstract class ScriptTestCase extends ESTestCase {
public Object exec(String script, Map<String, Object> vars) {
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);
}