Fix certain bad casts in Painless due to boxing/unboxing. (#23282)
This commit is contained in:
parent
7475175957
commit
fac2d954e3
File diff suppressed because it is too large
Load Diff
|
@ -87,6 +87,7 @@ public final class Definition {
|
|||
public static final Type CHAR_OBJ_TYPE = getType("Character");
|
||||
public static final Type OBJECT_TYPE = getType("Object");
|
||||
public static final Type DEF_TYPE = getType("def");
|
||||
public static final Type NUMBER_TYPE = getType("Number");
|
||||
public static final Type STRING_TYPE = getType("String");
|
||||
public static final Type EXCEPTION_TYPE = getType("Exception");
|
||||
public static final Type PATTERN_TYPE = getType("Pattern");
|
||||
|
@ -434,23 +435,23 @@ public final class Definition {
|
|||
public final Type from;
|
||||
public final Type to;
|
||||
public final boolean explicit;
|
||||
public final boolean unboxFrom;
|
||||
public final boolean unboxTo;
|
||||
public final boolean boxFrom;
|
||||
public final boolean boxTo;
|
||||
public final Type unboxFrom;
|
||||
public final Type unboxTo;
|
||||
public final Type boxFrom;
|
||||
public final Type boxTo;
|
||||
|
||||
public Cast(final Type from, final Type to, final boolean explicit) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.explicit = explicit;
|
||||
this.unboxFrom = false;
|
||||
this.unboxTo = false;
|
||||
this.boxFrom = false;
|
||||
this.boxTo = false;
|
||||
this.unboxFrom = null;
|
||||
this.unboxTo = null;
|
||||
this.boxFrom = null;
|
||||
this.boxTo = null;
|
||||
}
|
||||
|
||||
public Cast(final Type from, final Type to, final boolean explicit,
|
||||
final boolean unboxFrom, final boolean unboxTo, final boolean boxFrom, final boolean boxTo) {
|
||||
final Type unboxFrom, final Type unboxTo, final Type boxFrom, final Type boxTo) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.explicit = explicit;
|
||||
|
|
|
@ -131,51 +131,48 @@ public final class MethodWriter extends GeneratorAdapter {
|
|||
|
||||
public void writeCast(final Cast cast) {
|
||||
if (cast != null) {
|
||||
final Type from = cast.from;
|
||||
final Type to = cast.to;
|
||||
|
||||
if (from.sort == Sort.CHAR && to.sort == Sort.STRING) {
|
||||
if (cast.from.sort == Sort.CHAR && cast.to.sort == Sort.STRING) {
|
||||
invokeStatic(UTILITY_TYPE, CHAR_TO_STRING);
|
||||
} else if (from.sort == Sort.STRING && to.sort == Sort.CHAR) {
|
||||
} else if (cast.from.sort == Sort.STRING && cast.to.sort == Sort.CHAR) {
|
||||
invokeStatic(UTILITY_TYPE, STRING_TO_CHAR);
|
||||
} else if (cast.unboxFrom) {
|
||||
if (from.sort == Sort.DEF) {
|
||||
} else if (cast.unboxFrom != null) {
|
||||
unbox(cast.unboxFrom.type);
|
||||
writeCast(cast.from, cast.to);
|
||||
} else if (cast.unboxTo != null) {
|
||||
if (cast.from.sort == Sort.DEF) {
|
||||
if (cast.explicit) {
|
||||
if (to.sort == Sort.BOOL) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
|
||||
else if (to.sort == Sort.BYTE) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_EXPLICIT);
|
||||
else if (to.sort == Sort.SHORT) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_EXPLICIT);
|
||||
else if (to.sort == Sort.CHAR) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_EXPLICIT);
|
||||
else if (to.sort == Sort.INT) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_EXPLICIT);
|
||||
else if (to.sort == Sort.LONG) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_EXPLICIT);
|
||||
else if (to.sort == Sort.FLOAT) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_EXPLICIT);
|
||||
else if (to.sort == Sort.DOUBLE) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_EXPLICIT);
|
||||
if (cast.to.sort == Sort.BOOL_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
|
||||
else if (cast.to.sort == Sort.BYTE_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_EXPLICIT);
|
||||
else if (cast.to.sort == Sort.SHORT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_EXPLICIT);
|
||||
else if (cast.to.sort == Sort.CHAR_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_EXPLICIT);
|
||||
else if (cast.to.sort == Sort.INT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_EXPLICIT);
|
||||
else if (cast.to.sort == Sort.LONG_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_EXPLICIT);
|
||||
else if (cast.to.sort == Sort.FLOAT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_EXPLICIT);
|
||||
else if (cast.to.sort == Sort.DOUBLE_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_EXPLICIT);
|
||||
else throw new IllegalStateException("Illegal tree structure.");
|
||||
} else {
|
||||
if (to.sort == Sort.BOOL) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
|
||||
else if (to.sort == Sort.BYTE) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_IMPLICIT);
|
||||
else if (to.sort == Sort.SHORT) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_IMPLICIT);
|
||||
else if (to.sort == Sort.CHAR) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_IMPLICIT);
|
||||
else if (to.sort == Sort.INT) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_IMPLICIT);
|
||||
else if (to.sort == Sort.LONG) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_IMPLICIT);
|
||||
else if (to.sort == Sort.FLOAT) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_IMPLICIT);
|
||||
else if (to.sort == Sort.DOUBLE) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_IMPLICIT);
|
||||
if (cast.to.sort == Sort.BOOL_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
|
||||
else if (cast.to.sort == Sort.BYTE_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_IMPLICIT);
|
||||
else if (cast.to.sort == Sort.SHORT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_IMPLICIT);
|
||||
else if (cast.to.sort == Sort.CHAR_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_IMPLICIT);
|
||||
else if (cast.to.sort == Sort.INT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_IMPLICIT);
|
||||
else if (cast.to.sort == Sort.LONG_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_IMPLICIT);
|
||||
else if (cast.to.sort == Sort.FLOAT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_IMPLICIT);
|
||||
else if (cast.to.sort == Sort.DOUBLE_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_IMPLICIT);
|
||||
else throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
} else {
|
||||
unbox(from.type);
|
||||
writeCast(from, to);
|
||||
writeCast(cast.from, cast.to);
|
||||
unbox(cast.unboxTo.type);
|
||||
}
|
||||
} else if (cast.unboxTo) {
|
||||
writeCast(from, to);
|
||||
unbox(to.type);
|
||||
} else if (cast.boxFrom) {
|
||||
box(from.type);
|
||||
writeCast(from, to);
|
||||
} else if (cast.boxTo) {
|
||||
writeCast(from, to);
|
||||
box(to.type);
|
||||
} else if (cast.boxFrom != null) {
|
||||
box(cast.boxFrom.type);
|
||||
writeCast(cast.from, cast.to);
|
||||
} else if (cast.boxTo != null) {
|
||||
writeCast(cast.from, cast.to);
|
||||
box(cast.boxTo.type);
|
||||
} else {
|
||||
writeCast(from, to);
|
||||
writeCast(cast.from, cast.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,19 +266,19 @@ 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,
|
||||
public void writeDynamicBinaryInstruction(Location location, Type returnType, Type lhs, Type rhs,
|
||||
Operation operation, int flags) {
|
||||
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(returnType.type, lhs.type, rhs.type);
|
||||
|
||||
|
||||
switch (operation) {
|
||||
case MUL:
|
||||
invokeDefCall("mul", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("mul", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case DIV:
|
||||
invokeDefCall("div", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("div", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case REM:
|
||||
invokeDefCall("rem", methodType, 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,
|
||||
|
@ -294,31 +291,31 @@ public final class MethodWriter extends GeneratorAdapter {
|
|||
invokeDefCall("add", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case SUB:
|
||||
invokeDefCall("sub", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("sub", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case LSH:
|
||||
invokeDefCall("lsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
break;
|
||||
case USH:
|
||||
invokeDefCall("ush", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
invokeDefCall("ush", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
break;
|
||||
case RSH:
|
||||
invokeDefCall("rsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
invokeDefCall("rsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
break;
|
||||
case BWAND:
|
||||
case BWAND:
|
||||
invokeDefCall("and", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case XOR:
|
||||
case XOR:
|
||||
invokeDefCall("xor", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case BWOR:
|
||||
case BWOR:
|
||||
invokeDefCall("or", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
default:
|
||||
throw location.createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Writes a static binary instruction */
|
||||
public void writeBinaryInstruction(Location location, Type type, Operation operation) {
|
||||
final Sort sort = type.sort;
|
||||
|
|
|
@ -21,7 +21,7 @@ package org.elasticsearch.painless;
|
|||
|
||||
/** Tests for explicit casts */
|
||||
public class CastTests extends ScriptTestCase {
|
||||
|
||||
|
||||
/**
|
||||
* Unary operator with explicit cast
|
||||
*/
|
||||
|
@ -34,7 +34,7 @@ public class CastTests extends ScriptTestCase {
|
|||
assertEquals(5L, exec("long x = 5L; return (long) (+x);"));
|
||||
assertEquals(5D, exec("long x = 5L; return (double) (+x);"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Binary operators with explicit cast
|
||||
*/
|
||||
|
@ -73,7 +73,7 @@ public class CastTests extends ScriptTestCase {
|
|||
assertEquals(6L, exec("long x = 5L; return (long) (++x);"));
|
||||
assertEquals(6D, exec("long x = 5L; return (double) (++x);"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Binary compound postifx with explicit cast
|
||||
*/
|
||||
|
@ -86,7 +86,7 @@ public class CastTests extends ScriptTestCase {
|
|||
assertEquals(5L, exec("long x = 5L; return (long) (x++);"));
|
||||
assertEquals(5D, exec("long x = 5L; return (double) (x++);"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shift operators with explicit cast
|
||||
*/
|
||||
|
@ -99,7 +99,7 @@ public class CastTests extends ScriptTestCase {
|
|||
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
|
||||
*/
|
||||
|
@ -112,7 +112,7 @@ public class CastTests extends ScriptTestCase {
|
|||
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.
|
||||
*/
|
||||
|
@ -136,7 +136,7 @@ public class CastTests extends ScriptTestCase {
|
|||
exec("long x = 5L; boolean y = (x + x); return y");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that even with a cast, some things aren't allowed.
|
||||
*/
|
||||
|
@ -161,7 +161,7 @@ public class CastTests extends ScriptTestCase {
|
|||
public void testMethodCallDef() {
|
||||
assertEquals(5, exec("def x = 5; return (int)x.longValue();"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Currently these do not adopt the argument value, we issue a separate cast!
|
||||
*/
|
||||
|
@ -170,7 +170,7 @@ public class CastTests extends ScriptTestCase {
|
|||
assertEquals(6, exec("def x = 5; def y = 1L; return x + (int)y"));
|
||||
assertEquals('b', exec("def x = 'abcdeg'; def y = 1L; x.charAt((int)y)"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unary operators adopt the return value
|
||||
*/
|
||||
|
@ -183,7 +183,7 @@ public class CastTests extends ScriptTestCase {
|
|||
assertEquals(5L, exec("def x = 5L; return (long) (+x);"));
|
||||
assertEquals(5D, exec("def x = 5L; return (double) (+x);"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Binary operators adopt the return value
|
||||
*/
|
||||
|
@ -196,7 +196,7 @@ public class CastTests extends ScriptTestCase {
|
|||
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
|
||||
*/
|
||||
|
@ -209,7 +209,7 @@ public class CastTests extends ScriptTestCase {
|
|||
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
|
||||
*/
|
||||
|
@ -222,7 +222,7 @@ public class CastTests extends ScriptTestCase {
|
|||
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
|
||||
*/
|
||||
|
@ -235,7 +235,7 @@ public class CastTests extends ScriptTestCase {
|
|||
assertEquals(5L, exec("def x = 5L; return (long) (x++);"));
|
||||
assertEquals(5D, exec("def x = 5L; return (double) (x++);"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shift operators adopt the return value
|
||||
*/
|
||||
|
@ -248,7 +248,7 @@ public class CastTests extends ScriptTestCase {
|
|||
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
|
||||
*/
|
||||
|
@ -261,7 +261,7 @@ public class CastTests extends ScriptTestCase {
|
|||
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.
|
||||
*/
|
||||
|
@ -285,7 +285,21 @@ public class CastTests extends ScriptTestCase {
|
|||
exec("def x = 5L; boolean y = (x + x); return y");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void testUnboxMethodParameters() {
|
||||
assertEquals('a', exec("'a'.charAt(Integer.valueOf(0))"));
|
||||
}
|
||||
|
||||
public void testIllegalCastInMethodArgument() {
|
||||
assertEquals('a', exec("'a'.charAt(0)"));
|
||||
Exception e = expectScriptThrows(ClassCastException.class, () -> exec("'a'.charAt(0L)"));
|
||||
assertEquals("Cannot cast from [long] to [int].", e.getMessage());
|
||||
e = expectScriptThrows(ClassCastException.class, () -> exec("'a'.charAt(0.0f)"));
|
||||
assertEquals("Cannot cast from [float] to [int].", e.getMessage());
|
||||
e = expectScriptThrows(ClassCastException.class, () -> exec("'a'.charAt(0.0d)"));
|
||||
assertEquals("Cannot cast from [double] to [int].", e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that even with a cast, some things aren't allowed.
|
||||
* (stuff that methodhandles explicitCastArguments would otherwise allow)
|
||||
|
|
|
@ -54,6 +54,18 @@ public class FunctionTests extends ScriptTestCase {
|
|||
assertThat(expected.getMessage(), containsString("Cannot generate an empty function"));
|
||||
}
|
||||
|
||||
public void testReturnsAreUnboxedIfNeeded() {
|
||||
assertEquals((byte) 5, exec("byte get() {Byte.valueOf(5)} get()"));
|
||||
assertEquals((short) 5, exec("short get() {Byte.valueOf(5)} get()"));
|
||||
assertEquals(5, exec("int get() {Byte.valueOf(5)} get()"));
|
||||
assertEquals((short) 5, exec("short get() {Short.valueOf(5)} get()"));
|
||||
assertEquals(5, exec("int get() {Integer.valueOf(5)} get()"));
|
||||
assertEquals(5.0f, exec("float get() {Float.valueOf(5)} get()"));
|
||||
assertEquals(5.0d, exec("double get() {Float.valueOf(5)} get()"));
|
||||
assertEquals(5.0d, exec("double get() {Double.valueOf(5)} get()"));
|
||||
assertEquals(true, exec("boolean get() {Boolean.TRUE} get()"));
|
||||
}
|
||||
|
||||
public void testDuplicates() {
|
||||
Exception expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("void test(int x) {x = 2;} void test(def y) {y = 3;} test()");
|
||||
|
@ -61,6 +73,15 @@ public class FunctionTests extends ScriptTestCase {
|
|||
assertThat(expected.getMessage(), containsString("Duplicate functions"));
|
||||
}
|
||||
|
||||
public void testBadCastFromMethod() {
|
||||
Exception e = expectScriptThrows(ClassCastException.class, () -> exec("int get() {5L} get()"));
|
||||
assertEquals("Cannot cast from [long] to [int].", e.getMessage());
|
||||
e = expectScriptThrows(ClassCastException.class, () -> exec("int get() {5.1f} get()"));
|
||||
assertEquals("Cannot cast from [float] to [int].", e.getMessage());
|
||||
e = expectScriptThrows(ClassCastException.class, () -> exec("int get() {5.1d} get()"));
|
||||
assertEquals("Cannot cast from [double] to [int].", e.getMessage());
|
||||
}
|
||||
|
||||
public void testInfiniteLoop() {
|
||||
Error expected = expectScriptThrows(PainlessError.class, () -> {
|
||||
exec("void test() {boolean x = true; while (x) {}} test()");
|
||||
|
|
Loading…
Reference in New Issue