--- a/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Wed Sep 10 16:14:14 2014 +0100
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Wed Sep 10 19:19:46 2014 +0400
@@ -25,21 +25,20 @@
package java.lang.invoke;
-import sun.invoke.util.VerifyAccess;
-import static java.lang.invoke.LambdaForm.*;
-
-import sun.invoke.util.Wrapper;
-
import java.io.*;
import java.util.*;
+import java.lang.reflect.Modifier;
import jdk.internal.org.objectweb.asm.*;
-import java.lang.reflect.*;
+import static java.lang.invoke.LambdaForm.*;
+import static java.lang.invoke.LambdaForm.BasicType.*;
import static java.lang.invoke.MethodHandleStatics.*;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
-import static java.lang.invoke.LambdaForm.BasicType.*;
+import sun.invoke.util.ValueConversions;
+import sun.invoke.util.VerifyAccess;
import sun.invoke.util.VerifyType;
+import sun.invoke.util.Wrapper;
import sun.reflect.misc.ReflectUtil;
/**
@@ -74,7 +73,11 @@
private final LambdaForm lambdaForm;
private final String invokerName;
private final MethodType invokerType;
- private final int[] localsMap;
+
+ /** Info about local variables in compiled lambda form */
+ private final int[] localsMap; // index
+ private final BasicType[] localTypes; // basic type
+ private final Class<?>[] localClasses; // type
/** ASM bytecode generation. */
private ClassWriter cw;
@@ -83,6 +86,7 @@
private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory();
private static final Class<?> HOST_CLASS = LambdaForm.class;
+ /** Main constructor; other constructors delegate to this one. */
private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize,
String className, String invokerName, MethodType invokerType) {
if (invokerName.contains(".")) {
@@ -98,18 +102,26 @@
this.lambdaForm = lambdaForm;
this.invokerName = invokerName;
this.invokerType = invokerType;
- this.localsMap = new int[localsMapSize];
+ this.localsMap = new int[localsMapSize+1];
+ // last entry of localsMap is count of allocated local slots
+ this.localTypes = new BasicType[localsMapSize+1];
+ this.localClasses = new Class<?>[localsMapSize+1];
}
+ /** For generating LambdaForm interpreter entry points. */
private InvokerBytecodeGenerator(String className, String invokerName, MethodType invokerType) {
this(null, invokerType.parameterCount(),
className, invokerName, invokerType);
// Create an array to map name indexes to locals indexes.
+ localTypes[localTypes.length - 1] = V_TYPE;
for (int i = 0; i < localsMap.length; i++) {
localsMap[i] = invokerType.parameterSlotCount() - invokerType.parameterSlotDepth(i);
+ if (i < invokerType.parameterCount())
+ localTypes[i] = basicType(invokerType.parameterType(i));
}
}
+ /** For generating customized code for a single LambdaForm. */
private InvokerBytecodeGenerator(String className, LambdaForm form, MethodType invokerType) {
this(form, form.names.length,
className, form.debugName, invokerType);
@@ -117,7 +129,11 @@
Name[] names = form.names;
for (int i = 0, index = 0; i < localsMap.length; i++) {
localsMap[i] = index;
- index += names[i].type.basicTypeSlots();
+ if (i < names.length) {
+ BasicType type = names[i].type();
+ index += type.basicTypeSlots();
+ localTypes[i] = type;
+ }
}
}
@@ -148,7 +164,6 @@
static void maybeDump(final String className, final byte[] classFile) {
if (DUMP_CLASS_FILES) {
- System.out.println("dump: " + className);
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
@@ -156,6 +171,7 @@
String dumpName = className;
//dumpName = dumpName.replace('/', '-');
File dumpFile = new File(DUMP_CLASS_FILES_DIR, dumpName+".class");
+ System.out.println("dump: " + dumpFile);
dumpFile.getParentFile().mkdirs();
FileOutputStream file = new FileOutputStream(dumpFile);
file.write(classFile);
@@ -204,7 +220,7 @@
String constantPlaceholder(Object arg) {
String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++;
- if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + arg.toString() + ">>"; // debugging aid
+ if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + debugString(arg) + ">>"; // debugging aid
if (cpPatches.containsKey(cpPlaceholder)) {
throw new InternalError("observed CP placeholder twice: " + cpPlaceholder);
}
@@ -225,6 +241,17 @@
return res;
}
+ private static String debugString(Object arg) {
+ if (arg instanceof MethodHandle) {
+ MethodHandle mh = (MethodHandle) arg;
+ MemberName member = mh.internalMemberName();
+ if (member != null)
+ return member.toString();
+ return mh.debugString();
+ }
+ return arg.toString();
+ }
+
/**
* Extract the number of constant pool entries from a given class file.
*
@@ -400,6 +427,30 @@
emitStoreInsn(L_TYPE, index);
}
+ private void freeFrameLocal(int oldFrameLocal) {
+ int i = indexForFrameLocal(oldFrameLocal);
+ if (i < 0) return;
+ BasicType type = localTypes[i];
+ int newFrameLocal = makeLocalTemp(type);
+ mv.visitVarInsn(loadInsnOpcode(type), oldFrameLocal);
+ mv.visitVarInsn(storeInsnOpcode(type), newFrameLocal);
+ assert(localsMap[i] == oldFrameLocal);
+ localsMap[i] = newFrameLocal;
+ assert(indexForFrameLocal(oldFrameLocal) < 0);
+ }
+ private int indexForFrameLocal(int frameLocal) {
+ for (int i = 0; i < localsMap.length; i++) {
+ if (localsMap[i] == frameLocal && localTypes[i] != V_TYPE)
+ return i;
+ }
+ return -1;
+ }
+ private int makeLocalTemp(BasicType type) {
+ int frameLocal = localsMap[localsMap.length - 1];
+ localsMap[localsMap.length - 1] = frameLocal + type.basicTypeSlots();
+ return frameLocal;
+ }
+
/**
* Emit a boxing call.
*
@@ -421,41 +472,79 @@
String owner = "java/lang/" + wrapper.wrapperType().getSimpleName();
String name = wrapper.primitiveSimpleName() + "Value";
String desc = "()" + wrapper.basicTypeChar();
- mv.visitTypeInsn(Opcodes.CHECKCAST, owner);
+ emitReferenceCast(wrapper.wrapperType(), null);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false);
}
/**
- * Emit an implicit conversion.
+ * Emit an implicit conversion for an argument which must be of the given pclass.
+ * This is usually a no-op, except when pclass is a subword type or a reference other than Object or an interface.
*
* @param ptype type of value present on stack
* @param pclass type of value required on stack
+ * @param arg compile-time representation of value on stack (Node, constant) or null if none
*/
- private void emitImplicitConversion(BasicType ptype, Class<?> pclass) {
+ private void emitImplicitConversion(BasicType ptype, Class<?> pclass, Object arg) {
assert(basicType(pclass) == ptype); // boxing/unboxing handled by caller
if (pclass == ptype.basicTypeClass() && ptype != L_TYPE)
return; // nothing to do
switch (ptype) {
- case L_TYPE:
- if (VerifyType.isNullConversion(Object.class, pclass))
+ case L_TYPE:
+ if (VerifyType.isNullConversion(Object.class, pclass, false)) {
+ if (PROFILE_LEVEL > 0)
+ emitReferenceCast(Object.class, arg);
+ return;
+ }
+ emitReferenceCast(pclass, arg);
+ return;
+ case I_TYPE:
+ if (!VerifyType.isNullConversion(int.class, pclass, false))
+ emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass));
return;
- if (isStaticallyNameable(pclass)) {
- mv.visitTypeInsn(Opcodes.CHECKCAST, getInternalName(pclass));
- } else {
- mv.visitLdcInsn(constantPlaceholder(pclass));
- mv.visitTypeInsn(Opcodes.CHECKCAST, CLS);
- mv.visitInsn(Opcodes.SWAP);
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, MHI, "castReference", CLL_SIG, false);
- if (pclass.isArray())
- mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY);
+ }
+ throw newInternalError("bad implicit conversion: tc="+ptype+": "+pclass);
+ }
+
+ /** Update localClasses type map. Return true if the information is already present. */
+ private boolean assertStaticType(Class<?> cls, Name n) {
+ int local = n.index();
+ Class<?> aclass = localClasses[local];
+ if (aclass != null && (aclass == cls || cls.isAssignableFrom(aclass))) {
+ return true; // type info is already present
+ } else if (aclass == null || aclass.isAssignableFrom(cls)) {
+ localClasses[local] = cls; // type info can be improved
+ }
+ return false;
+ }
+
+ private void emitReferenceCast(Class<?> cls, Object arg) {
+ Name writeBack = null; // local to write back result
+ if (arg instanceof Name) {
+ Name n = (Name) arg;
+ if (assertStaticType(cls, n))
+ return; // this cast was already performed
+ if (lambdaForm.useCount(n) > 1) {
+ // This guy gets used more than once.
+ writeBack = n;
}
- return;
- case I_TYPE:
- if (!VerifyType.isNullConversion(int.class, pclass))
- emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass));
- return;
}
- throw new InternalError("bad implicit conversion: tc="+ptype+": "+pclass);
+ if (isStaticallyNameable(cls)) {
+ String sig = getInternalName(cls);
+ mv.visitTypeInsn(Opcodes.CHECKCAST, sig);
+ } else {
+ mv.visitLdcInsn(constantPlaceholder(cls));
+ mv.visitTypeInsn(Opcodes.CHECKCAST, CLS);
+ mv.visitInsn(Opcodes.SWAP);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, MHI, "castReference", CLL_SIG, false);
+ if (Object[].class.isAssignableFrom(cls))
+ mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY);
+ else if (PROFILE_LEVEL > 0)
+ mv.visitTypeInsn(Opcodes.CHECKCAST, OBJ);
+ }
+ if (writeBack != null) {
+ mv.visitInsn(Opcodes.DUP);
+ emitAstoreInsn(writeBack.index());
+ }
}
/**
@@ -477,7 +566,11 @@
}
private static String getInternalName(Class<?> c) {
- assert(VerifyAccess.isTypeVisible(c, Object.class));
+ if (c == Object.class) return OBJ;
+ else if (c == Object[].class) return OBJARY;
+ else if (c == Class.class) return CLS;
+ else if (c == MethodHandle.class) return MH;
+ assert(VerifyAccess.isTypeVisible(c, Object.class)) : c.getName();
return c.getName().replace('.', '/');
}
@@ -506,39 +599,32 @@
// iterate over the form's names, generating bytecode instructions for each
// start iterating at the first name following the arguments
+ Name onStack = null;
for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) {
Name name = lambdaForm.names[i];
MemberName member = name.function.member();
+ Class<?> rtype = name.function.methodType().returnType();
+
+ emitStoreResult(onStack);
+ onStack = name; // unless otherwise modified below
if (isSelectAlternative(i)) {
- emitSelectAlternative(name, lambdaForm.names[i + 1]);
+ onStack = emitSelectAlternative(name, lambdaForm.names[i + 1]);
i++; // skip MH.invokeBasic of the selectAlternative result
} else if (isGuardWithCatch(i)) {
- emitGuardWithCatch(i);
+ onStack = emitGuardWithCatch(i);
i = i+2; // Jump to the end of GWC idiom
+ } else if (isNewArray(rtype, name)) {
+ emitNewArray(rtype, name);
} else if (isStaticallyInvocable(member)) {
- emitStaticInvoke(member, name);
+ emitStaticInvoke(name);
} else {
emitInvoke(name);
}
-
- // Update cached form name's info in case an intrinsic spanning multiple names was encountered.
- name = lambdaForm.names[i];
- member = name.function.member();
-
- // store the result from evaluating to the target name in a local if required
- // (if this is the last value, i.e., the one that is going to be returned,
- // avoid store/load/return and just return)
- if (i == lambdaForm.names.length - 1 && i == lambdaForm.result) {
- // return value - do nothing
- } else if (name.type != V_TYPE) {
- // non-void: actually assign
- emitStoreInsn(name.type, name.index());
- }
}
// return statement
- emitReturn();
+ emitReturn(onStack);
classFileEpilogue();
bogusMethod(lambdaForm);
@@ -552,25 +638,24 @@
* Emit an invoke for the given name.
*/
void emitInvoke(Name name) {
+ assert(!isLinkerMethodInvoke(name)); // should use the static path for these
if (true) {
// push receiver
MethodHandle target = name.function.resolvedHandle;
assert(target != null) : name.exprString();
mv.visitLdcInsn(constantPlaceholder(target));
- mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
+ emitReferenceCast(MethodHandle.class, target);
} else {
// load receiver
emitAloadInsn(0);
- mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
+ emitReferenceCast(MethodHandle.class, null);
mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG);
mv.visitFieldInsn(Opcodes.GETFIELD, LF, "names", LFN_SIG);
// TODO more to come
}
// push arguments
- for (int i = 0; i < name.arguments.length; i++) {
- emitPushArgument(name, i);
- }
+ emitPushArguments(name);
// invocation
MethodType type = name.function.methodType();
@@ -585,6 +670,10 @@
//MethodHandle.class already covered
};
+ static boolean isStaticallyInvocable(Name name) {
+ return isStaticallyInvocable(name.function.member());
+ }
+
static boolean isStaticallyInvocable(MemberName member) {
if (member == null) return false;
if (member.isConstructor()) return false;
@@ -611,6 +700,8 @@
}
static boolean isStaticallyNameable(Class<?> cls) {
+ if (cls == Object.class)
+ return true;
while (cls.isArray())
cls = cls.getComponentType();
if (cls.isPrimitive())
@@ -631,12 +722,17 @@
return false;
}
+ void emitStaticInvoke(Name name) {
+ emitStaticInvoke(name.function.member(), name);
+ }
+
/**
* Emit an invoke for the given name, using the MemberName directly.
*/
void emitStaticInvoke(MemberName member, Name name) {
assert(member.equals(name.function.member()));
- String cname = getInternalName(member.getDeclaringClass());
+ Class<?> defc = member.getDeclaringClass();
+ String cname = getInternalName(defc);
String mname = member.getName();
String mtype;
byte refKind = member.getReferenceKind();
@@ -653,9 +749,7 @@
}
// push arguments
- for (int i = 0; i < name.arguments.length; i++) {
- emitPushArgument(name, i);
- }
+ emitPushArguments(name);
// invocation
if (member.isMethod()) {
@@ -666,6 +760,54 @@
mtype = MethodType.toFieldDescriptorString(member.getFieldType());
mv.visitFieldInsn(refKindOpcode(refKind), cname, mname, mtype);
}
+ // Issue a type assertion for the result, so we can avoid casts later.
+ if (name.type == L_TYPE) {
+ Class<?> rtype = member.getInvocationType().returnType();
+ assert(!rtype.isPrimitive());
+ if (rtype != Object.class && !rtype.isInterface()) {
+ assertStaticType(rtype, name);
+ }
+ }
+ }
+
+ boolean isNewArray(Class<?> rtype, Name name) {
+ return rtype.isArray() &&
+ isStaticallyNameable(rtype) &&
+ isArrayBuilder(name.function.resolvedHandle) &&
+ name.arguments.length > 0;
+ }
+
+ void emitNewArray(Class<?> rtype, Name name) throws InternalError {
+ Class<?> arrayElementType = rtype.getComponentType();
+ emitIconstInsn(name.arguments.length);
+ int xas;
+ if (!arrayElementType.isPrimitive()) {
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, getInternalName(arrayElementType));
+ xas = Opcodes.AASTORE;
+ } else {
+ int tc;
+ switch (Wrapper.forPrimitiveType(arrayElementType)) {
+ case BOOLEAN: tc = Opcodes.T_BOOLEAN; xas = Opcodes.BASTORE; break;
+ case BYTE: tc = Opcodes.T_BYTE; xas = Opcodes.BASTORE; break;
+ case CHAR: tc = Opcodes.T_CHAR; xas = Opcodes.CASTORE; break;
+ case SHORT: tc = Opcodes.T_SHORT; xas = Opcodes.SASTORE; break;
+ case INT: tc = Opcodes.T_INT; xas = Opcodes.IASTORE; break;
+ case LONG: tc = Opcodes.T_LONG; xas = Opcodes.LASTORE; break;
+ case FLOAT: tc = Opcodes.T_FLOAT; xas = Opcodes.FASTORE; break;
+ case DOUBLE: tc = Opcodes.T_DOUBLE; xas = Opcodes.DASTORE; break;
+ default: throw new InternalError(rtype.getName());
+ }
+ mv.visitIntInsn(Opcodes.NEWARRAY, tc);
+ }
+ // store arguments
+ for (int i = 0; i < name.arguments.length; i++) {
+ mv.visitInsn(Opcodes.DUP);
+ emitIconstInsn(i);
+ emitPushArgument(name, i);
+ mv.visitInsn(xas);
+ }
+ // the array is left on the stack
+ assertStaticType(rtype, name);
}
int refKindOpcode(byte refKind) {
switch (refKind) {
@@ -681,6 +823,24 @@
throw new InternalError("refKind="+refKind);
}
+ static boolean isArrayBuilder(MethodHandle fn) {
+ if (fn == null)
+ return false;
+ MethodType mtype = fn.type();
+ Class<?> rtype = mtype.returnType();
+ Class<?> arrayElementType = rtype.getComponentType();
+ if (arrayElementType == null)
+ return false;
+ List<Class<?>> ptypes = mtype.parameterList();
+ int size = ptypes.size();
+ if (!ptypes.equals(Collections.nCopies(size, arrayElementType)))
+ return false;
+ // Assume varargsArray caches pointers.
+ if (fn != ValueConversions.varargsArray(rtype, size))
+ return false;
+ return true;
+ }
+
/**
* Check if MemberName is a call to a method named {@code name} in class {@code declaredClass}.
*/
@@ -708,6 +868,21 @@
}
/**
+ * Check if MemberName is a call to MethodHandle.linkToStatic, etc.
+ */
+ private boolean isLinkerMethodInvoke(Name name) {
+ if (name.function == null)
+ return false;
+ if (name.arguments.length < 1)
+ return false; // must have MH argument
+ MemberName member = name.function.member();
+ return member != null &&
+ member.getDeclaringClass() == MethodHandle.class &&
+ !member.isPublic() && member.isStatic() &&
+ member.getName().startsWith("linkTo");
+ }
+
+ /**
* Check if i-th name is a call to MethodHandleImpl.selectAlternative.
*/
private boolean isSelectAlternative(int pos) {
@@ -755,7 +930,9 @@
* t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I}
* }</pre></blockquote>
*/
- private void emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) {
+ private Name emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) {
+ assert isStaticallyInvocable(invokeBasicName);
+
Name receiver = (Name) invokeBasicName.arguments[0];
Label L_fallback = new Label();
@@ -763,15 +940,15 @@
// load test result
emitPushArgument(selectAlternativeName, 0);
- mv.visitInsn(Opcodes.ICONST_1);
// if_icmpne L_fallback
- mv.visitJumpInsn(Opcodes.IF_ICMPNE, L_fallback);
+ mv.visitJumpInsn(Opcodes.IFEQ, L_fallback);
// invoke selectAlternativeName.arguments[1]
+ Class<?>[] preForkClasses = localClasses.clone();
emitPushArgument(selectAlternativeName, 1); // get 2nd argument of selectAlternative
emitAstoreInsn(receiver.index()); // store the MH in the receiver slot
- emitInvoke(invokeBasicName);
+ emitStaticInvoke(invokeBasicName);
// goto L_done
mv.visitJumpInsn(Opcodes.GOTO, L_done);
@@ -780,12 +957,17 @@
mv.visitLabel(L_fallback);
// invoke selectAlternativeName.arguments[2]
+ System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length);
emitPushArgument(selectAlternativeName, 2); // get 3rd argument of selectAlternative
emitAstoreInsn(receiver.index()); // store the MH in the receiver slot
- emitInvoke(invokeBasicName);
+ emitStaticInvoke(invokeBasicName);
// L_done:
mv.visitLabel(L_done);
+ // for now do not bother to merge typestate; just reset to the dominator state
+ System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length);
+
+ return invokeBasicName; // return what's on stack
}
/**
@@ -808,7 +990,7 @@
* return a3.invokeBasic(ex, a6, a7);
* }}
*/
- private void emitGuardWithCatch(int pos) {
+ private Name emitGuardWithCatch(int pos) {
Name args = lambdaForm.names[pos];
Name invoker = lambdaForm.names[pos+1];
Name result = lambdaForm.names[pos+2];
@@ -859,6 +1041,12 @@
mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(L_done);
+
+ return result;
+ }
+
+ private void emitPushArguments(Name args) {
+ emitPushArguments(args, 0);
}
private void emitPushArguments(Name args, int start) {
@@ -878,7 +1066,7 @@
if (arg instanceof Name) {
Name n = (Name) arg;
emitLoadInsn(n.type, n.index());
- emitImplicitConversion(n.type, ptype);
+ emitImplicitConversion(n.type, ptype, n);
} else if ((arg == null || arg instanceof String) && bptype == L_TYPE) {
emitConst(arg);
} else {
@@ -886,15 +1074,25 @@
emitConst(arg);
} else {
mv.visitLdcInsn(constantPlaceholder(arg));
- emitImplicitConversion(L_TYPE, ptype);
+ emitImplicitConversion(L_TYPE, ptype, arg);
}
}
}
/**
+ * Store the name to its local, if necessary.
+ */
+ private void emitStoreResult(Name name) {
+ if (name != null && name.type != V_TYPE) {
+ // non-void: actually assign
+ emitStoreInsn(name.type, name.index());
+ }
+ }
+
+ /**
* Emits a return statement from a LF invoker. If required, the result type is cast to the correct return type.
*/
- private void emitReturn() {
+ private void emitReturn(Name onStack) {
// return statement
Class<?> rclass = invokerType.returnType();
BasicType rtype = lambdaForm.returnType();
@@ -907,12 +1105,11 @@
LambdaForm.Name rn = lambdaForm.names[lambdaForm.result];
// put return value on the stack if it is not already there
- if (lambdaForm.result != lambdaForm.names.length - 1 ||
- lambdaForm.result < lambdaForm.arity) {
- emitLoadInsn(rn.type, lambdaForm.result);
+ if (rn != onStack) {
+ emitLoadInsn(rtype, lambdaForm.result);
}
- emitImplicitConversion(rtype, rclass);
+ emitImplicitConversion(rtype, rclass, rn);
// generate actual return statement
emitReturnInsn(rtype);
--- a/jdk/src/java.base/share/classes/sun/invoke/util/VerifyType.java Wed Sep 10 16:14:14 2014 +0100
+++ b/jdk/src/java.base/share/classes/sun/invoke/util/VerifyType.java Wed Sep 10 19:19:46 2014 +0400
@@ -40,18 +40,38 @@
/**
* True if a value can be stacked as the source type and unstacked as the
* destination type, without violating the JVM's type consistency.
+ * <p>
+ * If both types are references, we apply the verifier's subclass check
+ * (or subtyping, if keepInterfaces).
+ * If the src type is a type guaranteed to be null (Void) it can be converted
+ * to any other reference type.
+ * <p>
+ * If both types are primitives, we apply the verifier's primitive conversions.
+ * These do not include Java conversions such as long to double, since those
+ * require computation and (in general) stack depth changes.
+ * But very simple 32-bit viewing changes, such as byte to int,
+ * are null conversions, because they do not require any computation.
+ * These conversions are from any type to a wider type up to 32 bits,
+ * as long as the conversion is not signed to unsigned (byte to char).
+ * <p>
+ * The primitive type 'void' does not interconvert with any other type,
+ * even though it is legal to drop any type from the stack and "return void".
+ * The stack effects, though are different between void and any other type,
+ * so it is safer to report a non-trivial conversion.
*
* @param src the type of a stacked value
* @param dst the type by which we'd like to treat it
+ * @param keepInterfaces if false, we treat any interface as if it were Object
* @return whether the retyping can be done without motion or reformatting
*/
- public static boolean isNullConversion(Class<?> src, Class<?> dst) {
+ public static boolean isNullConversion(Class<?> src, Class<?> dst, boolean keepInterfaces) {
if (src == dst) return true;
// Verifier allows any interface to be treated as Object:
- if (dst.isInterface()) dst = Object.class;
- if (src.isInterface()) src = Object.class;
- if (src == dst) return true; // check again
- if (dst == void.class) return true; // drop any return value
+ if (!keepInterfaces) {
+ if (dst.isInterface()) dst = Object.class;
+ if (src.isInterface()) src = Object.class;
+ if (src == dst) return true; // check again
+ }
if (isNullType(src)) return !dst.isPrimitive();
if (!src.isPrimitive()) return dst.isAssignableFrom(src);
if (!dst.isPrimitive()) return false;
@@ -82,25 +102,13 @@
* Is the given type java.lang.Null or an equivalent null-only type?
*/
public static boolean isNullType(Class<?> type) {
- if (type == null) return false;
- return type == NULL_CLASS
- // This one may also be used as a null type.
- // TO DO: Decide if we really want to legitimize it here.
- // Probably we do, unless java.lang.Null really makes it into Java 7
- //|| type == Void.class
- // Locally known null-only class:
- || type == Empty.class
- ;
- }
- private static final Class<?> NULL_CLASS;
- static {
- Class<?> nullClass = null;
- try {
- nullClass = Class.forName("java.lang.Null");
- } catch (ClassNotFoundException ex) {
- // OK, we'll cope
- }
- NULL_CLASS = nullClass;
+ // Any reference statically typed as Void is guaranteed to be null.
+ // Therefore, it can be safely treated as a value of any
+ // other type that admits null, i.e., a reference type.
+ if (type == Void.class) return true;
+ // Locally known null-only class:
+ if (type == Empty.class) return true;
+ return false;
}
/**
@@ -111,14 +119,14 @@
* @param recv the type of the method handle receiving the call
* @return whether the retyping can be done without motion or reformatting
*/
- public static boolean isNullConversion(MethodType call, MethodType recv) {
+ public static boolean isNullConversion(MethodType call, MethodType recv, boolean keepInterfaces) {
if (call == recv) return true;
int len = call.parameterCount();
if (len != recv.parameterCount()) return false;
for (int i = 0; i < len; i++)
- if (!isNullConversion(call.parameterType(i), recv.parameterType(i)))
+ if (!isNullConversion(call.parameterType(i), recv.parameterType(i), keepInterfaces))
return false;
- return isNullConversion(recv.returnType(), call.returnType());
+ return isNullConversion(recv.returnType(), call.returnType(), keepInterfaces);
}
/**