This commit is contained in:
Christian Thalinger 2012-08-10 16:03:28 -07:00
commit eadc52ac6e
35 changed files with 7893 additions and 3696 deletions

View file

@ -489,9 +489,18 @@ public abstract class ClassValue<T> {
/** Remove an entry. */ /** Remove an entry. */
synchronized synchronized
void removeEntry(ClassValue<?> classValue) { void removeEntry(ClassValue<?> classValue) {
// make all cache elements for this guy go stale: Entry<?> e = remove(classValue.identity);
if (remove(classValue.identity) != null) { if (e == null) {
// Uninitialized, and no pending calls to computeValue. No change.
} else if (e.isPromise()) {
// State is uninitialized, with a pending call to finishEntry.
// Since remove is a no-op in such a state, keep the promise
// by putting it back into the map.
put(classValue.identity, e);
} else {
// In an initialized state. Bump forward, and de-initialize.
classValue.bumpVersion(); classValue.bumpVersion();
// Make all cache elements for this guy go stale.
removeStaleEntries(classValue); removeStaleEntries(classValue);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,164 +25,828 @@
package java.lang.invoke; package java.lang.invoke;
import sun.invoke.util.VerifyType; import static com.sun.xml.internal.ws.org.objectweb.asm.Opcodes.*;
import sun.invoke.util.Wrapper; import static java.lang.invoke.LambdaForm.basicTypes;
import static java.lang.invoke.MethodHandleNatives.Constants.REF_invokeStatic;
import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandleStatics.*;
import java.lang.invoke.LambdaForm.Name;
import java.lang.invoke.LambdaForm.NamedFunction;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import sun.invoke.util.ValueConversions;
import sun.invoke.util.Wrapper;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.MethodVisitor;
import com.sun.xml.internal.ws.org.objectweb.asm.Type;
/** /**
* The flavor of method handle which emulates an invoke instruction * The flavor of method handle which emulates an invoke instruction
* on a predetermined argument. The JVM dispatches to the correct method * on a predetermined argument. The JVM dispatches to the correct method
* when the handle is created, not when it is invoked. * when the handle is created, not when it is invoked.
* @author jrose *
* All bound arguments are encapsulated in dedicated species.
*/ */
class BoundMethodHandle extends MethodHandle { /* non-public */ abstract class BoundMethodHandle extends MethodHandle {
//MethodHandle vmtarget; // next BMH or final DMH or methodOop
private final Object argument; // argument to insert
private final int vmargslot; // position at which it is inserted
// Constructors in this class *must* be package scoped or private. /* non-public */ BoundMethodHandle(MethodType type, LambdaForm form) {
super(type, form);
/** Bind a direct MH to its receiver (or first ref. argument).
* The JVM will pre-dispatch the MH if it is not already static.
*/
/*non-public*/ BoundMethodHandle(DirectMethodHandle mh, Object argument) {
super(mh.type().dropParameterTypes(0, 1));
// check the type now, once for all:
this.argument = checkReferenceArgument(argument, mh, 0);
this.vmargslot = this.type().parameterSlotCount();
initTarget(mh, 0);
} }
/** Insert an argument into an arbitrary method handle. //
* If argnum is zero, inserts the first argument, etc. // BMH API and internals
* The argument type must be a reference. //
*/
/*non-public*/ BoundMethodHandle(MethodHandle mh, Object argument, int argnum) { static MethodHandle bindSingle(MethodType type, LambdaForm form, char xtype, Object x) {
this(mh.type().dropParameterTypes(argnum, argnum+1), // for some type signatures, there exist pre-defined concrete BMH classes
mh, argument, argnum); try {
switch (xtype) {
case 'L':
if (true) return bindSingle(type, form, x); // Use known fast path.
return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('L').constructor[0].invokeBasic(type, form, x);
case 'I':
return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('I').constructor[0].invokeBasic(type, form, ValueConversions.widenSubword(x));
case 'J':
return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('J').constructor[0].invokeBasic(type, form, (long) x);
case 'F':
return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('F').constructor[0].invokeBasic(type, form, (float) x);
case 'D':
return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('D').constructor[0].invokeBasic(type, form, (double) x);
default : throw new InternalError("unexpected xtype: " + xtype);
}
} catch (Throwable t) {
throw new InternalError(t);
}
} }
/** Insert an argument into an arbitrary method handle. static MethodHandle bindSingle(MethodType type, LambdaForm form, Object x) {
* If argnum is zero, inserts the first argument, etc. return new Species_L(type, form, x);
*/
/*non-public*/ BoundMethodHandle(MethodType type, MethodHandle mh, Object argument, int argnum) {
super(type);
if (mh.type().parameterType(argnum).isPrimitive())
this.argument = bindPrimitiveArgument(argument, mh, argnum);
else {
this.argument = checkReferenceArgument(argument, mh, argnum);
}
this.vmargslot = type.parameterSlotDepth(argnum);
initTarget(mh, argnum);
} }
private void initTarget(MethodHandle mh, int argnum) { MethodHandle cloneExtend(MethodType type, LambdaForm form, char xtype, Object x) {
//this.vmtarget = mh; // maybe updated by JVM try {
MethodHandleNatives.init(this, mh, argnum); switch (xtype) {
case 'L': return cloneExtendL(type, form, x);
case 'I': return cloneExtendI(type, form, ValueConversions.widenSubword(x));
case 'J': return cloneExtendJ(type, form, (long) x);
case 'F': return cloneExtendF(type, form, (float) x);
case 'D': return cloneExtendD(type, form, (double) x);
} }
} catch (Throwable t) {
/** For the AdapterMethodHandle subclass. throw new InternalError(t);
*/
/*non-public*/ BoundMethodHandle(MethodType type, Object argument, int vmargslot) {
super(type);
this.argument = argument;
this.vmargslot = vmargslot;
assert(this instanceof AdapterMethodHandle);
} }
throw new InternalError("unexpected type: " + xtype);
/** Initialize the current object as a self-bound method handle, binding it
* as the first argument of the method handle {@code entryPoint}.
* The invocation type of the resulting method handle will be the
* same as {@code entryPoint}, except that the first argument
* type will be dropped.
*/
/*non-public*/ BoundMethodHandle(MethodHandle entryPoint) {
super(entryPoint.type().dropParameterTypes(0, 1));
this.argument = this; // kludge; get rid of
this.vmargslot = this.type().parameterSlotDepth(0);
initTarget(entryPoint, 0);
}
/** Make sure the given {@code argument} can be used as {@code argnum}-th
* parameter of the given method handle {@code mh}, which must be a reference.
* <p>
* If this fails, throw a suitable {@code WrongMethodTypeException},
* which will prevent the creation of an illegally typed bound
* method handle.
*/
final static Object checkReferenceArgument(Object argument, MethodHandle mh, int argnum) {
Class<?> ptype = mh.type().parameterType(argnum);
if (ptype.isPrimitive()) {
// fail
} else if (argument == null) {
return null;
} else if (VerifyType.isNullReferenceConversion(argument.getClass(), ptype)) {
return argument;
}
throw badBoundArgumentException(argument, mh, argnum);
}
/** Make sure the given {@code argument} can be used as {@code argnum}-th
* parameter of the given method handle {@code mh}, which must be a primitive.
* <p>
* If this fails, throw a suitable {@code WrongMethodTypeException},
* which will prevent the creation of an illegally typed bound
* method handle.
*/
final static Object bindPrimitiveArgument(Object argument, MethodHandle mh, int argnum) {
Class<?> ptype = mh.type().parameterType(argnum);
Wrapper wrap = Wrapper.forPrimitiveType(ptype);
Object zero = wrap.zero();
if (zero == null) {
// fail
} else if (argument == null) {
if (ptype != int.class && wrap.isSubwordOrInt())
return Integer.valueOf(0);
else
return zero;
} else if (VerifyType.isNullReferenceConversion(argument.getClass(), zero.getClass())) {
if (ptype != int.class && wrap.isSubwordOrInt())
return Wrapper.INT.wrap(argument);
else
return argument;
}
throw badBoundArgumentException(argument, mh, argnum);
}
final static RuntimeException badBoundArgumentException(Object argument, MethodHandle mh, int argnum) {
String atype = (argument == null) ? "null" : argument.getClass().toString();
return new ClassCastException("cannot bind "+atype+" argument to parameter #"+argnum+" of "+mh.type());
} }
@Override @Override
String debugString() { MethodHandle bindArgument(int pos, char basicType, Object value) {
return addTypeString(baseName(), this); MethodType type = type().dropParameterTypes(pos, pos+1);
LambdaForm form = internalForm().bind(1+pos, speciesData());
return cloneExtend(type, form, basicType, value);
} }
/** Component of toString() before the type string. */ @Override
protected String baseName() { MethodHandle dropArguments(MethodType srcType, int pos, int drops) {
MethodHandle mh = this; LambdaForm form = internalForm().addArguments(pos, srcType.parameterList().subList(pos, pos+drops));
while (mh instanceof BoundMethodHandle) { try {
Object info = MethodHandleNatives.getTargetInfo(mh); return clone(srcType, form);
if (info instanceof MethodHandle) { } catch (Throwable t) {
mh = (MethodHandle) info; throw new InternalError(t);
}
}
@Override
MethodHandle permuteArguments(MethodType newType, int[] reorder) {
try {
return clone(newType, form.permuteArguments(1, reorder, basicTypes(newType.parameterList())));
} catch (Throwable t) {
throw new InternalError(t);
}
}
static final String EXTENSION_TYPES = "LIJFD";
static final byte INDEX_L = 0, INDEX_I = 1, INDEX_J = 2, INDEX_F = 3, INDEX_D = 4;
static byte extensionIndex(char type) {
int i = EXTENSION_TYPES.indexOf(type);
if (i < 0) throw new InternalError();
return (byte) i;
}
/**
* Return the {@link SpeciesData} instance representing this BMH species. All subclasses must provide a
* static field containing this value, and they must accordingly implement this method.
*/
protected abstract SpeciesData speciesData();
@Override
final Object internalValues() {
Object[] boundValues = new Object[speciesData().fieldCount()];
for (int i = 0; i < boundValues.length; ++i) {
boundValues[i] = arg(i);
}
return Arrays.asList(boundValues);
}
public final Object arg(int i) {
try {
switch (speciesData().fieldType(i)) {
case 'L': return argL(i);
case 'I': return argI(i);
case 'F': return argF(i);
case 'D': return argD(i);
case 'J': return argJ(i);
}
} catch (Throwable ex) {
throw new InternalError(ex);
}
throw new InternalError("unexpected type: " + speciesData().types+"."+i);
}
public final Object argL(int i) throws Throwable { return speciesData().getters[i].invokeBasic(this); }
public final int argI(int i) throws Throwable { return (int) speciesData().getters[i].invokeBasic(this); }
public final float argF(int i) throws Throwable { return (float) speciesData().getters[i].invokeBasic(this); }
public final double argD(int i) throws Throwable { return (double) speciesData().getters[i].invokeBasic(this); }
public final long argJ(int i) throws Throwable { return (long) speciesData().getters[i].invokeBasic(this); }
//
// cloning API
//
public abstract BoundMethodHandle clone(MethodType mt, LambdaForm lf) throws Throwable;
public abstract BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) throws Throwable;
public abstract BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) throws Throwable;
public abstract BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) throws Throwable;
public abstract BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) throws Throwable;
public abstract BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) throws Throwable;
// The following is a grossly irregular hack:
@Override MethodHandle reinvokerTarget() {
try {
return (MethodHandle) argL(0);
} catch (Throwable ex) {
throw new InternalError(ex);
}
}
//
// concrete BMH classes required to close bootstrap loops
//
private // make it private to force users to access the enclosing class first
static final class Species_L extends BoundMethodHandle {
final Object argL0;
public Species_L(MethodType mt, LambdaForm lf, Object argL0) {
super(mt, lf);
this.argL0 = argL0;
}
// The following is a grossly irregular hack:
@Override MethodHandle reinvokerTarget() { return (MethodHandle) argL0; }
@Override
public SpeciesData speciesData() {
return SPECIES_DATA;
}
public static final SpeciesData SPECIES_DATA = SpeciesData.getForClass("L", Species_L.class);
@Override
public final BoundMethodHandle clone(MethodType mt, LambdaForm lf) throws Throwable {
return new Species_L(mt, lf, argL0);
}
@Override
public final BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_L).constructor[0].invokeBasic(mt, lf, argL0, narg);
}
@Override
public final BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_I).constructor[0].invokeBasic(mt, lf, argL0, narg);
}
@Override
public final BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_J).constructor[0].invokeBasic(mt, lf, argL0, narg);
}
@Override
public final BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_F).constructor[0].invokeBasic(mt, lf, argL0, narg);
}
@Override
public final BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_D).constructor[0].invokeBasic(mt, lf, argL0, narg);
}
}
/*
static final class Species_LL extends BoundMethodHandle {
final Object argL0;
final Object argL1;
public Species_LL(MethodType mt, LambdaForm lf, Object argL0, Object argL1) {
super(mt, lf);
this.argL0 = argL0;
this.argL1 = argL1;
}
@Override
public SpeciesData speciesData() {
return SPECIES_DATA;
}
public static final SpeciesData SPECIES_DATA = SpeciesData.getForClass("LL", Species_LL.class);
@Override
public final BoundMethodHandle clone(MethodType mt, LambdaForm lf) throws Throwable {
return new Species_LL(mt, lf, argL0, argL1);
}
@Override
public final BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_L).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg);
}
@Override
public final BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_I).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg);
}
@Override
public final BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_J).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg);
}
@Override
public final BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_F).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg);
}
@Override
public final BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_D).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg);
}
}
static final class Species_JL extends BoundMethodHandle {
final long argJ0;
final Object argL1;
public Species_JL(MethodType mt, LambdaForm lf, long argJ0, Object argL1) {
super(mt, lf);
this.argJ0 = argJ0;
this.argL1 = argL1;
}
@Override
public SpeciesData speciesData() {
return SPECIES_DATA;
}
public static final SpeciesData SPECIES_DATA = SpeciesData.getForClass("JL", Species_JL.class);
@Override public final long argJ0() { return argJ0; }
@Override public final Object argL1() { return argL1; }
@Override
public final BoundMethodHandle clone(MethodType mt, LambdaForm lf) throws Throwable {
return new Species_JL(mt, lf, argJ0, argL1);
}
@Override
public final BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_L).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg);
}
@Override
public final BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_I).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg);
}
@Override
public final BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_J).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg);
}
@Override
public final BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_F).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg);
}
@Override
public final BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) throws Throwable {
return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_D).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg);
}
}
*/
//
// BMH species meta-data
//
/**
* Meta-data wrapper for concrete BMH classes.
*/
static class SpeciesData {
final String types;
final Class<? extends BoundMethodHandle> clazz;
// Bootstrapping requires circular relations MH -> BMH -> SpeciesData -> MH
// Therefore, we need a non-final link in the chain. Use array elements.
final MethodHandle[] constructor;
final MethodHandle[] getters;
final SpeciesData[] extensions;
public int fieldCount() {
return types.length();
}
public char fieldType(int i) {
return types.charAt(i);
}
public String toString() {
return "SpeciesData["+(isPlaceholder() ? "<placeholder>" : clazz.getSimpleName())+":"+types+"]";
}
/**
* Return a {@link LambdaForm.Name} containing a {@link LambdaForm.NamedFunction} that
* represents a MH bound to a generic invoker, which in turn forwards to the corresponding
* getter.
*/
Name getterName(Name mhName, int i) {
MethodHandle mh = getters[i];
assert(mh != null) : this+"."+i;
return new Name(mh, mhName);
}
static final SpeciesData EMPTY = new SpeciesData("", BoundMethodHandle.class);
private SpeciesData(String types, Class<? extends BoundMethodHandle> clazz) {
this.types = types;
this.clazz = clazz;
if (!INIT_DONE) {
this.constructor = new MethodHandle[1];
this.getters = new MethodHandle[types.length()];
} else { } else {
String name = null; this.constructor = Factory.makeCtors(clazz, types, null);
if (info instanceof MemberName) this.getters = Factory.makeGetters(clazz, types, null);
name = ((MemberName)info).getName();
if (name != null)
return name;
else
return noParens(super.toString()); // "invoke", probably
} }
assert(mh != this); this.extensions = new SpeciesData[EXTENSION_TYPES.length()];
}
return noParens(mh.toString());
} }
private static String noParens(String str) { private void initForBootstrap() {
int paren = str.indexOf('('); assert(!INIT_DONE);
if (paren >= 0) str = str.substring(0, paren); if (constructor[0] == null) {
return str; Factory.makeCtors(clazz, types, this.constructor);
Factory.makeGetters(clazz, types, this.getters);
} }
} }
private SpeciesData(String types) {
// Placeholder only.
this.types = types;
this.clazz = null;
this.constructor = null;
this.getters = null;
this.extensions = null;
}
private boolean isPlaceholder() { return clazz == null; }
private static final HashMap<String, SpeciesData> CACHE = new HashMap<>();
private static final boolean INIT_DONE; // set after <clinit> finishes...
SpeciesData extendWithType(char type) {
int i = extensionIndex(type);
SpeciesData d = extensions[i];
if (d != null) return d;
extensions[i] = d = get(types+type);
return d;
}
SpeciesData extendWithIndex(byte index) {
SpeciesData d = extensions[index];
if (d != null) return d;
extensions[index] = d = get(types+EXTENSION_TYPES.charAt(index));
return d;
}
private static SpeciesData get(String types) {
// Acquire cache lock for query.
SpeciesData d = lookupCache(types);
if (!d.isPlaceholder())
return d;
synchronized (d) {
// Use synch. on the placeholder to prevent multiple instantiation of one species.
// Creating this class forces a recursive call to getForClass.
if (lookupCache(types).isPlaceholder())
Factory.generateConcreteBMHClass(types);
}
// Reacquire cache lock.
d = lookupCache(types);
// Class loading must have upgraded the cache.
assert(d != null && !d.isPlaceholder());
return d;
}
static SpeciesData getForClass(String types, Class<? extends BoundMethodHandle> clazz) {
// clazz is a new class which is initializing its SPECIES_DATA field
return updateCache(types, new SpeciesData(types, clazz));
}
private static synchronized SpeciesData lookupCache(String types) {
SpeciesData d = CACHE.get(types);
if (d != null) return d;
d = new SpeciesData(types);
assert(d.isPlaceholder());
CACHE.put(types, d);
return d;
}
private static synchronized SpeciesData updateCache(String types, SpeciesData d) {
SpeciesData d2;
assert((d2 = CACHE.get(types)) == null || d2.isPlaceholder());
assert(!d.isPlaceholder());
CACHE.put(types, d);
return d;
}
static {
// pre-fill the BMH speciesdata cache with BMH's inner classes
final Class<BoundMethodHandle> rootCls = BoundMethodHandle.class;
SpeciesData d0 = BoundMethodHandle.SPECIES_DATA; // trigger class init
assert(d0 == null || d0 == lookupCache("")) : d0;
try {
for (Class<?> c : rootCls.getDeclaredClasses()) {
if (rootCls.isAssignableFrom(c)) {
final Class<? extends BoundMethodHandle> cbmh = c.asSubclass(BoundMethodHandle.class);
SpeciesData d = Factory.speciesDataFromConcreteBMHClass(cbmh);
assert(d != null) : cbmh.getName();
assert(d.clazz == cbmh);
assert(d == lookupCache(d.types));
}
}
} catch (Throwable e) {
throw new InternalError(e);
}
for (SpeciesData d : CACHE.values()) {
d.initForBootstrap();
}
// Note: Do not simplify this, because INIT_DONE must not be
// a compile-time constant during bootstrapping.
INIT_DONE = Boolean.TRUE;
}
}
static SpeciesData getSpeciesData(String types) {
return SpeciesData.get(types);
}
/**
* Generation of concrete BMH classes.
*
* A concrete BMH species is fit for binding a number of values adhering to a
* given type pattern. Reference types are erased.
*
* BMH species are cached by type pattern.
*
* A BMH species has a number of fields with the concrete (possibly erased) types of
* bound values. Setters are provided as an API in BMH. Getters are exposed as MHs,
* which can be included as names in lambda forms.
*/
static class Factory {
static final String JLO_SIG = "Ljava/lang/Object;";
static final String JLS_SIG = "Ljava/lang/String;";
static final String JLC_SIG = "Ljava/lang/Class;";
static final String MH = "java/lang/invoke/MethodHandle";
static final String MH_SIG = "L"+MH+";";
static final String BMH = "java/lang/invoke/BoundMethodHandle";
static final String BMH_SIG = "L"+BMH+";";
static final String SPECIES_DATA = "java/lang/invoke/BoundMethodHandle$SpeciesData";
static final String SPECIES_DATA_SIG = "L"+SPECIES_DATA+";";
static final String SPECIES_PREFIX_NAME = "Species_";
static final String SPECIES_PREFIX_PATH = BMH + "$" + SPECIES_PREFIX_NAME;
static final String BMHSPECIES_DATA_EWI_SIG = "(B)" + SPECIES_DATA_SIG;
static final String BMHSPECIES_DATA_GFC_SIG = "(" + JLS_SIG + JLC_SIG + ")" + SPECIES_DATA_SIG;
static final String MYSPECIES_DATA_SIG = "()" + SPECIES_DATA_SIG;
static final String VOID_SIG = "()V";
static final String SIG_INCIPIT = "(Ljava/lang/invoke/MethodType;Ljava/lang/invoke/LambdaForm;";
static final Class<?>[] TYPES = new Class<?>[] { Object.class, int.class, long.class, float.class, double.class };
static final String[] E_THROWABLE = new String[] { "java/lang/Throwable" };
/**
* Generate a concrete subclass of BMH for a given combination of bound types.
*
* A concrete BMH species adheres to the following schema:
*
* <pre>
* class Species_<<types>> extends BoundMethodHandle {
* <<fields>>
* final SpeciesData speciesData() { return SpeciesData.get("<<types>>"); }
* }
* </pre>
*
* The {@code <<types>>} signature is precisely the string that is passed to this
* method.
*
* The {@code <<fields>>} section consists of one field definition per character in
* the type signature, adhering to the naming schema described in the definition of
* {@link #makeFieldName()}.
*
* For example, a concrete BMH species for two reference and one integral bound values
* would have the following shape:
*
* <pre>
* class BoundMethodHandle { ... private static
* final class Species_LLI extends BoundMethodHandle {
* final Object argL0;
* final Object argL1;
* final int argI2;
* public Species_LLI(MethodType mt, LambdaForm lf, Object argL0, Object argL1, int argI2) {
* super(mt, lf);
* this.argL0 = argL0;
* this.argL1 = argL1;
* this.argI2 = argI2;
* }
* public final SpeciesData speciesData() { return SPECIES_DATA; }
* public static final SpeciesData SPECIES_DATA = SpeciesData.getForClass("LLI", Species_LLI.class);
* public final BoundMethodHandle clone(MethodType mt, LambdaForm lf) {
* return SPECIES_DATA.constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2);
* }
* public final BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) {
* return SPECIES_DATA.extendWithIndex(INDEX_L).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
* }
* public final BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) {
* return SPECIES_DATA.extendWithIndex(INDEX_I).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
* }
* public final BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) {
* return SPECIES_DATA.extendWithIndex(INDEX_J).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
* }
* public final BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) {
* return SPECIES_DATA.extendWithIndex(INDEX_F).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
* }
* public final BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) {
* return SPECIES_DATA.extendWithIndex(INDEX_D).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
* }
* }
* </pre>
*
* @param types the type signature, wherein reference types are erased to 'L'
* @return the generated concrete BMH class
*/
static Class<? extends BoundMethodHandle> generateConcreteBMHClass(String types) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
final String className = SPECIES_PREFIX_PATH + types;
final String sourceFile = SPECIES_PREFIX_NAME + types;
cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, BMH, null);
cw.visitSource(sourceFile, null);
// emit static types and SPECIES_DATA fields
cw.visitField(ACC_PUBLIC + ACC_STATIC, "SPECIES_DATA", SPECIES_DATA_SIG, null, null).visitEnd();
// emit bound argument fields
for (int i = 0; i < types.length(); ++i) {
final char t = types.charAt(i);
final String fieldName = makeFieldName(types, i);
final String fieldDesc = t == 'L' ? JLO_SIG : String.valueOf(t);
cw.visitField(ACC_FINAL, fieldName, fieldDesc, null, null).visitEnd();
}
MethodVisitor mv;
// emit constructor
mv = cw.visitMethod(ACC_PUBLIC, "<init>", makeSignature(types, true), null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKESPECIAL, BMH, "<init>", makeSignature("", true));
for (int i = 0, j = 0; i < types.length(); ++i, ++j) {
// i counts the arguments, j counts corresponding argument slots
char t = types.charAt(i);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(typeLoadOp(t), j + 3); // parameters start at 3
mv.visitFieldInsn(PUTFIELD, className, makeFieldName(types, i), typeSig(t));
if (t == 'J' || t == 'D') {
++j; // adjust argument register access
}
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// emit implementation of reinvokerTarget()
mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "reinvokerTarget", "()" + MH_SIG, null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, "argL0", JLO_SIG);
mv.visitTypeInsn(CHECKCAST, MH);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// emit implementation of speciesData()
mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "speciesData", MYSPECIES_DATA_SIG, null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, className, "SPECIES_DATA", SPECIES_DATA_SIG);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// emit clone()
mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "clone", makeSignature("", false), null, E_THROWABLE);
mv.visitCode();
// return speciesData().constructor[0].invokeBasic(mt, lf, argL0, ...)
// obtain constructor
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETSTATIC, className, "SPECIES_DATA", SPECIES_DATA_SIG);
mv.visitFieldInsn(GETFIELD, SPECIES_DATA, "constructor", "[" + MH_SIG);
mv.visitInsn(ICONST_0);
mv.visitInsn(AALOAD);
// load mt, lf
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
// put fields on the stack
emitPushFields(types, className, mv);
// finally, invoke the constructor and return
mv.visitMethodInsn(INVOKEVIRTUAL, MH, "invokeBasic", makeSignature(types, false));
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// for each type, emit cloneExtendT()
for (Class<?> c : TYPES) {
char t = Wrapper.basicTypeChar(c);
mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "cloneExtend" + t, makeSignature(String.valueOf(t), false), null, E_THROWABLE);
mv.visitCode();
// return SPECIES_DATA.extendWithIndex(extensionIndex(t)).constructor[0].invokeBasic(mt, lf, argL0, ..., narg)
// obtain constructor
mv.visitFieldInsn(GETSTATIC, className, "SPECIES_DATA", SPECIES_DATA_SIG);
int iconstInsn = ICONST_0 + extensionIndex(t);
assert(iconstInsn <= ICONST_5);
mv.visitInsn(iconstInsn);
mv.visitMethodInsn(INVOKEVIRTUAL, SPECIES_DATA, "extendWithIndex", BMHSPECIES_DATA_EWI_SIG);
mv.visitFieldInsn(GETFIELD, SPECIES_DATA, "constructor", "[" + MH_SIG);
mv.visitInsn(ICONST_0);
mv.visitInsn(AALOAD);
// load mt, lf
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
// put fields on the stack
emitPushFields(types, className, mv);
// put narg on stack
mv.visitVarInsn(typeLoadOp(t), 3);
// finally, invoke the constructor and return
mv.visitMethodInsn(INVOKEVIRTUAL, MH, "invokeBasic", makeSignature(types + t, false));
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
// emit class initializer
mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "<clinit>", VOID_SIG, null, null);
mv.visitCode();
mv.visitLdcInsn(types);
mv.visitLdcInsn(Type.getObjectType(className));
mv.visitMethodInsn(INVOKESTATIC, SPECIES_DATA, "getForClass", BMHSPECIES_DATA_GFC_SIG);
mv.visitFieldInsn(PUTSTATIC, className, "SPECIES_DATA", SPECIES_DATA_SIG);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
// load class
final byte[] classFile = cw.toByteArray();
InvokerBytecodeGenerator.maybeDump(className, classFile);
Class<? extends BoundMethodHandle> bmhClass =
//UNSAFE.defineAnonymousClass(BoundMethodHandle.class, classFile, null).asSubclass(BoundMethodHandle.class);
UNSAFE.defineClass(className, classFile, 0, classFile.length).asSubclass(BoundMethodHandle.class);
UNSAFE.ensureClassInitialized(bmhClass);
return bmhClass;
}
private static int typeLoadOp(char t) {
switch (t) {
case 'L': return ALOAD;
case 'I': return ILOAD;
case 'J': return LLOAD;
case 'F': return FLOAD;
case 'D': return DLOAD;
default : throw new InternalError("unrecognized type " + t);
}
}
private static void emitPushFields(String types, String className, MethodVisitor mv) {
for (int i = 0; i < types.length(); ++i) {
char tc = types.charAt(i);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, makeFieldName(types, i), typeSig(tc));
}
}
static String typeSig(char t) {
return t == 'L' ? JLO_SIG : String.valueOf(t);
}
//
// Getter MH generation.
//
private static MethodHandle makeGetter(Class<?> cbmhClass, String types, int index) {
String fieldName = makeFieldName(types, index);
Class<?> fieldType = Wrapper.forBasicType(types.charAt(index)).primitiveType();
try {
return LOOKUP.findGetter(cbmhClass, fieldName, fieldType);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new InternalError(e);
}
}
static MethodHandle[] makeGetters(Class<?> cbmhClass, String types, MethodHandle[] mhs) {
if (mhs == null) mhs = new MethodHandle[types.length()];
for (int i = 0; i < mhs.length; ++i) {
mhs[i] = makeGetter(cbmhClass, types, i);
assert(mhs[i].internalMemberName().getDeclaringClass() == cbmhClass);
}
return mhs;
}
static MethodHandle[] makeCtors(Class<? extends BoundMethodHandle> cbmh, String types, MethodHandle mhs[]) {
if (mhs == null) mhs = new MethodHandle[1];
mhs[0] = makeCbmhCtor(cbmh, types);
return mhs;
}
//
// Auxiliary methods.
//
static SpeciesData speciesDataFromConcreteBMHClass(Class<? extends BoundMethodHandle> cbmh) {
try {
Field F_SPECIES_DATA = cbmh.getDeclaredField("SPECIES_DATA");
return (SpeciesData) F_SPECIES_DATA.get(null);
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
}
/**
* Field names in concrete BMHs adhere to this pattern:
* arg + type + index
* where type is a single character (L, I, J, F, D).
*/
private static String makeFieldName(String types, int index) {
assert index >= 0 && index < types.length();
return "arg" + types.charAt(index) + index;
}
private static String makeSignature(String types, boolean ctor) {
StringBuilder buf = new StringBuilder(SIG_INCIPIT);
for (char c : types.toCharArray()) {
buf.append(typeSig(c));
}
return buf.append(')').append(ctor ? "V" : BMH_SIG).toString();
}
static MethodHandle makeCbmhCtor(Class<? extends BoundMethodHandle> cbmh, String types) {
try {
return linkConstructor(LOOKUP.findConstructor(cbmh, MethodType.fromMethodDescriptorString(makeSignature(types, true), null)));
} catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | TypeNotPresentException e) {
throw new InternalError(e);
}
}
/**
* Wrap a constructor call in a {@link LambdaForm}.
*
* If constructors ({@code <init>} methods) are called in LFs, problems might arise if the LFs
* are turned into bytecode, because the call to the allocator is routed through an MH, and the
* verifier cannot find a {@code NEW} instruction preceding the {@code INVOKESPECIAL} to
* {@code <init>}. To avoid this, we add an indirection by invoking {@code <init>} through
* {@link MethodHandle#linkToSpecial}.
*
* The last {@link LambdaForm#Name Name} in the argument's form is expected to be the {@code void}
* result of the {@code <init>} invocation. This entry is replaced.
*/
private static MethodHandle linkConstructor(MethodHandle cmh) {
final LambdaForm lf = cmh.form;
final int initNameIndex = lf.names.length - 1;
final Name initName = lf.names[initNameIndex];
final MemberName ctorMN = initName.function.member;
final MethodType ctorMT = ctorMN.getInvocationType();
// obtain function member (call target)
// linker method type replaces initial parameter (BMH species) with BMH to avoid naming a species (anonymous class!)
final MethodType linkerMT = ctorMT.changeParameterType(0, BoundMethodHandle.class).appendParameterTypes(MemberName.class);
MemberName linkerMN = new MemberName(MethodHandle.class, "linkToSpecial", linkerMT, REF_invokeStatic);
try {
linkerMN = MemberName.getFactory().resolveOrFail(REF_invokeStatic, linkerMN, null, NoSuchMethodException.class);
assert(linkerMN.isStatic());
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
// extend arguments array
Object[] newArgs = Arrays.copyOf(initName.arguments, initName.arguments.length + 1);
newArgs[newArgs.length - 1] = ctorMN;
// replace function
final NamedFunction nf = new NamedFunction(linkerMN);
final Name linkedCtor = new Name(nf, newArgs);
linkedCtor.initIndex(initNameIndex);
lf.names[initNameIndex] = linkedCtor;
return cmh;
}
}
private static final Lookup LOOKUP = Lookup.IMPL_LOOKUP;
/**
* All subclasses must provide such a value describing their type signature.
*/
static final SpeciesData SPECIES_DATA = SpeciesData.EMPTY;
}

View file

@ -26,7 +26,7 @@
package java.lang.invoke; package java.lang.invoke;
import sun.invoke.empty.Empty; import sun.invoke.empty.Empty;
import sun.misc.Unsafe; import static java.lang.invoke.MethodHandleStatics.*;
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
/** /**
@ -86,13 +86,9 @@ abstract
public class CallSite { public class CallSite {
static { MethodHandleImpl.initStatics(); } static { MethodHandleImpl.initStatics(); }
// Fields used only by the JVM. Do not use or change.
private MemberName vmmethod; // supplied by the JVM (ref. to calling method)
private int vmindex; // supplied by the JVM (BCI within calling method)
// The actual payload of this call site: // The actual payload of this call site:
/*package-private*/ /*package-private*/
MethodHandle target; MethodHandle target; // Note: This field is known to the JVM. Do not change.
/** /**
* Make a blank call site object with the given method type. * Make a blank call site object with the given method type.
@ -151,24 +147,6 @@ public class CallSite {
return target.type(); return target.type();
} }
/** Called from JVM (or low-level Java code) after the BSM returns the newly created CallSite.
* The parameters are JVM-specific.
*/
void initializeFromJVM(String name,
MethodType type,
MemberName callerMethod,
int callerBCI) {
if (this.vmmethod != null) {
// FIXME
throw new BootstrapMethodError("call site has already been linked to an invokedynamic instruction");
}
if (!this.type().equals(type)) {
throw wrongTargetType(target, type);
}
this.vmindex = callerBCI;
this.vmmethod = callerMethod;
}
/** /**
* Returns the target method of the call site, according to the * Returns the target method of the call site, according to the
* behavior defined by this call site's specific class. * behavior defined by this call site's specific class.
@ -233,7 +211,7 @@ public class CallSite {
public abstract MethodHandle dynamicInvoker(); public abstract MethodHandle dynamicInvoker();
/*non-public*/ MethodHandle makeDynamicInvoker() { /*non-public*/ MethodHandle makeDynamicInvoker() {
MethodHandle getTarget = MethodHandleImpl.bindReceiver(GET_TARGET, this); MethodHandle getTarget = GET_TARGET.bindReceiver(this);
MethodHandle invoker = MethodHandles.exactInvoker(this.type()); MethodHandle invoker = MethodHandles.exactInvoker(this.type());
return MethodHandles.foldArguments(invoker, getTarget); return MethodHandles.foldArguments(invoker, getTarget);
} }
@ -255,12 +233,10 @@ public class CallSite {
} }
// unsafe stuff: // unsafe stuff:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long TARGET_OFFSET; private static final long TARGET_OFFSET;
static { static {
try { try {
TARGET_OFFSET = unsafe.objectFieldOffset(CallSite.class.getDeclaredField("target")); TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target"));
} catch (Exception ex) { throw new Error(ex); } } catch (Exception ex) { throw new Error(ex); }
} }
@ -270,7 +246,7 @@ public class CallSite {
} }
/*package-private*/ /*package-private*/
MethodHandle getTargetVolatile() { MethodHandle getTargetVolatile() {
return (MethodHandle) unsafe.getObjectVolatile(this, TARGET_OFFSET); return (MethodHandle) UNSAFE.getObjectVolatile(this, TARGET_OFFSET);
} }
/*package-private*/ /*package-private*/
void setTargetVolatile(MethodHandle newTarget) { void setTargetVolatile(MethodHandle newTarget) {
@ -284,8 +260,7 @@ public class CallSite {
// Extra arguments for BSM, if any: // Extra arguments for BSM, if any:
Object info, Object info,
// Caller information: // Caller information:
MemberName callerMethod, int callerBCI) { Class<?> callerClass) {
Class<?> callerClass = callerMethod.getDeclaringClass();
Object caller = IMPL_LOOKUP.in(callerClass); Object caller = IMPL_LOOKUP.in(callerClass);
CallSite site; CallSite site;
try { try {

View file

@ -25,29 +25,635 @@
package java.lang.invoke; package java.lang.invoke;
import sun.misc.Unsafe;
import java.lang.reflect.Method;
import java.util.Arrays;
import sun.invoke.util.VerifyAccess;
import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.MethodTypeForm.*;
import static java.lang.invoke.MethodHandleStatics.*;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import sun.invoke.util.ValueConversions;
import sun.invoke.util.VerifyType;
import sun.invoke.util.Wrapper;
/** /**
* The flavor of method handle which emulates invokespecial or invokestatic. * The flavor of method handle which implements a constant reference
* to a class member.
* @author jrose * @author jrose
*/ */
class DirectMethodHandle extends MethodHandle { class DirectMethodHandle extends MethodHandle {
//inherited oop vmtarget; // methodOop or virtual class/interface oop final MemberName member;
private final int vmindex; // method index within class or interface
{ vmindex = VM_INDEX_UNINITIALIZED; } // JVM may change this
// Constructors in this class *must* be package scoped or private. // Constructors and factory methods in this class *must* be package scoped or private.
DirectMethodHandle(MethodType mtype, MemberName m, boolean doDispatch, Class<?> lookupClass) { private DirectMethodHandle(MethodType mtype, LambdaForm form, MemberName member) {
super(mtype); super(mtype, form);
if (!member.isResolved()) throw new InternalError();
assert(m.isMethod() || !doDispatch && m.isConstructor()); this.member = member;
if (!m.isResolved())
throw new InternalError();
MethodHandleNatives.init(this, (Object) m, doDispatch, lookupClass);
} }
boolean isValid() { // Factory methods:
return (vmindex != VM_INDEX_UNINITIALIZED);
static DirectMethodHandle make(Class<?> receiver, MemberName member) {
MethodType mtype = member.getMethodOrFieldType();
if (!member.isStatic()) {
if (!member.getDeclaringClass().isAssignableFrom(receiver) || member.isConstructor())
throw new InternalError(member.toString());
mtype = mtype.insertParameterTypes(0, receiver);
}
if (!member.isField()) {
LambdaForm lform = preparedLambdaForm(member);
return new DirectMethodHandle(mtype, lform, member);
} else {
LambdaForm lform = preparedFieldLambdaForm(member);
if (member.isStatic()) {
long offset = MethodHandleNatives.staticFieldOffset(member);
Object base = MethodHandleNatives.staticFieldBase(member);
return new StaticAccessor(mtype, lform, member, base, offset);
} else {
long offset = MethodHandleNatives.objectFieldOffset(member);
assert(offset == (int)offset);
return new Accessor(mtype, lform, member, (int)offset);
}
}
}
static DirectMethodHandle make(MemberName member) {
if (member.isConstructor())
return makeAllocator(member);
return make(member.getDeclaringClass(), member);
}
static DirectMethodHandle make(Method method) {
return make(method.getDeclaringClass(), new MemberName(method));
}
static DirectMethodHandle make(Field field) {
return make(field.getDeclaringClass(), new MemberName(field));
}
private static DirectMethodHandle makeAllocator(MemberName ctor) {
assert(ctor.isConstructor() && ctor.getName().equals("<init>"));
Class<?> instanceClass = ctor.getDeclaringClass();
ctor = ctor.asConstructor();
assert(ctor.isConstructor() && ctor.getReferenceKind() == REF_newInvokeSpecial) : ctor;
MethodType mtype = ctor.getMethodType().changeReturnType(instanceClass);
LambdaForm lform = preparedLambdaForm(ctor);
MemberName init = ctor.asSpecial();
assert(init.getMethodType().returnType() == void.class);
return new Constructor(mtype, lform, ctor, init, instanceClass);
}
@Override
MethodHandle copyWith(MethodType mt, LambdaForm lf) {
return new DirectMethodHandle(mt, lf, member);
}
@Override
String debugString() {
return "DMH["+member.toString()+"]="+super.debugString();
}
//// Implementation methods.
@Override
@ForceInline
MemberName internalMemberName() {
return member;
}
@Override
MethodHandle bindArgument(int pos, char basicType, Object value) {
// If the member needs dispatching, do so.
if (pos == 0 && basicType == 'L') {
DirectMethodHandle concrete = maybeRebind(value);
if (concrete != null)
return concrete.bindReceiver(value);
}
return super.bindArgument(pos, basicType, value);
}
@Override
MethodHandle bindReceiver(Object receiver) {
// If the member needs dispatching, do so.
DirectMethodHandle concrete = maybeRebind(receiver);
if (concrete != null)
return concrete.bindReceiver(receiver);
return super.bindReceiver(receiver);
}
private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory();
private DirectMethodHandle maybeRebind(Object receiver) {
if (receiver != null) {
switch (member.getReferenceKind()) {
case REF_invokeInterface:
case REF_invokeVirtual:
// Pre-dispatch the member.
Class<?> concreteClass = receiver.getClass();
MemberName concrete = new MemberName(concreteClass, member.getName(), member.getMethodType(), REF_invokeSpecial);
concrete = IMPL_NAMES.resolveOrNull(REF_invokeSpecial, concrete, concreteClass);
if (concrete != null)
return new DirectMethodHandle(type(), preparedLambdaForm(concrete), concrete);
break;
}
}
return null;
}
/**
* Create a LF which can invoke the given method.
* Cache and share this structure among all methods with
* the same basicType and refKind.
*/
private static LambdaForm preparedLambdaForm(MemberName m) {
assert(m.isInvocable()) : m; // call preparedFieldLambdaForm instead
MethodType mtype = m.getInvocationType().basicType();
assert(!m.isMethodHandleInvoke() || "invokeBasic".equals(m.getName())) : m;
int which;
switch (m.getReferenceKind()) {
case REF_invokeVirtual: which = LF_INVVIRTUAL; break;
case REF_invokeStatic: which = LF_INVSTATIC; break;
case REF_invokeSpecial: which = LF_INVSPECIAL; break;
case REF_invokeInterface: which = LF_INVINTERFACE; break;
case REF_newInvokeSpecial: which = LF_NEWINVSPECIAL; break;
default: throw new InternalError(m.toString());
}
if (which == LF_INVSTATIC && shouldBeInitialized(m)) {
// precompute the barrier-free version:
preparedLambdaForm(mtype, which);
which = LF_INVSTATIC_INIT;
}
LambdaForm lform = preparedLambdaForm(mtype, which);
maybeCompile(lform, m);
assert(lform.methodType().dropParameterTypes(0, 1)
.equals(m.getInvocationType().basicType()))
: Arrays.asList(m, m.getInvocationType().basicType(), lform, lform.methodType());
return lform;
}
private static LambdaForm preparedLambdaForm(MethodType mtype, int which) {
LambdaForm lform = mtype.form().cachedLambdaForm(which);
if (lform != null) return lform;
lform = makePreparedLambdaForm(mtype, which);
return mtype.form().setCachedLambdaForm(which, lform);
}
private static LambdaForm makePreparedLambdaForm(MethodType mtype, int which) {
boolean needsInit = (which == LF_INVSTATIC_INIT);
boolean doesAlloc = (which == LF_NEWINVSPECIAL);
String linkerName, lambdaName;
switch (which) {
case LF_INVVIRTUAL: linkerName = "linkToVirtual"; lambdaName = "DMH.invokeVirtual"; break;
case LF_INVSTATIC: linkerName = "linkToStatic"; lambdaName = "DMH.invokeStatic"; break;
case LF_INVSTATIC_INIT:linkerName = "linkToStatic"; lambdaName = "DMH.invokeStaticInit"; break;
case LF_INVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.invokeSpecial"; break;
case LF_INVINTERFACE: linkerName = "linkToInterface"; lambdaName = "DMH.invokeInterface"; break;
case LF_NEWINVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.newInvokeSpecial"; break;
default: throw new InternalError("which="+which);
}
MethodType mtypeWithArg = mtype.appendParameterTypes(MemberName.class);
if (doesAlloc)
mtypeWithArg = mtypeWithArg
.insertParameterTypes(0, Object.class) // insert newly allocated obj
.changeReturnType(void.class); // <init> returns void
MemberName linker = new MemberName(MethodHandle.class, linkerName, mtypeWithArg, REF_invokeStatic);
try {
linker = IMPL_NAMES.resolveOrFail(REF_invokeStatic, linker, null, NoSuchMethodException.class);
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
final int DMH_THIS = 0;
final int ARG_BASE = 1;
final int ARG_LIMIT = ARG_BASE + mtype.parameterCount();
int nameCursor = ARG_LIMIT;
final int NEW_OBJ = (doesAlloc ? nameCursor++ : -1);
final int GET_MEMBER = nameCursor++;
final int LINKER_CALL = nameCursor++;
Name[] names = arguments(nameCursor - ARG_LIMIT, mtype.invokerType());
assert(names.length == nameCursor);
if (doesAlloc) {
// names = { argx,y,z,... new C, init method }
names[NEW_OBJ] = new Name(NF_allocateInstance, names[DMH_THIS]);
names[GET_MEMBER] = new Name(NF_constructorMethod, names[DMH_THIS]);
} else if (needsInit) {
names[GET_MEMBER] = new Name(NF_internalMemberNameEnsureInit, names[DMH_THIS]);
} else {
names[GET_MEMBER] = new Name(NF_internalMemberName, names[DMH_THIS]);
}
Object[] outArgs = Arrays.copyOfRange(names, ARG_BASE, GET_MEMBER+1, Object[].class);
assert(outArgs[outArgs.length-1] == names[GET_MEMBER]); // look, shifted args!
int result = LambdaForm.LAST_RESULT;
if (doesAlloc) {
assert(outArgs[outArgs.length-2] == names[NEW_OBJ]); // got to move this one
System.arraycopy(outArgs, 0, outArgs, 1, outArgs.length-2);
outArgs[0] = names[NEW_OBJ];
result = NEW_OBJ;
}
names[LINKER_CALL] = new Name(linker, outArgs);
lambdaName += "_" + LambdaForm.basicTypeSignature(mtype);
LambdaForm lform = new LambdaForm(lambdaName, ARG_LIMIT, names, result);
// This is a tricky bit of code. Don't send it through the LF interpreter.
lform.compileToBytecode();
return lform;
}
private static void maybeCompile(LambdaForm lform, MemberName m) {
if (VerifyAccess.isSamePackage(m.getDeclaringClass(), MethodHandle.class))
// Help along bootstrapping...
lform.compileToBytecode();
}
/** Static wrapper for DirectMethodHandle.internalMemberName. */
@ForceInline
/*non-public*/ static Object internalMemberName(Object mh) {
return ((DirectMethodHandle)mh).member;
}
/** Static wrapper for DirectMethodHandle.internalMemberName.
* This one also forces initialization.
*/
/*non-public*/ static Object internalMemberNameEnsureInit(Object mh) {
DirectMethodHandle dmh = (DirectMethodHandle)mh;
dmh.ensureInitialized();
return dmh.member;
}
/*non-public*/ static
boolean shouldBeInitialized(MemberName member) {
switch (member.getReferenceKind()) {
case REF_invokeStatic:
case REF_getStatic:
case REF_putStatic:
case REF_newInvokeSpecial:
break;
default:
// No need to initialize the class on this kind of member.
return false;
}
Class<?> cls = member.getDeclaringClass();
if (cls == ValueConversions.class ||
cls == MethodHandleImpl.class ||
cls == Invokers.class) {
// These guys have lots of <clinit> DMH creation but we know
// the MHs will not be used until the system is booted.
return false;
}
if (VerifyAccess.isSamePackage(MethodHandle.class, cls) ||
VerifyAccess.isSamePackage(ValueConversions.class, cls)) {
// It is a system class. It is probably in the process of
// being initialized, but we will help it along just to be safe.
if (UNSAFE.shouldBeInitialized(cls)) {
UNSAFE.ensureClassInitialized(cls);
}
return false;
}
return UNSAFE.shouldBeInitialized(cls);
}
private static class EnsureInitialized extends ClassValue<WeakReference<Thread>> {
@Override
protected WeakReference<Thread> computeValue(Class<?> type) {
UNSAFE.ensureClassInitialized(type);
if (UNSAFE.shouldBeInitialized(type))
// If the previous call didn't block, this can happen.
// We are executing inside <clinit>.
return new WeakReference<>(Thread.currentThread());
return null;
}
static final EnsureInitialized INSTANCE = new EnsureInitialized();
}
private void ensureInitialized() {
if (checkInitialized(member)) {
// The coast is clear. Delete the <clinit> barrier.
if (member.isField())
updateForm(preparedFieldLambdaForm(member));
else
updateForm(preparedLambdaForm(member));
}
}
private static boolean checkInitialized(MemberName member) {
Class<?> defc = member.getDeclaringClass();
WeakReference<Thread> ref = EnsureInitialized.INSTANCE.get(defc);
if (ref == null) {
return true; // the final state
}
Thread clinitThread = ref.get();
// Somebody may still be running defc.<clinit>.
if (clinitThread == Thread.currentThread()) {
// If anybody is running defc.<clinit>, it is this thread.
if (UNSAFE.shouldBeInitialized(defc))
// Yes, we are running it; keep the barrier for now.
return false;
} else {
// We are in a random thread. Block.
UNSAFE.ensureClassInitialized(defc);
}
assert(!UNSAFE.shouldBeInitialized(defc));
// put it into the final state
EnsureInitialized.INSTANCE.remove(defc);
return true;
}
/*non-public*/ static void ensureInitialized(Object mh) {
((DirectMethodHandle)mh).ensureInitialized();
}
/** This subclass handles constructor references. */
static class Constructor extends DirectMethodHandle {
final MemberName initMethod;
final Class<?> instanceClass;
private Constructor(MethodType mtype, LambdaForm form, MemberName constructor,
MemberName initMethod, Class<?> instanceClass) {
super(mtype, form, constructor);
this.initMethod = initMethod;
this.instanceClass = instanceClass;
assert(initMethod.isResolved());
}
}
/*non-public*/ static Object constructorMethod(Object mh) {
Constructor dmh = (Constructor)mh;
return dmh.initMethod;
}
/*non-public*/ static Object allocateInstance(Object mh) throws InstantiationException {
Constructor dmh = (Constructor)mh;
return UNSAFE.allocateInstance(dmh.instanceClass);
}
/** This subclass handles non-static field references. */
static class Accessor extends DirectMethodHandle {
final Class<?> fieldType;
final int fieldOffset;
private Accessor(MethodType mtype, LambdaForm form, MemberName member,
int fieldOffset) {
super(mtype, form, member);
this.fieldType = member.getFieldType();
this.fieldOffset = fieldOffset;
}
@Override Object checkCast(Object obj) {
return fieldType.cast(obj);
}
}
@ForceInline
/*non-public*/ static long fieldOffset(Object accessorObj) {
// Note: We return a long because that is what Unsafe.getObject likes.
// We store a plain int because it is more compact.
return ((Accessor)accessorObj).fieldOffset;
}
@ForceInline
/*non-public*/ static Object checkBase(Object obj) {
// Note that the object's class has already been verified,
// since the parameter type of the Accessor method handle
// is either member.getDeclaringClass or a subclass.
// This was verified in DirectMethodHandle.make.
// Therefore, the only remaining check is for null.
// Since this check is *not* guaranteed by Unsafe.getInt
// and its siblings, we need to make an explicit one here.
obj.getClass(); // maybe throw NPE
return obj;
}
/** This subclass handles static field references. */
static class StaticAccessor extends DirectMethodHandle {
final private Class<?> fieldType;
final private Object staticBase;
final private long staticOffset;
private StaticAccessor(MethodType mtype, LambdaForm form, MemberName member,
Object staticBase, long staticOffset) {
super(mtype, form, member);
this.fieldType = member.getFieldType();
this.staticBase = staticBase;
this.staticOffset = staticOffset;
}
@Override Object checkCast(Object obj) {
return fieldType.cast(obj);
}
}
@ForceInline
/*non-public*/ static Object nullCheck(Object obj) {
obj.getClass();
return obj;
}
@ForceInline
/*non-public*/ static Object staticBase(Object accessorObj) {
return ((StaticAccessor)accessorObj).staticBase;
}
@ForceInline
/*non-public*/ static long staticOffset(Object accessorObj) {
return ((StaticAccessor)accessorObj).staticOffset;
}
@ForceInline
/*non-public*/ static Object checkCast(Object mh, Object obj) {
return ((DirectMethodHandle) mh).checkCast(obj);
}
Object checkCast(Object obj) {
return member.getReturnType().cast(obj);
}
// Caching machinery for field accessors:
private static byte
AF_GETFIELD = 0,
AF_PUTFIELD = 1,
AF_GETSTATIC = 2,
AF_PUTSTATIC = 3,
AF_GETSTATIC_INIT = 4,
AF_PUTSTATIC_INIT = 5,
AF_LIMIT = 6;
// Enumerate the different field kinds using Wrapper,
// with an extra case added for checked references.
private static int
FT_LAST_WRAPPER = Wrapper.values().length-1,
FT_UNCHECKED_REF = Wrapper.OBJECT.ordinal(),
FT_CHECKED_REF = FT_LAST_WRAPPER+1,
FT_LIMIT = FT_LAST_WRAPPER+2;
private static int afIndex(byte formOp, boolean isVolatile, int ftypeKind) {
return ((formOp * FT_LIMIT * 2)
+ (isVolatile ? FT_LIMIT : 0)
+ ftypeKind);
}
private static final LambdaForm[] ACCESSOR_FORMS
= new LambdaForm[afIndex(AF_LIMIT, false, 0)];
private static int ftypeKind(Class<?> ftype) {
if (ftype.isPrimitive())
return Wrapper.forPrimitiveType(ftype).ordinal();
else if (VerifyType.isNullReferenceConversion(Object.class, ftype))
return FT_UNCHECKED_REF;
else
return FT_CHECKED_REF;
}
/**
* Create a LF which can access the given field.
* Cache and share this structure among all fields with
* the same basicType and refKind.
*/
private static LambdaForm preparedFieldLambdaForm(MemberName m) {
Class<?> ftype = m.getFieldType();
boolean isVolatile = m.isVolatile();
byte formOp;
switch (m.getReferenceKind()) {
case REF_getField: formOp = AF_GETFIELD; break;
case REF_putField: formOp = AF_PUTFIELD; break;
case REF_getStatic: formOp = AF_GETSTATIC; break;
case REF_putStatic: formOp = AF_PUTSTATIC; break;
default: throw new InternalError(m.toString());
}
if (shouldBeInitialized(m)) {
// precompute the barrier-free version:
preparedFieldLambdaForm(formOp, isVolatile, ftype);
assert((AF_GETSTATIC_INIT - AF_GETSTATIC) ==
(AF_PUTSTATIC_INIT - AF_PUTSTATIC));
formOp += (AF_GETSTATIC_INIT - AF_GETSTATIC);
}
LambdaForm lform = preparedFieldLambdaForm(formOp, isVolatile, ftype);
maybeCompile(lform, m);
assert(lform.methodType().dropParameterTypes(0, 1)
.equals(m.getInvocationType().basicType()))
: Arrays.asList(m, m.getInvocationType().basicType(), lform, lform.methodType());
return lform;
}
private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatile, Class<?> ftype) {
int afIndex = afIndex(formOp, isVolatile, ftypeKind(ftype));
LambdaForm lform = ACCESSOR_FORMS[afIndex];
if (lform != null) return lform;
lform = makePreparedFieldLambdaForm(formOp, isVolatile, ftypeKind(ftype));
ACCESSOR_FORMS[afIndex] = lform; // don't bother with a CAS
return lform;
}
private static LambdaForm makePreparedFieldLambdaForm(byte formOp, boolean isVolatile, int ftypeKind) {
boolean isGetter = (formOp & 1) == (AF_GETFIELD & 1);
boolean isStatic = (formOp >= AF_GETSTATIC);
boolean needsInit = (formOp >= AF_GETSTATIC_INIT);
boolean needsCast = (ftypeKind == FT_CHECKED_REF);
Wrapper fw = (needsCast ? Wrapper.OBJECT : Wrapper.values()[ftypeKind]);
Class<?> ft = fw.primitiveType();
assert(ftypeKind(needsCast ? String.class : ft) == ftypeKind);
String tname = fw.primitiveSimpleName();
String ctname = Character.toUpperCase(tname.charAt(0)) + tname.substring(1);
if (isVolatile) ctname += "Volatile";
String getOrPut = (isGetter ? "get" : "put");
String linkerName = (getOrPut + ctname); // getObject, putIntVolatile, etc.
MethodType linkerType;
if (isGetter)
linkerType = MethodType.methodType(ft, Object.class, long.class);
else
linkerType = MethodType.methodType(void.class, Object.class, long.class, ft);
MemberName linker = new MemberName(Unsafe.class, linkerName, linkerType, REF_invokeVirtual);
try {
linker = IMPL_NAMES.resolveOrFail(REF_invokeVirtual, linker, null, NoSuchMethodException.class);
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
// What is the external type of the lambda form?
MethodType mtype;
if (isGetter)
mtype = MethodType.methodType(ft);
else
mtype = MethodType.methodType(void.class, ft);
mtype = mtype.basicType(); // erase short to int, etc.
if (!isStatic)
mtype = mtype.insertParameterTypes(0, Object.class);
final int DMH_THIS = 0;
final int ARG_BASE = 1;
final int ARG_LIMIT = ARG_BASE + mtype.parameterCount();
// if this is for non-static access, the base pointer is stored at this index:
final int OBJ_BASE = isStatic ? -1 : ARG_BASE;
// if this is for write access, the value to be written is stored at this index:
final int SET_VALUE = isGetter ? -1 : ARG_LIMIT - 1;
int nameCursor = ARG_LIMIT;
final int F_HOLDER = (isStatic ? nameCursor++ : -1); // static base if any
final int F_OFFSET = nameCursor++; // Either static offset or field offset.
final int OBJ_CHECK = (OBJ_BASE >= 0 ? nameCursor++ : -1);
final int INIT_BAR = (needsInit ? nameCursor++ : -1);
final int PRE_CAST = (needsCast && !isGetter ? nameCursor++ : -1);
final int LINKER_CALL = nameCursor++;
final int POST_CAST = (needsCast && isGetter ? nameCursor++ : -1);
final int RESULT = nameCursor-1; // either the call or the cast
Name[] names = arguments(nameCursor - ARG_LIMIT, mtype.invokerType());
if (needsInit)
names[INIT_BAR] = new Name(NF_ensureInitialized, names[DMH_THIS]);
if (needsCast && !isGetter)
names[PRE_CAST] = new Name(NF_checkCast, names[DMH_THIS], names[SET_VALUE]);
Object[] outArgs = new Object[1 + linkerType.parameterCount()];
assert(outArgs.length == (isGetter ? 3 : 4));
outArgs[0] = UNSAFE;
if (isStatic) {
outArgs[1] = names[F_HOLDER] = new Name(NF_staticBase, names[DMH_THIS]);
outArgs[2] = names[F_OFFSET] = new Name(NF_staticOffset, names[DMH_THIS]);
} else {
outArgs[1] = names[OBJ_CHECK] = new Name(NF_checkBase, names[OBJ_BASE]);
outArgs[2] = names[F_OFFSET] = new Name(NF_fieldOffset, names[DMH_THIS]);
}
if (!isGetter) {
outArgs[3] = (needsCast ? names[PRE_CAST] : names[SET_VALUE]);
}
for (Object a : outArgs) assert(a != null);
names[LINKER_CALL] = new Name(linker, outArgs);
if (needsCast && isGetter)
names[POST_CAST] = new Name(NF_checkCast, names[DMH_THIS], names[LINKER_CALL]);
for (Name n : names) assert(n != null);
String fieldOrStatic = (isStatic ? "Static" : "Field");
String lambdaName = (linkerName + fieldOrStatic); // significant only for debugging
if (needsCast) lambdaName += "Cast";
if (needsInit) lambdaName += "Init";
return new LambdaForm(lambdaName, ARG_LIMIT, names, RESULT);
}
private static final NamedFunction
NF_internalMemberName,
NF_internalMemberNameEnsureInit,
NF_ensureInitialized,
NF_fieldOffset,
NF_checkBase,
NF_staticBase,
NF_staticOffset,
NF_checkCast,
NF_allocateInstance,
NF_constructorMethod;
static {
try {
NamedFunction nfs[] = {
NF_internalMemberName = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("internalMemberName", Object.class)),
NF_internalMemberNameEnsureInit = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("internalMemberNameEnsureInit", Object.class)),
NF_ensureInitialized = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("ensureInitialized", Object.class)),
NF_fieldOffset = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("fieldOffset", Object.class)),
NF_checkBase = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("checkBase", Object.class)),
NF_staticBase = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("staticBase", Object.class)),
NF_staticOffset = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("staticOffset", Object.class)),
NF_checkCast = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("checkCast", Object.class, Object.class)),
NF_allocateInstance = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("allocateInstance", Object.class)),
NF_constructorMethod = new NamedFunction(DirectMethodHandle.class
.getDeclaredMethod("constructorMethod", Object.class))
};
for (NamedFunction nf : nfs) {
// Each nf must be statically invocable or we get tied up in our bootstraps.
assert(InvokerBytecodeGenerator.isStaticallyInvocable(nf.member)) : nf;
nf.resolve();
}
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,26 +25,13 @@
package java.lang.invoke; package java.lang.invoke;
import static java.lang.invoke.MethodHandleNatives.Constants.*; import java.lang.annotation.*;
/** /**
* This method handle is used to optionally provide a count of how * Internal marker for some methods in the JSR 292 implementation.
* many times it was invoked.
*
* @author never
*/ */
class CountingMethodHandle extends AdapterMethodHandle { /*non-public*/
private int vmcount; @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
private CountingMethodHandle(MethodHandle target) { @interface DontInline {
super(target, target.type(), AdapterMethodHandle.makeConv(OP_RETYPE_ONLY));
}
/** Wrap the incoming MethodHandle in a CountingMethodHandle if they are enabled */
static MethodHandle wrap(MethodHandle mh) {
if (MethodHandleNatives.COUNT_GWT) {
return new CountingMethodHandle(mh);
}
return mh;
}
} }

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.invoke;
import java.lang.annotation.*;
/**
* Internal marker for some methods in the JSR 292 implementation.
*/
/*non-public*/
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
@interface ForceInline {
}

File diff suppressed because it is too large Load diff

View file

@ -25,8 +25,11 @@
package java.lang.invoke; package java.lang.invoke;
import java.util.Arrays;
import sun.invoke.empty.Empty; import sun.invoke.empty.Empty;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
import static java.lang.invoke.LambdaForm.*;
/** /**
* Construction and caching of often-used invokers. * Construction and caching of often-used invokers.
@ -36,11 +39,15 @@ class Invokers {
// exact type (sans leading taget MH) for the outgoing call // exact type (sans leading taget MH) for the outgoing call
private final MethodType targetType; private final MethodType targetType;
// FIXME: Get rid of the invokers that are not useful.
// exact invoker for the outgoing call // exact invoker for the outgoing call
private /*lazy*/ MethodHandle exactInvoker; private /*lazy*/ MethodHandle exactInvoker;
// erased (partially untyped but with primitives) invoker for the outgoing call // erased (partially untyped but with primitives) invoker for the outgoing call
// FIXME: get rid of
private /*lazy*/ MethodHandle erasedInvoker; private /*lazy*/ MethodHandle erasedInvoker;
// FIXME: get rid of
/*lazy*/ MethodHandle erasedInvokerWithDrops; // for InvokeGeneric /*lazy*/ MethodHandle erasedInvokerWithDrops; // for InvokeGeneric
// general invoker for the outgoing call // general invoker for the outgoing call
@ -63,14 +70,13 @@ class Invokers {
this.spreadInvokers = new MethodHandle[targetType.parameterCount()+1]; this.spreadInvokers = new MethodHandle[targetType.parameterCount()+1];
} }
/*non-public*/ static MethodType invokerType(MethodType targetType) {
return targetType.insertParameterTypes(0, MethodHandle.class);
}
/*non-public*/ MethodHandle exactInvoker() { /*non-public*/ MethodHandle exactInvoker() {
MethodHandle invoker = exactInvoker; MethodHandle invoker = exactInvoker;
if (invoker != null) return invoker; if (invoker != null) return invoker;
invoker = lookupInvoker("invokeExact"); MethodType mtype = targetType;
LambdaForm lform = invokeForm(mtype, MethodTypeForm.LF_EX_INVOKER);
invoker = BoundMethodHandle.bindSingle(mtype.invokerType(), lform, mtype);
assert(checkInvoker(invoker));
exactInvoker = invoker; exactInvoker = invoker;
return invoker; return invoker;
} }
@ -78,29 +84,50 @@ class Invokers {
/*non-public*/ MethodHandle generalInvoker() { /*non-public*/ MethodHandle generalInvoker() {
MethodHandle invoker = generalInvoker; MethodHandle invoker = generalInvoker;
if (invoker != null) return invoker; if (invoker != null) return invoker;
invoker = lookupInvoker("invoke"); MethodType mtype = targetType;
prepareForGenericCall(mtype);
LambdaForm lform = invokeForm(mtype, MethodTypeForm.LF_GEN_INVOKER);
invoker = BoundMethodHandle.bindSingle(mtype.invokerType(), lform, mtype);
assert(checkInvoker(invoker));
generalInvoker = invoker; generalInvoker = invoker;
return invoker; return invoker;
} }
private MethodHandle lookupInvoker(String name) { /*non-public*/ MethodHandle makeBasicInvoker() {
MethodHandle invoker; MethodHandle invoker = DirectMethodHandle.make(invokeBasicMethod(targetType));
try { assert(targetType == targetType.basicType());
invoker = IMPL_LOOKUP.findVirtual(MethodHandle.class, name, targetType); // Note: This is not cached here. It is cached by the calling MethodTypeForm.
} catch (ReflectiveOperationException ex) { assert(checkInvoker(invoker));
throw new InternalError("JVM cannot find invoker for "+targetType, ex);
}
assert(invokerType(targetType) == invoker.type());
assert(!invoker.isVarargsCollector());
return invoker; return invoker;
} }
static MemberName invokeBasicMethod(MethodType type) {
String name = "invokeBasic";
try {
//Lookup.findVirtual(MethodHandle.class, name, type);
return IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, MethodHandle.class, name, type);
} catch (ReflectiveOperationException ex) {
throw new InternalError("JVM cannot find invoker for "+type, ex);
}
}
private boolean checkInvoker(MethodHandle invoker) {
assert(targetType.invokerType().equals(invoker.type()))
: java.util.Arrays.asList(targetType, targetType.invokerType(), invoker);
assert(invoker.internalMemberName() == null ||
invoker.internalMemberName().getMethodType().equals(targetType));
assert(!invoker.isVarargsCollector());
return true;
}
// FIXME: get rid of
/*non-public*/ MethodHandle erasedInvoker() { /*non-public*/ MethodHandle erasedInvoker() {
MethodHandle xinvoker = exactInvoker(); MethodHandle xinvoker = exactInvoker();
MethodHandle invoker = erasedInvoker; MethodHandle invoker = erasedInvoker;
if (invoker != null) return invoker; if (invoker != null) return invoker;
MethodType erasedType = targetType.erase(); MethodType erasedType = targetType.erase();
invoker = xinvoker.asType(invokerType(erasedType)); invoker = xinvoker.asType(erasedType.invokerType());
erasedInvoker = invoker; erasedInvoker = invoker;
return invoker; return invoker;
} }
@ -118,7 +145,7 @@ class Invokers {
/*non-public*/ MethodHandle varargsInvoker() { /*non-public*/ MethodHandle varargsInvoker() {
MethodHandle vaInvoker = varargsInvoker; MethodHandle vaInvoker = varargsInvoker;
if (vaInvoker != null) return vaInvoker; if (vaInvoker != null) return vaInvoker;
vaInvoker = spreadInvoker(0).asType(invokerType(MethodType.genericMethodType(0, true))); vaInvoker = spreadInvoker(0).asType(MethodType.genericMethodType(0, true).invokerType());
varargsInvoker = vaInvoker; varargsInvoker = vaInvoker;
return vaInvoker; return vaInvoker;
} }
@ -137,16 +164,18 @@ class Invokers {
uninitializedCallSite = invoker; uninitializedCallSite = invoker;
return invoker; return invoker;
} }
if (THROW_UCS == null) { invoker = THROW_UCS;
if (invoker == null) {
try { try {
THROW_UCS = IMPL_LOOKUP THROW_UCS = invoker = IMPL_LOOKUP
.findStatic(CallSite.class, "uninitializedCallSite", .findStatic(CallSite.class, "uninitializedCallSite",
MethodType.methodType(Empty.class)); MethodType.methodType(Empty.class));
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
} }
invoker = AdapterMethodHandle.makeRetypeRaw(targetType, THROW_UCS); invoker = MethodHandles.explicitCastArguments(invoker, MethodType.methodType(targetType.returnType()));
invoker = invoker.dropArguments(targetType, 0, targetType.parameterCount());
assert(invoker.type().equals(targetType)); assert(invoker.type().equals(targetType));
uninitializedCallSite = invoker; uninitializedCallSite = invoker;
return invoker; return invoker;
@ -155,4 +184,208 @@ class Invokers {
public String toString() { public String toString() {
return "Invokers"+targetType; return "Invokers"+targetType;
} }
private static MethodType fixMethodType(Class<?> callerClass, Object type) {
if (type instanceof MethodType)
return (MethodType) type;
else
return MethodType.fromMethodDescriptorString((String)type, callerClass.getClassLoader());
}
static MemberName exactInvokerMethod(Class<?> callerClass, Object type, Object[] appendixResult) {
MethodType mtype = fixMethodType(callerClass, type);
LambdaForm lform = invokeForm(mtype, MethodTypeForm.LF_EX_LINKER);
appendixResult[0] = mtype;
return lform.vmentry;
}
static MemberName genericInvokerMethod(Class<?> callerClass, Object type, Object[] appendixResult) {
MethodType mtype = fixMethodType(callerClass, type);
LambdaForm lform = invokeForm(mtype, MethodTypeForm.LF_GEN_LINKER);
prepareForGenericCall(mtype);
appendixResult[0] = mtype;
return lform.vmentry;
}
private static LambdaForm invokeForm(MethodType mtype, int which) {
mtype = mtype.basicType(); // normalize Z to I, String to Object, etc.
boolean isLinker, isGeneric;
String debugName;
switch (which) {
case MethodTypeForm.LF_EX_LINKER: isLinker = true; isGeneric = false; debugName = "invokeExact_MT"; break;
case MethodTypeForm.LF_EX_INVOKER: isLinker = false; isGeneric = false; debugName = "exactInvoker"; break;
case MethodTypeForm.LF_GEN_LINKER: isLinker = true; isGeneric = true; debugName = "invoke_MT"; break;
case MethodTypeForm.LF_GEN_INVOKER: isLinker = false; isGeneric = true; debugName = "invoker"; break;
default: throw new InternalError();
}
LambdaForm lform = mtype.form().cachedLambdaForm(which);
if (lform != null) return lform;
// exactInvokerForm (Object,Object)Object
// link with java.lang.invoke.MethodHandle.invokeBasic(MethodHandle,Object,Object)Object/invokeSpecial
final int THIS_MH = 0;
final int CALL_MH = THIS_MH + (isLinker ? 0 : 1);
final int ARG_BASE = CALL_MH + 1;
final int OUTARG_LIMIT = ARG_BASE + mtype.parameterCount();
final int INARG_LIMIT = OUTARG_LIMIT + (isLinker ? 1 : 0);
int nameCursor = OUTARG_LIMIT;
final int MTYPE_ARG = nameCursor++; // might be last in-argument
final int CHECK_TYPE = nameCursor++;
final int LINKER_CALL = nameCursor++;
MethodType invokerFormType = mtype.invokerType();
if (isLinker) {
invokerFormType = invokerFormType.appendParameterTypes(MemberName.class);
} else {
invokerFormType = invokerFormType.invokerType();
}
Name[] names = arguments(nameCursor - INARG_LIMIT, invokerFormType);
assert(names.length == nameCursor);
if (MTYPE_ARG >= INARG_LIMIT) {
assert(names[MTYPE_ARG] == null);
names[MTYPE_ARG] = BoundMethodHandle.getSpeciesData("L").getterName(names[THIS_MH], 0);
// else if isLinker, then MTYPE is passed in from the caller (e.g., the JVM)
}
// Make the final call. If isGeneric, then prepend the result of type checking.
MethodType outCallType;
Object[] outArgs;
if (!isGeneric) {
names[CHECK_TYPE] = new Name(NF_checkExactType, names[CALL_MH], names[MTYPE_ARG]);
// mh.invokeExact(a*):R => checkExactType(mh, TYPEOF(a*:R)); mh.invokeBasic(a*)
outArgs = Arrays.copyOfRange(names, CALL_MH, OUTARG_LIMIT, Object[].class);
outCallType = mtype;
} else {
names[CHECK_TYPE] = new Name(NF_checkGenericType, names[CALL_MH], names[MTYPE_ARG]);
// mh.invokeGeneric(a*):R =>
// let mt=TYPEOF(a*:R), gamh=checkGenericType(mh, mt);
// gamh.invokeBasic(mt, mh, a*)
final int PREPEND_GAMH = 0, PREPEND_MT = 1, PREPEND_COUNT = 2;
outArgs = Arrays.copyOfRange(names, CALL_MH, OUTARG_LIMIT + PREPEND_COUNT, Object[].class);
// prepend arguments:
System.arraycopy(outArgs, 0, outArgs, PREPEND_COUNT, outArgs.length - PREPEND_COUNT);
outArgs[PREPEND_GAMH] = names[CHECK_TYPE];
outArgs[PREPEND_MT] = names[MTYPE_ARG];
outCallType = mtype.insertParameterTypes(0, MethodType.class, MethodHandle.class);
}
names[LINKER_CALL] = new Name(invokeBasicMethod(outCallType), outArgs);
lform = new LambdaForm(debugName, INARG_LIMIT, names);
if (isLinker)
lform.compileToBytecode(); // JVM needs a real methodOop
lform = mtype.form().setCachedLambdaForm(which, lform);
return lform;
}
/*non-public*/ static
WrongMethodTypeException newWrongMethodTypeException(MethodType actual, MethodType expected) {
// FIXME: merge with JVM logic for throwing WMTE
return new WrongMethodTypeException("expected "+expected+" but found "+actual);
}
/** Static definition of MethodHandle.invokeExact checking code. */
/*non-public*/ static
@ForceInline
void checkExactType(Object mhObj, Object expectedObj) {
MethodHandle mh = (MethodHandle) mhObj;
MethodType expected = (MethodType) expectedObj;
MethodType actual = mh.type();
if (actual != expected)
throw newWrongMethodTypeException(expected, actual);
}
/** Static definition of MethodHandle.invokeGeneric checking code. */
/*non-public*/ static
@ForceInline
Object checkGenericType(Object mhObj, Object expectedObj) {
MethodHandle mh = (MethodHandle) mhObj;
MethodType expected = (MethodType) expectedObj;
//MethodType actual = mh.type();
MethodHandle gamh = expected.form().genericInvoker;
if (gamh != null) return gamh;
return prepareForGenericCall(expected);
}
/**
* Returns an adapter GA for invoking a MH with type adjustments.
* The MethodType of the generic invocation site is prepended to MH
* and its arguments as follows:
* {@code (R)MH.invoke(A*) => GA.invokeBasic(TYPEOF<A*,R>, MH, A*)}
*/
/*non-public*/ static MethodHandle prepareForGenericCall(MethodType mtype) {
// force any needed adapters to be preconstructed
MethodTypeForm form = mtype.form();
MethodHandle gamh = form.genericInvoker;
if (gamh != null) return gamh;
try {
// Trigger adapter creation.
gamh = InvokeGeneric.generalInvokerOf(form.erasedType);
form.genericInvoker = gamh;
return gamh;
} catch (Exception ex) {
throw new InternalError("Exception while resolving inexact invoke", ex);
}
}
static MemberName linkToCallSiteMethod(MethodType mtype) {
LambdaForm lform = callSiteForm(mtype);
return lform.vmentry;
}
private static LambdaForm callSiteForm(MethodType mtype) {
mtype = mtype.basicType(); // normalize Z to I, String to Object, etc.
LambdaForm lform = mtype.form().cachedLambdaForm(MethodTypeForm.LF_CS_LINKER);
if (lform != null) return lform;
// exactInvokerForm (Object,Object)Object
// link with java.lang.invoke.MethodHandle.invokeBasic(MethodHandle,Object,Object)Object/invokeSpecial
final int ARG_BASE = 0;
final int OUTARG_LIMIT = ARG_BASE + mtype.parameterCount();
final int INARG_LIMIT = OUTARG_LIMIT + 1;
int nameCursor = OUTARG_LIMIT;
final int CSITE_ARG = nameCursor++; // the last in-argument
final int CALL_MH = nameCursor++; // result of getTarget
final int LINKER_CALL = nameCursor++;
MethodType invokerFormType = mtype.appendParameterTypes(CallSite.class);
Name[] names = arguments(nameCursor - INARG_LIMIT, invokerFormType);
assert(names.length == nameCursor);
assert(names[CSITE_ARG] != null);
names[CALL_MH] = new Name(NF_getCallSiteTarget, names[CSITE_ARG]);
// (site.)invokedynamic(a*):R => mh = site.getTarget(); mh.invokeBasic(a*)
final int PREPEND_MH = 0, PREPEND_COUNT = 1;
Object[] outArgs = Arrays.copyOfRange(names, ARG_BASE, OUTARG_LIMIT + PREPEND_COUNT, Object[].class);
// prepend MH argument:
System.arraycopy(outArgs, 0, outArgs, PREPEND_COUNT, outArgs.length - PREPEND_COUNT);
outArgs[PREPEND_MH] = names[CALL_MH];
names[LINKER_CALL] = new Name(invokeBasicMethod(mtype), outArgs);
lform = new LambdaForm("linkToCallSite", INARG_LIMIT, names);
lform.compileToBytecode(); // JVM needs a real methodOop
lform = mtype.form().setCachedLambdaForm(MethodTypeForm.LF_CS_LINKER, lform);
return lform;
}
/** Static definition of MethodHandle.invokeGeneric checking code. */
/*non-public*/ static
@ForceInline
Object getCallSiteTarget(Object site) {
return ((CallSite)site).getTarget();
}
// Local constant functions:
private static final NamedFunction NF_checkExactType;
private static final NamedFunction NF_checkGenericType;
private static final NamedFunction NF_getCallSiteTarget;
static {
try {
NF_checkExactType = new NamedFunction(Invokers.class
.getDeclaredMethod("checkExactType", Object.class, Object.class));
NF_checkGenericType = new NamedFunction(Invokers.class
.getDeclaredMethod("checkGenericType", Object.class, Object.class));
NF_getCallSiteTarget = new NamedFunction(Invokers.class
.getDeclaredMethod("getCallSiteTarget", Object.class));
NF_checkExactType.resolve();
NF_checkGenericType.resolve();
NF_getCallSiteTarget.resolve();
// bound
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
}
} }

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,8 @@
package java.lang.invoke; package java.lang.invoke;
import sun.invoke.util.BytecodeDescriptor; import sun.invoke.util.BytecodeDescriptor;
import sun.invoke.util.VerifyAccess;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -38,6 +40,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandleStatics.*;
import java.util.Objects;
/** /**
* A {@code MemberName} is a compact symbolic datum which fully characterizes * A {@code MemberName} is a compact symbolic datum which fully characterizes
@ -71,19 +74,14 @@ import static java.lang.invoke.MethodHandleStatics.*;
private String name; // may be null if not yet materialized private String name; // may be null if not yet materialized
private Object type; // may be null if not yet materialized private Object type; // may be null if not yet materialized
private int flags; // modifier bits; see reflect.Modifier private int flags; // modifier bits; see reflect.Modifier
//@Injected JVM_Method* vmtarget;
private Object vmtarget; // VM-specific target value //@Injected int vmindex;
private int vmindex; // method index within class or interface private Object resolution; // if null, this guy is resolved
{ vmindex = VM_INDEX_UNINITIALIZED; }
/** Return the declaring class of this member. /** Return the declaring class of this member.
* In the case of a bare name and type, the declaring class will be null. * In the case of a bare name and type, the declaring class will be null.
*/ */
public Class<?> getDeclaringClass() { public Class<?> getDeclaringClass() {
if (clazz == null && isResolved()) {
expandFromVM();
}
return clazz; return clazz;
} }
@ -105,6 +103,16 @@ import static java.lang.invoke.MethodHandleStatics.*;
return name; return name;
} }
public MethodType getMethodOrFieldType() {
if (isInvocable())
return getMethodType();
if (isGetter())
return MethodType.methodType(getFieldType());
if (isSetter())
return MethodType.methodType(void.class, getFieldType());
throw new InternalError("not a method or field: "+this);
}
/** Return the declared type of this member, which /** Return the declared type of this member, which
* must be a method or constructor. * must be a method or constructor.
*/ */
@ -140,9 +148,11 @@ import static java.lang.invoke.MethodHandleStatics.*;
* a reference to declaring class. For static methods, it is the same as the declared type. * a reference to declaring class. For static methods, it is the same as the declared type.
*/ */
public MethodType getInvocationType() { public MethodType getInvocationType() {
MethodType itype = getMethodType(); MethodType itype = getMethodOrFieldType();
if (isConstructor() && getReferenceKind() == REF_newInvokeSpecial)
return itype.changeReturnType(clazz);
if (!isStatic()) if (!isStatic())
itype = itype.insertParameterTypes(0, clazz); return itype.insertParameterTypes(0, clazz);
return itype; return itype;
} }
@ -208,9 +218,98 @@ import static java.lang.invoke.MethodHandleStatics.*;
return (flags & RECOGNIZED_MODIFIERS); return (flags & RECOGNIZED_MODIFIERS);
} }
/** Return the reference kind of this member, or zero if none.
*/
public byte getReferenceKind() {
return (byte) ((flags >>> MN_REFERENCE_KIND_SHIFT) & MN_REFERENCE_KIND_MASK);
}
private boolean referenceKindIsConsistent() {
byte refKind = getReferenceKind();
if (refKind == REF_NONE) return isType();
if (isField()) {
assert(staticIsConsistent());
assert(MethodHandleNatives.refKindIsField(refKind));
} else if (isConstructor()) {
assert(refKind == REF_newInvokeSpecial || refKind == REF_invokeSpecial);
} else if (isMethod()) {
assert(staticIsConsistent());
assert(MethodHandleNatives.refKindIsMethod(refKind));
if (clazz.isInterface())
assert(refKind == REF_invokeInterface ||
refKind == REF_invokeVirtual && isObjectPublicMethod());
} else {
assert(false);
}
return true;
}
private boolean isObjectPublicMethod() {
if (clazz == Object.class) return true;
MethodType mtype = getMethodType();
if (name.equals("toString") && mtype.returnType() == String.class && mtype.parameterCount() == 0)
return true;
if (name.equals("hashCode") && mtype.returnType() == int.class && mtype.parameterCount() == 0)
return true;
if (name.equals("equals") && mtype.returnType() == boolean.class && mtype.parameterCount() == 1 && mtype.parameterType(0) == Object.class)
return true;
return false;
}
/*non-public*/ boolean referenceKindIsConsistentWith(int originalRefKind) {
int refKind = getReferenceKind();
if (refKind == originalRefKind) return true;
switch (originalRefKind) {
case REF_invokeInterface:
// Looking up an interface method, can get (e.g.) Object.hashCode
assert(refKind == REF_invokeVirtual ||
refKind == REF_invokeSpecial) : this;
return true;
case REF_invokeVirtual:
case REF_newInvokeSpecial:
// Looked up a virtual, can get (e.g.) final String.hashCode.
assert(refKind == REF_invokeSpecial) : this;
return true;
}
assert(false) : this;
return true;
}
private boolean staticIsConsistent() {
byte refKind = getReferenceKind();
return MethodHandleNatives.refKindIsStatic(refKind) == isStatic() || getModifiers() == 0;
}
private boolean vminfoIsConsistent() {
byte refKind = getReferenceKind();
assert(isResolved()); // else don't call
Object vminfo = MethodHandleNatives.getMemberVMInfo(this);
assert(vminfo instanceof Object[]);
long vmindex = (Long) ((Object[])vminfo)[0];
Object vmtarget = ((Object[])vminfo)[1];
if (MethodHandleNatives.refKindIsField(refKind)) {
assert(vmindex >= 0) : vmindex + ":" + this;
assert(vmtarget instanceof Class);
} else {
if (MethodHandleNatives.refKindDoesDispatch(refKind))
assert(vmindex >= 0) : vmindex + ":" + this;
else
assert(vmindex < 0) : vmindex;
assert(vmtarget instanceof MemberName) : vmtarget + " in " + this;
}
return true;
}
private MemberName changeReferenceKind(byte refKind, byte oldKind) {
assert(getReferenceKind() == oldKind);
assert(MethodHandleNatives.refKindIsValid(refKind));
flags += (((int)refKind - oldKind) << MN_REFERENCE_KIND_SHIFT);
// if (isConstructor() && refKind != REF_newInvokeSpecial)
// flags += (IS_METHOD - IS_CONSTRUCTOR);
// else if (refKind == REF_newInvokeSpecial && isMethod())
// flags += (IS_CONSTRUCTOR - IS_METHOD);
return this;
}
private void setFlags(int flags) { private void setFlags(int flags) {
this.flags = flags; this.flags = flags;
assert(testAnyFlags(ALL_KINDS)); assert(testAnyFlags(ALL_KINDS));
assert(referenceKindIsConsistent());
} }
private boolean testFlags(int mask, int value) { private boolean testFlags(int mask, int value) {
@ -223,6 +322,17 @@ import static java.lang.invoke.MethodHandleStatics.*;
return !testFlags(mask, 0); return !testFlags(mask, 0);
} }
/** Utility method to query if this member is a method handle invocation (invoke or invokeExact). */
public boolean isMethodHandleInvoke() {
final int bits = Modifier.NATIVE | Modifier.FINAL;
final int negs = Modifier.STATIC;
if (testFlags(bits | negs, bits) &&
clazz == MethodHandle.class) {
return name.equals("invoke") || name.equals("invokeExact");
}
return false;
}
/** Utility method to query the modifier flags of this member. */ /** Utility method to query the modifier flags of this member. */
public boolean isStatic() { public boolean isStatic() {
return Modifier.isStatic(flags); return Modifier.isStatic(flags);
@ -243,10 +353,22 @@ import static java.lang.invoke.MethodHandleStatics.*;
public boolean isFinal() { public boolean isFinal() {
return Modifier.isFinal(flags); return Modifier.isFinal(flags);
} }
/** Utility method to query whether this member or its defining class is final. */
public boolean canBeStaticallyBound() {
return Modifier.isFinal(flags | clazz.getModifiers());
}
/** Utility method to query the modifier flags of this member. */
public boolean isVolatile() {
return Modifier.isVolatile(flags);
}
/** Utility method to query the modifier flags of this member. */ /** Utility method to query the modifier flags of this member. */
public boolean isAbstract() { public boolean isAbstract() {
return Modifier.isAbstract(flags); return Modifier.isAbstract(flags);
} }
/** Utility method to query the modifier flags of this member. */
public boolean isNative() {
return Modifier.isNative(flags);
}
// let the rest (native, volatile, transient, etc.) be tested via Modifier.isFoo // let the rest (native, volatile, transient, etc.) be tested via Modifier.isFoo
// unofficial modifier flags, used by HotSpot: // unofficial modifier flags, used by HotSpot:
@ -279,15 +401,12 @@ import static java.lang.invoke.MethodHandleStatics.*;
IS_CONSTRUCTOR = MN_IS_CONSTRUCTOR, // constructor IS_CONSTRUCTOR = MN_IS_CONSTRUCTOR, // constructor
IS_FIELD = MN_IS_FIELD, // field IS_FIELD = MN_IS_FIELD, // field
IS_TYPE = MN_IS_TYPE; // nested type IS_TYPE = MN_IS_TYPE; // nested type
static final int // for MethodHandleNatives.getMembers
SEARCH_SUPERCLASSES = MN_SEARCH_SUPERCLASSES,
SEARCH_INTERFACES = MN_SEARCH_INTERFACES;
static final int ALL_ACCESS = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED; static final int ALL_ACCESS = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED;
static final int ALL_KINDS = IS_METHOD | IS_CONSTRUCTOR | IS_FIELD | IS_TYPE; static final int ALL_KINDS = IS_METHOD | IS_CONSTRUCTOR | IS_FIELD | IS_TYPE;
static final int IS_INVOCABLE = IS_METHOD | IS_CONSTRUCTOR; static final int IS_INVOCABLE = IS_METHOD | IS_CONSTRUCTOR;
static final int IS_FIELD_OR_METHOD = IS_METHOD | IS_FIELD; static final int IS_FIELD_OR_METHOD = IS_METHOD | IS_FIELD;
static final int SEARCH_ALL_SUPERS = SEARCH_SUPERCLASSES | SEARCH_INTERFACES; static final int SEARCH_ALL_SUPERS = MN_SEARCH_SUPERCLASSES | MN_SEARCH_INTERFACES;
/** Utility method to query whether this member is a method or constructor. */ /** Utility method to query whether this member is a method or constructor. */
public boolean isInvocable() { public boolean isInvocable() {
@ -318,6 +437,12 @@ import static java.lang.invoke.MethodHandleStatics.*;
return !testAnyFlags(ALL_ACCESS); return !testAnyFlags(ALL_ACCESS);
} }
/** Utility method to query whether this member is accessible from a given lookup class. */
public boolean isAccessibleFrom(Class<?> lookupClass) {
return VerifyAccess.isMemberAccessible(this.getDeclaringClass(), this.getDeclaringClass(), flags,
lookupClass, ALL_ACCESS|MethodHandles.Lookup.PACKAGE);
}
/** Initialize a query. It is not resolved. */ /** Initialize a query. It is not resolved. */
private void init(Class<?> defClass, String name, Object type, int flags) { private void init(Class<?> defClass, String name, Object type, int flags) {
// defining class is allowed to be null (for a naked name/type pair) // defining class is allowed to be null (for a naked name/type pair)
@ -328,7 +453,7 @@ import static java.lang.invoke.MethodHandleStatics.*;
this.name = name; this.name = name;
this.type = type; this.type = type;
setFlags(flags); setFlags(flags);
assert(!isResolved()); assert(this.resolution == null); // nobody should have touched this yet
} }
private void expandFromVM() { private void expandFromVM() {
@ -339,39 +464,94 @@ import static java.lang.invoke.MethodHandleStatics.*;
} }
// Capturing information from the Core Reflection API: // Capturing information from the Core Reflection API:
private static int flagsMods(int flags, int mods) { private static int flagsMods(int flags, int mods, byte refKind) {
assert((flags & RECOGNIZED_MODIFIERS) == 0); assert((flags & RECOGNIZED_MODIFIERS) == 0);
assert((mods & ~RECOGNIZED_MODIFIERS) == 0); assert((mods & ~RECOGNIZED_MODIFIERS) == 0);
return flags | mods; assert((refKind & ~MN_REFERENCE_KIND_MASK) == 0);
return flags | mods | (refKind << MN_REFERENCE_KIND_SHIFT);
} }
/** Create a name for the given reflected method. The resulting name will be in a resolved state. */ /** Create a name for the given reflected method. The resulting name will be in a resolved state. */
public MemberName(Method m) { public MemberName(Method m) {
Object[] typeInfo = { m.getReturnType(), m.getParameterTypes() }; this(m, false);
init(m.getDeclaringClass(), m.getName(), typeInfo, flagsMods(IS_METHOD, m.getModifiers())); }
@SuppressWarnings("LeakingThisInConstructor")
public MemberName(Method m, boolean wantSpecial) {
m.getClass(); // NPE check
// fill in vmtarget, vmindex while we have m in hand: // fill in vmtarget, vmindex while we have m in hand:
MethodHandleNatives.init(this, m); MethodHandleNatives.init(this, m);
assert(isResolved()); assert(isResolved() && this.clazz != null);
this.name = m.getName();
if (this.type == null)
this.type = new Object[] { m.getReturnType(), m.getParameterTypes() };
if (wantSpecial) {
if (getReferenceKind() == REF_invokeVirtual)
changeReferenceKind(REF_invokeSpecial, REF_invokeVirtual);
}
}
public MemberName asSpecial() {
switch (getReferenceKind()) {
case REF_invokeSpecial: return this;
case REF_invokeVirtual: return clone().changeReferenceKind(REF_invokeSpecial, REF_invokeVirtual);
case REF_newInvokeSpecial: return clone().changeReferenceKind(REF_invokeSpecial, REF_newInvokeSpecial);
}
throw new IllegalArgumentException(this.toString());
}
public MemberName asConstructor() {
switch (getReferenceKind()) {
case REF_invokeSpecial: return clone().changeReferenceKind(REF_newInvokeSpecial, REF_invokeSpecial);
case REF_newInvokeSpecial: return this;
}
throw new IllegalArgumentException(this.toString());
} }
/** Create a name for the given reflected constructor. The resulting name will be in a resolved state. */ /** Create a name for the given reflected constructor. The resulting name will be in a resolved state. */
@SuppressWarnings("LeakingThisInConstructor")
public MemberName(Constructor<?> ctor) { public MemberName(Constructor<?> ctor) {
Object[] typeInfo = { void.class, ctor.getParameterTypes() }; ctor.getClass(); // NPE check
init(ctor.getDeclaringClass(), CONSTRUCTOR_NAME, typeInfo, flagsMods(IS_CONSTRUCTOR, ctor.getModifiers()));
// fill in vmtarget, vmindex while we have ctor in hand: // fill in vmtarget, vmindex while we have ctor in hand:
MethodHandleNatives.init(this, ctor); MethodHandleNatives.init(this, ctor);
assert(isResolved()); assert(isResolved() && this.clazz != null);
this.name = CONSTRUCTOR_NAME;
if (this.type == null)
this.type = new Object[] { void.class, ctor.getParameterTypes() };
} }
/** Create a name for the given reflected field. The resulting name will be in a resolved state. */ /** Create a name for the given reflected field. The resulting name will be in a resolved state.
*/
public MemberName(Field fld) { public MemberName(Field fld) {
init(fld.getDeclaringClass(), fld.getName(), fld.getType(), flagsMods(IS_FIELD, fld.getModifiers())); this(fld, false);
}
@SuppressWarnings("LeakingThisInConstructor")
public MemberName(Field fld, boolean makeSetter) {
fld.getClass(); // NPE check
// fill in vmtarget, vmindex while we have fld in hand: // fill in vmtarget, vmindex while we have fld in hand:
MethodHandleNatives.init(this, fld); MethodHandleNatives.init(this, fld);
assert(isResolved()); assert(isResolved() && this.clazz != null);
this.name = fld.getName();
this.type = fld.getType();
assert((REF_putStatic - REF_getStatic) == (REF_putField - REF_getField));
byte refKind = this.getReferenceKind();
assert(refKind == (isStatic() ? REF_getStatic : REF_getField));
if (makeSetter) {
changeReferenceKind((byte)(refKind + (REF_putStatic - REF_getStatic)), refKind);
}
}
public boolean isGetter() {
return MethodHandleNatives.refKindIsGetter(getReferenceKind());
}
public boolean isSetter() {
return MethodHandleNatives.refKindIsSetter(getReferenceKind());
}
public MemberName asSetter() {
byte refKind = getReferenceKind();
assert(MethodHandleNatives.refKindIsGetter(refKind));
assert((REF_putStatic - REF_getStatic) == (REF_putField - REF_getField));
byte setterRefKind = (byte)(refKind + (REF_putField - REF_getField));
return clone().changeReferenceKind(setterRefKind, refKind);
} }
/** Create a name for the given class. The resulting name will be in a resolved state. */ /** Create a name for the given class. The resulting name will be in a resolved state. */
public MemberName(Class<?> type) { public MemberName(Class<?> type) {
init(type.getDeclaringClass(), type.getSimpleName(), type, flagsMods(IS_TYPE, type.getModifiers())); init(type.getDeclaringClass(), type.getSimpleName(), type,
vmindex = 0; // isResolved flagsMods(IS_TYPE, type.getModifiers(), REF_NONE));
assert(isResolved()); initResolved(true);
} }
// bare-bones constructor; the JVM will fill it in // bare-bones constructor; the JVM will fill it in
@ -386,41 +566,89 @@ import static java.lang.invoke.MethodHandleStatics.*;
} }
} }
// %%% define equals/hashcode? /** Get the definition of this member name.
* This may be in a super-class of the declaring class of this member.
*/
public MemberName getDefinition() {
if (!isResolved()) throw new IllegalStateException("must be resolved: "+this);
if (isType()) return this;
MemberName res = this.clone();
res.clazz = null;
res.type = null;
res.name = null;
res.resolution = res;
res.expandFromVM();
assert(res.getName().equals(this.getName()));
return res;
}
@Override
public int hashCode() {
return Objects.hash(clazz, flags, name, getType());
}
@Override
public boolean equals(Object that) {
return (that instanceof MemberName && this.equals((MemberName)that));
}
/** Decide if two member names have exactly the same symbolic content.
* Does not take into account any actual class members, so even if
* two member names resolve to the same actual member, they may
* be distinct references.
*/
public boolean equals(MemberName that) {
if (this == that) return true;
if (that == null) return false;
return this.clazz == that.clazz
&& this.flags == that.flags
&& Objects.equals(this.name, that.name)
&& Objects.equals(this.getType(), that.getType());
}
// Construction from symbolic parts, for queries: // Construction from symbolic parts, for queries:
/** Create a field or type name from the given components: Declaring class, name, type, modifiers. /** Create a field or type name from the given components: Declaring class, name, type, reference kind.
* The declaring class may be supplied as null if this is to be a bare name and type. * The declaring class may be supplied as null if this is to be a bare name and type.
* The resulting name will in an unresolved state. * The resulting name will in an unresolved state.
*/ */
public MemberName(Class<?> defClass, String name, Class<?> type, int modifiers) { public MemberName(Class<?> defClass, String name, Class<?> type, byte refKind) {
init(defClass, name, type, IS_FIELD | (modifiers & RECOGNIZED_MODIFIERS)); init(defClass, name, type, flagsMods(IS_FIELD, 0, refKind));
initResolved(false);
} }
/** Create a field or type name from the given components: Declaring class, name, type. /** Create a field or type name from the given components: Declaring class, name, type.
* The declaring class may be supplied as null if this is to be a bare name and type. * The declaring class may be supplied as null if this is to be a bare name and type.
* The modifier flags default to zero. * The modifier flags default to zero.
* The resulting name will in an unresolved state. * The resulting name will in an unresolved state.
*/ */
public MemberName(Class<?> defClass, String name, Class<?> type) { public MemberName(Class<?> defClass, String name, Class<?> type, Void unused) {
this(defClass, name, type, 0); this(defClass, name, type, REF_NONE);
initResolved(false);
} }
/** Create a method or constructor name from the given components: Declaring class, name, type, modifiers. /** Create a method or constructor name from the given components: Declaring class, name, type, modifiers.
* It will be a constructor if and only if the name is {@code "&lt;init&gt;"}. * It will be a constructor if and only if the name is {@code "&lt;init&gt;"}.
* The declaring class may be supplied as null if this is to be a bare name and type. * The declaring class may be supplied as null if this is to be a bare name and type.
* The last argument is optional, a boolean which requests REF_invokeSpecial.
* The resulting name will in an unresolved state. * The resulting name will in an unresolved state.
*/ */
public MemberName(Class<?> defClass, String name, MethodType type, int modifiers) { public MemberName(Class<?> defClass, String name, MethodType type, byte refKind) {
int flagBit = (name.equals(CONSTRUCTOR_NAME) ? IS_CONSTRUCTOR : IS_METHOD); @SuppressWarnings("LocalVariableHidesMemberVariable")
init(defClass, name, type, flagBit | (modifiers & RECOGNIZED_MODIFIERS)); int flags = (name != null && name.equals(CONSTRUCTOR_NAME) ? IS_CONSTRUCTOR : IS_METHOD);
init(defClass, name, type, flagsMods(flags, 0, refKind));
initResolved(false);
} }
/** Create a method or constructor name from the given components: Declaring class, name, type, modifiers. // /** Create a method or constructor name from the given components: Declaring class, name, type, modifiers.
* It will be a constructor if and only if the name is {@code "&lt;init&gt;"}. // * It will be a constructor if and only if the name is {@code "&lt;init&gt;"}.
* The declaring class may be supplied as null if this is to be a bare name and type. // * The declaring class may be supplied as null if this is to be a bare name and type.
* The modifier flags default to zero. // * The modifier flags default to zero.
* The resulting name will in an unresolved state. // * The resulting name will in an unresolved state.
// */
// public MemberName(Class<?> defClass, String name, MethodType type, Void unused) {
// this(defClass, name, type, REF_NONE);
// }
/** Query whether this member name is resolved to a non-static, non-final method.
*/ */
public MemberName(Class<?> defClass, String name, MethodType type) { public boolean hasReceiverTypeDispatch() {
this(defClass, name, type, 0); return MethodHandleNatives.refKindDoesDispatch(getReferenceKind());
} }
/** Query whether this member name is resolved. /** Query whether this member name is resolved.
@ -429,15 +657,38 @@ import static java.lang.invoke.MethodHandleStatics.*;
* (Document?) * (Document?)
*/ */
public boolean isResolved() { public boolean isResolved() {
return (vmindex != VM_INDEX_UNINITIALIZED); return resolution == null;
} }
/** Query whether this member name is resolved to a non-static, non-final method. private void initResolved(boolean isResolved) {
*/ assert(this.resolution == null); // not initialized yet!
public boolean hasReceiverTypeDispatch() { if (!isResolved)
return (isMethod() && getVMIndex() >= 0); this.resolution = this;
assert(isResolved() == isResolved);
} }
void checkForTypeAlias() {
if (isInvocable()) {
MethodType type;
if (this.type instanceof MethodType)
type = (MethodType) this.type;
else
this.type = type = getMethodType();
if (type.erase() == type) return;
if (VerifyAccess.isTypeVisible(type, clazz)) return;
throw new LinkageError("bad method type alias: "+type+" not visible from "+clazz);
} else {
Class<?> type;
if (this.type instanceof Class<?>)
type = (Class<?>) this.type;
else
this.type = type = getFieldType();
if (VerifyAccess.isTypeVisible(type, clazz)) return;
throw new LinkageError("bad field type alias: "+type+" not visible from "+clazz);
}
}
/** Produce a string form of this member name. /** Produce a string form of this member name.
* For types, it is simply the type's own string (as reported by {@code toString}). * For types, it is simply the type's own string (as reported by {@code toString}).
* For fields, it is {@code "DeclaringClass.name/type"}. * For fields, it is {@code "DeclaringClass.name/type"}.
@ -445,6 +696,7 @@ import static java.lang.invoke.MethodHandleStatics.*;
* If the declaring class is null, the prefix {@code "DeclaringClass."} is omitted. * If the declaring class is null, the prefix {@code "DeclaringClass."} is omitted.
* If the member is unresolved, a prefix {@code "*."} is prepended. * If the member is unresolved, a prefix {@code "*."} is prepended.
*/ */
@SuppressWarnings("LocalVariableHidesMemberVariable")
@Override @Override
public String toString() { public String toString() {
if (isType()) if (isType())
@ -464,22 +716,12 @@ import static java.lang.invoke.MethodHandleStatics.*;
} else { } else {
buf.append(type == null ? "(*)*" : getName(type)); buf.append(type == null ? "(*)*" : getName(type));
} }
/* byte refKind = getReferenceKind();
if (refKind != REF_NONE) {
buf.append('/'); buf.append('/');
// key: Public, private, pRotected, sTatic, Final, sYnchronized, buf.append(MethodHandleNatives.refKindName(refKind));
// transient/Varargs, native, (interface), abstract, sTrict, sYnthetic,
// (annotation), Enum, (unused)
final String FIELD_MOD_CHARS = "PprTF?vt????Y?E?";
final String METHOD_MOD_CHARS = "PprTFybVn?atY???";
String modChars = (isInvocable() ? METHOD_MOD_CHARS : FIELD_MOD_CHARS);
for (int i = 0; i < modChars.length(); i++) {
if ((flags & (1 << i)) != 0) {
char mc = modChars.charAt(i);
if (mc != '?')
buf.append(mc);
} }
} //buf.append("#").append(System.identityHashCode(this));
*/
return buf.toString(); return buf.toString();
} }
private static String getName(Object obj) { private static String getName(Object obj) {
@ -488,19 +730,6 @@ import static java.lang.invoke.MethodHandleStatics.*;
return String.valueOf(obj); return String.valueOf(obj);
} }
// Queries to the JVM:
/** Document? */
/*non-public*/ int getVMIndex() {
if (!isResolved())
throw newIllegalStateException("not resolved", this);
return vmindex;
}
// /*non-public*/ Object getVMTarget() {
// if (!isResolved())
// throw newIllegalStateException("not resolved", this);
// return vmtarget;
// }
public IllegalAccessException makeAccessException(String message, Object from) { public IllegalAccessException makeAccessException(String message, Object from) {
message = message + ": "+ toString(); message = message + ": "+ toString();
if (from != null) message += ", from " + from; if (from != null) message += ", from " + from;
@ -518,14 +747,19 @@ import static java.lang.invoke.MethodHandleStatics.*;
} }
public ReflectiveOperationException makeAccessException() { public ReflectiveOperationException makeAccessException() {
String message = message() + ": "+ toString(); String message = message() + ": "+ toString();
if (isResolved()) ReflectiveOperationException ex;
return new IllegalAccessException(message); if (isResolved() || !(resolution instanceof NoSuchMethodError ||
resolution instanceof NoSuchFieldError))
ex = new IllegalAccessException(message);
else if (isConstructor()) else if (isConstructor())
return new NoSuchMethodException(message); ex = new NoSuchMethodException(message);
else if (isMethod()) else if (isMethod())
return new NoSuchMethodException(message); ex = new NoSuchMethodException(message);
else else
return new NoSuchFieldException(message); ex = new NoSuchFieldException(message);
if (resolution instanceof Throwable)
ex.initCause((Throwable) resolution);
return ex;
} }
/** Actually making a query requires an access check. */ /** Actually making a query requires an access check. */
@ -539,7 +773,7 @@ import static java.lang.invoke.MethodHandleStatics.*;
private Factory() { } // singleton pattern private Factory() { } // singleton pattern
static Factory INSTANCE = new Factory(); static Factory INSTANCE = new Factory();
private static int ALLOWED_FLAGS = SEARCH_ALL_SUPERS | ALL_KINDS; private static int ALLOWED_FLAGS = ALL_KINDS;
/// Queries /// Queries
List<MemberName> getMembers(Class<?> defc, List<MemberName> getMembers(Class<?> defc,
@ -573,14 +807,14 @@ import static java.lang.invoke.MethodHandleStatics.*;
// JVM returned to us with an intentional overflow! // JVM returned to us with an intentional overflow!
totalCount += buf.length; totalCount += buf.length;
int excess = bufCount - buf.length; int excess = bufCount - buf.length;
if (bufs == null) bufs = new ArrayList<MemberName[]>(1); if (bufs == null) bufs = new ArrayList<>(1);
bufs.add(buf); bufs.add(buf);
int len2 = buf.length; int len2 = buf.length;
len2 = Math.max(len2, excess); len2 = Math.max(len2, excess);
len2 = Math.max(len2, totalCount / 4); len2 = Math.max(len2, totalCount / 4);
buf = newMemberBuffer(Math.min(BUF_MAX, len2)); buf = newMemberBuffer(Math.min(BUF_MAX, len2));
} }
ArrayList<MemberName> result = new ArrayList<MemberName>(totalCount); ArrayList<MemberName> result = new ArrayList<>(totalCount);
if (bufs != null) { if (bufs != null) {
for (MemberName[] buf0 : bufs) { for (MemberName[] buf0 : bufs) {
Collections.addAll(result, buf0); Collections.addAll(result, buf0);
@ -599,43 +833,29 @@ import static java.lang.invoke.MethodHandleStatics.*;
} }
return result; return result;
} }
boolean resolveInPlace(MemberName m, boolean searchSupers, Class<?> lookupClass) {
if (m.name == null || m.type == null) { // find unique non-overloaded name
Class<?> defc = m.getDeclaringClass();
List<MemberName> choices = null;
if (m.isMethod())
choices = getMethods(defc, searchSupers, m.name, (MethodType) m.type, lookupClass);
else if (m.isConstructor())
choices = getConstructors(defc, lookupClass);
else if (m.isField())
choices = getFields(defc, searchSupers, m.name, (Class<?>) m.type, lookupClass);
//System.out.println("resolving "+m+" to "+choices);
if (choices == null || choices.size() != 1)
return false;
if (m.name == null) m.name = choices.get(0).name;
if (m.type == null) m.type = choices.get(0).type;
}
MethodHandleNatives.resolve(m, lookupClass);
if (m.isResolved()) return true;
int matchFlags = m.flags | (searchSupers ? SEARCH_ALL_SUPERS : 0);
String matchSig = m.getSignature();
MemberName[] buf = { m };
int n = MethodHandleNatives.getMembers(m.getDeclaringClass(),
m.getName(), matchSig, matchFlags, lookupClass, 0, buf);
if (n != 1) return false;
return m.isResolved();
}
/** Produce a resolved version of the given member. /** Produce a resolved version of the given member.
* Super types are searched (for inherited members) if {@code searchSupers} is true. * Super types are searched (for inherited members) if {@code searchSupers} is true.
* Access checking is performed on behalf of the given {@code lookupClass}. * Access checking is performed on behalf of the given {@code lookupClass}.
* If lookup fails or access is not permitted, null is returned. * If lookup fails or access is not permitted, null is returned.
* Otherwise a fresh copy of the given member is returned, with modifier bits filled in. * Otherwise a fresh copy of the given member is returned, with modifier bits filled in.
*/ */
public MemberName resolveOrNull(MemberName m, boolean searchSupers, Class<?> lookupClass) { private MemberName resolve(byte refKind, MemberName ref, Class<?> lookupClass) {
MemberName result = m.clone(); MemberName m = ref.clone(); // JVM will side-effect the ref
if (resolveInPlace(result, searchSupers, lookupClass)) assert(refKind == m.getReferenceKind());
return result; try {
return null; m = MethodHandleNatives.resolve(m, lookupClass);
m.checkForTypeAlias();
m.resolution = null;
} catch (LinkageError ex) {
// JVM reports that the "bytecode behavior" would get an error
assert(!m.isResolved());
m.resolution = ex;
return m;
}
assert(m.referenceKindIsConsistent());
m.initResolved(true);
assert(m.vminfoIsConsistent());
return m;
} }
/** Produce a resolved version of the given member. /** Produce a resolved version of the given member.
* Super types are searched (for inherited members) if {@code searchSupers} is true. * Super types are searched (for inherited members) if {@code searchSupers} is true.
@ -645,16 +865,29 @@ import static java.lang.invoke.MethodHandleStatics.*;
*/ */
public public
<NoSuchMemberException extends ReflectiveOperationException> <NoSuchMemberException extends ReflectiveOperationException>
MemberName resolveOrFail(MemberName m, boolean searchSupers, Class<?> lookupClass, MemberName resolveOrFail(byte refKind, MemberName m, Class<?> lookupClass,
Class<NoSuchMemberException> nsmClass) Class<NoSuchMemberException> nsmClass)
throws IllegalAccessException, NoSuchMemberException { throws IllegalAccessException, NoSuchMemberException {
MemberName result = resolveOrNull(m, searchSupers, lookupClass); MemberName result = resolve(refKind, m, lookupClass);
if (result != null) if (result.isResolved())
return result; return result;
ReflectiveOperationException ex = m.makeAccessException(); ReflectiveOperationException ex = result.makeAccessException();
if (ex instanceof IllegalAccessException) throw (IllegalAccessException) ex; if (ex instanceof IllegalAccessException) throw (IllegalAccessException) ex;
throw nsmClass.cast(ex); throw nsmClass.cast(ex);
} }
/** Produce a resolved version of the given member.
* Super types are searched (for inherited members) if {@code searchSupers} is true.
* Access checking is performed on behalf of the given {@code lookupClass}.
* If lookup fails or access is not permitted, return null.
* Otherwise a fresh copy of the given member is returned, with modifier bits filled in.
*/
public
MemberName resolveOrNull(byte refKind, MemberName m, Class<?> lookupClass) {
MemberName result = resolve(refKind, m, lookupClass);
if (result.isResolved())
return result;
return null;
}
/** Return a list of all methods defined by the given class. /** Return a list of all methods defined by the given class.
* Super types are searched (for inherited members) if {@code searchSupers} is true. * Super types are searched (for inherited members) if {@code searchSupers} is true.
* Access checking is performed on behalf of the given {@code lookupClass}. * Access checking is performed on behalf of the given {@code lookupClass}.

View file

@ -26,9 +26,13 @@
package java.lang.invoke; package java.lang.invoke;
import java.util.ArrayList; import java.util.*;
import sun.invoke.util.ValueConversions; import sun.invoke.util.*;
import sun.misc.Unsafe;
import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandleStatics.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* A method handle is a typed, directly executable reference to an underlying method, * A method handle is a typed, directly executable reference to an underlying method,
@ -208,8 +212,8 @@ import static java.lang.invoke.MethodHandleStatics.*;
* refers directly to an associated {@code CONSTANT_Methodref}, * refers directly to an associated {@code CONSTANT_Methodref},
* {@code CONSTANT_InterfaceMethodref}, or {@code CONSTANT_Fieldref} * {@code CONSTANT_InterfaceMethodref}, or {@code CONSTANT_Fieldref}
* constant pool entry. * constant pool entry.
* (For more details on method handle constants, * (For full details on method handle constants,
* see the <a href="package-summary.html#mhcon">package summary</a>.) * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
* <p> * <p>
* Method handles produced by lookups or constant loads from methods or * Method handles produced by lookups or constant loads from methods or
* constructors with the variable arity modifier bit ({@code 0x0080}) * constructors with the variable arity modifier bit ({@code 0x0080})
@ -224,6 +228,19 @@ import static java.lang.invoke.MethodHandleStatics.*;
* (E.g., if a non-static method handle is obtained via {@code ldc}, * (E.g., if a non-static method handle is obtained via {@code ldc},
* the type of the receiver is the class named in the constant pool entry.) * the type of the receiver is the class named in the constant pool entry.)
* <p> * <p>
* Method handle constants are subject to the same link-time access checks
* their corresponding bytecode instructions, and the {@code ldc} instruction
* will throw corresponding linkage errors if the bytecode behaviors would
* throw such errors.
* <p>
* As a corollary of this, access to protected members is restricted
* to receivers only of the accessing class, or one of its subclasses,
* and the accessing class must in turn be a subclass (or package sibling)
* of the protected member's defining class.
* If a method reference refers to a protected non-static method or field
* of a class outside the current package, the receiver argument will
* be narrowed to the type of the accessing class.
* <p>
* When a method handle to a virtual method is invoked, the method is * When a method handle to a virtual method is invoked, the method is
* always looked up in the receiver (that is, the first argument). * always looked up in the receiver (that is, the first argument).
* <p> * <p>
@ -390,39 +407,8 @@ mh.invokeExact(System.out, "Hello, world.");
* @author John Rose, JSR 292 EG * @author John Rose, JSR 292 EG
*/ */
public abstract class MethodHandle { public abstract class MethodHandle {
// { JVM internals:
private byte vmentry; // adapter stub or method entry point
//private int vmslots; // optionally, hoist type.form.vmslots
/*non-public*/ Object vmtarget; // VM-specific, class-specific target value
// TO DO: vmtarget should be invisible to Java, since the JVM puts internal
// managed pointers into it. Making it visible exposes it to debuggers,
// which can cause errors when they treat the pointer as an Object.
// These two dummy fields are present to force 'I' and 'J' signatures
// into this class's constant pool, so they can be transferred
// to vmentry when this class is loaded.
static final int INT_FIELD = 0;
static final long LONG_FIELD = 0;
// vmentry (a void* field) is used *only* by the JVM.
// The JVM adjusts its type to int or long depending on system wordsize.
// Since it is statically typed as neither int nor long, it is impossible
// to use this field from Java bytecode. (Please don't try to, either.)
// The vmentry is an assembly-language stub which is jumped to
// immediately after the method type is verified.
// For a direct MH, this stub loads the vmtarget's entry point
// and jumps to it.
// } End of JVM internals.
static { MethodHandleImpl.initStatics(); } static { MethodHandleImpl.initStatics(); }
// interface MethodHandle<R throws X extends Exception,A...>
// { MethodType<R throws X,A...> type(); public R invokeExact(A...) throws X; }
/** /**
* Internal marker interface which distinguishes (to the Java compiler) * Internal marker interface which distinguishes (to the Java compiler)
* those methods which are <a href="MethodHandle.html#sigpoly">signature polymorphic</a>. * those methods which are <a href="MethodHandle.html#sigpoly">signature polymorphic</a>.
@ -431,7 +417,9 @@ public abstract class MethodHandle {
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@interface PolymorphicSignature { } @interface PolymorphicSignature { }
private MethodType type; private final MethodType type;
/*private*/ final LambdaForm form;
// form is not private so that invokers can easily fetch it
/** /**
* Reports the type of this method handle. * Reports the type of this method handle.
@ -448,9 +436,13 @@ public abstract class MethodHandle {
* the {@code java.lang.invoke} package. * the {@code java.lang.invoke} package.
*/ */
// @param type type (permanently assigned) of the new method handle // @param type type (permanently assigned) of the new method handle
/*non-public*/ MethodHandle(MethodType type) { /*non-public*/ MethodHandle(MethodType type, LambdaForm form) {
type.getClass(); // elicit NPE type.getClass(); // explicit NPE
form.getClass(); // explicit NPE
this.type = type; this.type = type;
this.form = form;
form.prepare(); // TO DO: Try to delay this step until just before invocation.
} }
/** /**
@ -505,6 +497,46 @@ public abstract class MethodHandle {
*/ */
public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable; public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;
/**
* Private method for trusted invocation of a method handle respecting simplified signatures.
* Type mismatches will not throw {@code WrongMethodTypeException}, but could crash the JVM.
* <p>
* The caller signature is restricted to the following basic types:
* Object, int, long, float, double, and void return.
* <p>
* The caller is responsible for maintaining type correctness by ensuring
* that the each outgoing argument value is a member of the range of the corresponding
* callee argument type.
* (The caller should therefore issue appropriate casts and integer narrowing
* operations on outgoing argument values.)
* The caller can assume that the incoming result value is part of the range
* of the callee's return type.
*/
/*non-public*/ final native @PolymorphicSignature Object invokeBasic(Object... args) throws Throwable;
/*non-public*/ static native @PolymorphicSignature Object linkToVirtual(Object... args) throws Throwable;
/**
* Private method for trusted invocation of a MemberName of kind {@code REF_invokeStatic}.
* The caller signature is restricted to basic types as with {@code invokeBasic}.
* The trailing (not leading) argument must be a MemberName.
*/
/*non-public*/ static native @PolymorphicSignature Object linkToStatic(Object... args) throws Throwable;
/**
* Private method for trusted invocation of a MemberName of kind {@code REF_invokeSpecial}.
* The caller signature is restricted to basic types as with {@code invokeBasic}.
* The trailing (not leading) argument must be a MemberName.
*/
/*non-public*/ static native @PolymorphicSignature Object linkToSpecial(Object... args) throws Throwable;
/**
* Private method for trusted invocation of a MemberName of kind {@code REF_invokeInterface}.
* The caller signature is restricted to basic types as with {@code invokeBasic}.
* The trailing (not leading) argument must be a MemberName.
*/
/*non-public*/ static native @PolymorphicSignature Object linkToInterface(Object... args) throws Throwable;
/** /**
* Performs a variable arity invocation, passing the arguments in the given array * Performs a variable arity invocation, passing the arguments in the given array
* to the method handle, as if via an inexact {@link #invoke invoke} from a call site * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
@ -557,6 +589,7 @@ public abstract class MethodHandle {
*/ */
public Object invokeWithArguments(Object... arguments) throws Throwable { public Object invokeWithArguments(Object... arguments) throws Throwable {
int argc = arguments == null ? 0 : arguments.length; int argc = arguments == null ? 0 : arguments.length;
@SuppressWarnings("LocalVariableHidesMemberVariable")
MethodType type = type(); MethodType type = type();
if (type.parameterCount() != argc || isVarargsCollector()) { if (type.parameterCount() != argc || isVarargsCollector()) {
// simulate invoke // simulate invoke
@ -690,7 +723,7 @@ public abstract class MethodHandle {
if (!type.isConvertibleTo(newType)) { if (!type.isConvertibleTo(newType)) {
throw new WrongMethodTypeException("cannot convert "+this+" to "+newType); throw new WrongMethodTypeException("cannot convert "+this+" to "+newType);
} }
return MethodHandleImpl.convertArguments(this, newType, 1); return convertArguments(newType);
} }
/** /**
@ -772,7 +805,8 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray
*/ */
public MethodHandle asSpreader(Class<?> arrayType, int arrayLength) { public MethodHandle asSpreader(Class<?> arrayType, int arrayLength) {
asSpreaderChecks(arrayType, arrayLength); asSpreaderChecks(arrayType, arrayLength);
return MethodHandleImpl.spreadArguments(this, arrayType, arrayLength); int spreadArgPos = type.parameterCount() - arrayLength;
return MethodHandleImpl.makeSpreadArguments(this, arrayType, spreadArgPos, arrayLength);
} }
private void asSpreaderChecks(Class<?> arrayType, int arrayLength) { private void asSpreaderChecks(Class<?> arrayType, int arrayLength) {
@ -790,7 +824,7 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray
} }
} }
if (sawProblem) { if (sawProblem) {
ArrayList<Class<?>> ptypes = new ArrayList<Class<?>>(type().parameterList()); ArrayList<Class<?>> ptypes = new ArrayList<>(type().parameterList());
for (int i = nargs - arrayLength; i < nargs; i++) { for (int i = nargs - arrayLength; i < nargs; i++) {
ptypes.set(i, arrayElement); ptypes.set(i, arrayElement);
} }
@ -885,8 +919,12 @@ assertEquals("[123]", (String) longsToString.invokeExact((long)123));
*/ */
public MethodHandle asCollector(Class<?> arrayType, int arrayLength) { public MethodHandle asCollector(Class<?> arrayType, int arrayLength) {
asCollectorChecks(arrayType, arrayLength); asCollectorChecks(arrayType, arrayLength);
int collectArgPos = type().parameterCount()-1;
MethodHandle target = this;
if (arrayType != type().parameterType(collectArgPos))
target = convertArguments(type().changeParameterType(collectArgPos, arrayType));
MethodHandle collector = ValueConversions.varargsArray(arrayType, arrayLength); MethodHandle collector = ValueConversions.varargsArray(arrayType, arrayLength);
return MethodHandleImpl.collectArguments(this, type.parameterCount()-1, collector); return MethodHandleImpl.makeCollectArguments(target, collector, collectArgPos, false);
} }
// private API: return true if last param exactly matches arrayType // private API: return true if last param exactly matches arrayType
@ -1056,7 +1094,7 @@ assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
boolean lastMatch = asCollectorChecks(arrayType, 0); boolean lastMatch = asCollectorChecks(arrayType, 0);
if (isVarargsCollector() && lastMatch) if (isVarargsCollector() && lastMatch)
return this; return this;
return AdapterMethodHandle.makeVarargsCollector(this, arrayType); return MethodHandleImpl.makeVarargsCollector(this, arrayType);
} }
/** /**
@ -1155,14 +1193,13 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
*/ */
public MethodHandle bindTo(Object x) { public MethodHandle bindTo(Object x) {
Class<?> ptype; Class<?> ptype;
if (type().parameterCount() == 0 || @SuppressWarnings("LocalVariableHidesMemberVariable")
(ptype = type().parameterType(0)).isPrimitive()) MethodType type = type();
if (type.parameterCount() == 0 ||
(ptype = type.parameterType(0)).isPrimitive())
throw newIllegalArgumentException("no leading reference parameter", x); throw newIllegalArgumentException("no leading reference parameter", x);
x = MethodHandles.checkValue(ptype, x); x = ptype.cast(x); // throw CCE if needed
// Cf. MethodHandles.insertArguments for the following logic: return bindReceiver(x);
MethodHandle bmh = MethodHandleImpl.bindReceiver(this, x);
if (bmh != null) return bmh;
return MethodHandleImpl.bindArgument(this, 0, x);
} }
/** /**
@ -1183,11 +1220,178 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
@Override @Override
public String toString() { public String toString() {
if (DEBUG_METHOD_HANDLE_NAMES) return debugString(); if (DEBUG_METHOD_HANDLE_NAMES) return debugString();
return standardString();
}
String standardString() {
return "MethodHandle"+type; return "MethodHandle"+type;
} }
String debugString() {
return standardString()+"="+internalForm()+internalValues();
}
//// Implementation methods.
//// Sub-classes can override these default implementations.
//// All these methods assume arguments are already validated.
// Other transforms to do: convert, explicitCast, permute, drop, filter, fold, GWT, catch
/*non-public*/
MethodHandle setVarargs(MemberName member) throws IllegalAccessException {
if (!member.isVarargs()) return this;
int argc = type().parameterCount();
if (argc != 0) {
Class<?> arrayType = type().parameterType(argc-1);
if (arrayType.isArray()) {
return MethodHandleImpl.makeVarargsCollector(this, arrayType);
}
}
throw member.makeAccessException("cannot make variable arity", null);
}
/*non-public*/
MethodHandle viewAsType(MethodType newType) {
// No actual conversions, just a new view of the same method.
if (!type.isViewableAs(newType))
throw new InternalError();
return MethodHandleImpl.makePairwiseConvert(this, newType, 0);
}
// Decoding
/*non-public*/
LambdaForm internalForm() {
return form;
}
/*non-public*/ /*non-public*/
String debugString() { MemberName internalMemberName() {
return getNameString(this); return null; // DMH returns DMH.member
}
/*non-public*/
Object internalValues() {
return "";
}
//// Method handle implementation methods.
//// Sub-classes can override these default implementations.
//// All these methods assume arguments are already validated.
/*non-public*/ MethodHandle convertArguments(MethodType newType) {
// Override this if it can be improved.
return MethodHandleImpl.makePairwiseConvert(this, newType, 1);
}
/*non-public*/
MethodHandle bindArgument(int pos, char basicType, Object value) {
// Override this if it can be improved.
return rebind().bindArgument(pos, basicType, value);
}
/*non-public*/
MethodHandle bindReceiver(Object receiver) {
// Override this if it can be improved.
return bindArgument(0, 'L', receiver);
}
/*non-public*/
MethodHandle bindImmediate(int pos, char basicType, Object value) {
// Bind an immediate value to a position in the arguments.
// This means, elide the respective argument,
// and replace all references to it in NamedFunction args with the specified value.
// CURRENT RESTRICTIONS
// * only for pos 0 and UNSAFE (position is adjusted in MHImpl to make API usable for others)
assert pos == 0 && basicType == 'L' && value instanceof Unsafe;
MethodType type2 = type.dropParameterTypes(pos, pos + 1); // adjustment: ignore receiver!
LambdaForm form2 = form.bindImmediate(pos + 1, basicType, value); // adjust pos to form-relative pos
return copyWith(type2, form2);
}
/*non-public*/
MethodHandle copyWith(MethodType mt, LambdaForm lf) {
throw new InternalError("copyWith: " + this.getClass());
}
/*non-public*/
MethodHandle dropArguments(MethodType srcType, int pos, int drops) {
// Override this if it can be improved.
return rebind().dropArguments(srcType, pos, drops);
}
/*non-public*/
MethodHandle permuteArguments(MethodType newType, int[] reorder) {
// Override this if it can be improved.
return rebind().permuteArguments(newType, reorder);
}
/*non-public*/
MethodHandle rebind() {
// Bind 'this' into a new invoker, of the known class BMH.
MethodType type2 = type();
LambdaForm form2 = reinvokerForm(type2.basicType());
// form2 = lambda (bmh, arg*) { thismh = bmh[0]; invokeBasic(thismh, arg*) }
return BoundMethodHandle.bindSingle(type2, form2, this);
}
/*non-public*/
MethodHandle reinvokerTarget() {
throw new InternalError("not a reinvoker MH: "+this.getClass().getName()+": "+this);
}
/** Create a LF which simply reinvokes a target of the given basic type.
* The target MH must override {@link #reinvokerTarget} to provide the target.
*/
static LambdaForm reinvokerForm(MethodType mtype) {
mtype = mtype.basicType();
LambdaForm reinvoker = mtype.form().cachedLambdaForm(MethodTypeForm.LF_REINVOKE);
if (reinvoker != null) return reinvoker;
MethodHandle MH_invokeBasic = MethodHandles.basicInvoker(mtype);
final int THIS_BMH = 0;
final int ARG_BASE = 1;
final int ARG_LIMIT = ARG_BASE + mtype.parameterCount();
int nameCursor = ARG_LIMIT;
final int NEXT_MH = nameCursor++;
final int REINVOKE = nameCursor++;
LambdaForm.Name[] names = LambdaForm.arguments(nameCursor - ARG_LIMIT, mtype.invokerType());
names[NEXT_MH] = new LambdaForm.Name(NF_reinvokerTarget, names[THIS_BMH]);
Object[] targetArgs = Arrays.copyOfRange(names, THIS_BMH, ARG_LIMIT, Object[].class);
targetArgs[0] = names[NEXT_MH]; // overwrite this MH with next MH
names[REINVOKE] = new LambdaForm.Name(MH_invokeBasic, targetArgs);
return mtype.form().setCachedLambdaForm(MethodTypeForm.LF_REINVOKE, new LambdaForm("BMH.reinvoke", ARG_LIMIT, names));
}
private static final LambdaForm.NamedFunction NF_reinvokerTarget;
static {
try {
NF_reinvokerTarget = new LambdaForm.NamedFunction(MethodHandle.class
.getDeclaredMethod("reinvokerTarget"));
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
}
/**
* Replace the old lambda form of this method handle with a new one.
* The new one must be functionally equivalent to the old one.
* Threads may continue running the old form indefinitely,
* but it is likely that the new one will be preferred for new executions.
* Use with discretion.
* @param newForm
*/
/*non-public*/
void updateForm(LambdaForm newForm) {
if (form == newForm) return;
// ISSUE: Should we have a memory fence here?
UNSAFE.putObject(this, FORM_OFFSET, newForm);
this.form.prepare(); // as in MethodHandle.<init>
}
private static final long FORM_OFFSET;
static {
try {
FORM_OFFSET = UNSAFE.objectFieldOffset(MethodHandle.class.getDeclaredField("form"));
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.invoke;
import java.lang.invoke.MethodHandleNatives.Constants;
//Not yet public: public
class MethodHandleInfo {
public static final int
REF_NONE = Constants.REF_NONE,
REF_getField = Constants.REF_getField,
REF_getStatic = Constants.REF_getStatic,
REF_putField = Constants.REF_putField,
REF_putStatic = Constants.REF_putStatic,
REF_invokeVirtual = Constants.REF_invokeVirtual,
REF_invokeStatic = Constants.REF_invokeStatic,
REF_invokeSpecial = Constants.REF_invokeSpecial,
REF_newInvokeSpecial = Constants.REF_newInvokeSpecial,
REF_invokeInterface = Constants.REF_invokeInterface;
private final Class<?> declaringClass;
private final String name;
private final MethodType methodType;
private final int referenceKind;
public MethodHandleInfo(MethodHandle mh) throws ReflectiveOperationException {
MemberName mn = mh.internalMemberName();
this.declaringClass = mn.getDeclaringClass();
this.name = mn.getName();
this.methodType = mn.getMethodType();
this.referenceKind = mn.getReferenceKind();
}
public Class<?> getDeclaringClass() {
return declaringClass;
}
public String getName() {
return name;
}
public MethodType getMethodType() {
return methodType;
}
public int getReferenceKind() {
return referenceKind;
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -29,6 +29,7 @@ import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.AccessibleObject; import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandleStatics.*;
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
/** /**
@ -41,76 +42,28 @@ class MethodHandleNatives {
private MethodHandleNatives() { } // static only private MethodHandleNatives() { } // static only
/// MethodName support /// MemberName support
static native void init(MemberName self, Object ref); static native void init(MemberName self, Object ref);
static native void expand(MemberName self); static native void expand(MemberName self);
static native void resolve(MemberName self, Class<?> caller); static native MemberName resolve(MemberName self, Class<?> caller) throws LinkageError;
static native int getMembers(Class<?> defc, String matchName, String matchSig, static native int getMembers(Class<?> defc, String matchName, String matchSig,
int matchFlags, Class<?> caller, int skip, MemberName[] results); int matchFlags, Class<?> caller, int skip, MemberName[] results);
/// Field layout queries parallel to sun.misc.Unsafe:
static native long objectFieldOffset(MemberName self); // e.g., returns vmindex
static native long staticFieldOffset(MemberName self); // e.g., returns vmindex
static native Object staticFieldBase(MemberName self); // e.g., returns clazz
static native Object getMemberVMInfo(MemberName self); // returns {vmindex,vmtarget}
/// MethodHandle support /// MethodHandle support
/** Initialize the method handle to adapt the call. */
static native void init(AdapterMethodHandle self, MethodHandle target, int argnum);
/** Initialize the method handle to call the correct method, directly. */
static native void init(BoundMethodHandle self, Object target, int argnum);
/** Initialize the method handle to call as if by an invoke* instruction. */
static native void init(DirectMethodHandle self, Object ref, boolean doDispatch, Class<?> caller);
/** Initialize a method type, once per form. */
static native void init(MethodType self);
/** Fetch the vmtarget field.
* It will be sanitized as necessary to avoid exposing non-Java references.
* This routine is for debugging and reflection.
*/
static native Object getTarget(MethodHandle self, int format);
/** Fetch the name of the handled method, if available.
* This routine is for debugging and reflection.
*/
static MemberName getMethodName(MethodHandle self) {
return (MemberName) getTarget(self, ETF_METHOD_NAME);
}
/** Fetch the reflective version of the handled method, if available.
*/
static AccessibleObject getTargetMethod(MethodHandle self) {
return (AccessibleObject) getTarget(self, ETF_REFLECT_METHOD);
}
/** Fetch the target of this method handle.
* If it directly targets a method, return a MemberName for the method.
* If it is chained to another method handle, return that handle.
*/
static Object getTargetInfo(MethodHandle self) {
return getTarget(self, ETF_HANDLE_OR_METHOD_NAME);
}
static Object[] makeTarget(Class<?> defc, String name, String sig, int mods, Class<?> refc) {
return new Object[] { defc, name, sig, mods, refc };
}
/** Fetch MH-related JVM parameter. /** Fetch MH-related JVM parameter.
* which=0 retrieves MethodHandlePushLimit * which=0 retrieves MethodHandlePushLimit
* which=1 retrieves stack slot push size (in address units) * which=1 retrieves stack slot push size (in address units)
*/ */
static native int getConstant(int which); static native int getConstant(int which);
/** Java copy of MethodHandlePushLimit in range 2..255. */
static final int JVM_PUSH_LIMIT;
/** JVM stack motion (in words) after one slot is pushed, usually -1.
*/
static final int JVM_STACK_MOVE_UNIT;
/** Which conv-ops are implemented by the JVM? */
static final int CONV_OP_IMPLEMENTED_MASK;
/** Derived mode flag. Only false on some old JVM implementations. */
static final boolean HAVE_RICOCHET_FRAMES;
static final int OP_ROT_ARGS_DOWN_LIMIT_BIAS;
static final boolean COUNT_GWT; static final boolean COUNT_GWT;
/// CallSite support /// CallSite support
@ -122,16 +75,10 @@ class MethodHandleNatives {
private static native void registerNatives(); private static native void registerNatives();
static { static {
registerNatives(); registerNatives();
int k;
JVM_PUSH_LIMIT = getConstant(Constants.GC_JVM_PUSH_LIMIT);
JVM_STACK_MOVE_UNIT = getConstant(Constants.GC_JVM_STACK_MOVE_UNIT);
k = getConstant(Constants.GC_CONV_OP_IMPLEMENTED_MASK);
CONV_OP_IMPLEMENTED_MASK = (k != 0) ? k : DEFAULT_CONV_OP_IMPLEMENTED_MASK;
k = getConstant(Constants.GC_OP_ROT_ARGS_DOWN_LIMIT_BIAS);
OP_ROT_ARGS_DOWN_LIMIT_BIAS = (k != 0) ? (byte)k : -1;
HAVE_RICOCHET_FRAMES = (CONV_OP_IMPLEMENTED_MASK & (1<<OP_COLLECT_ARGS)) != 0;
COUNT_GWT = getConstant(Constants.GC_COUNT_GWT) != 0; COUNT_GWT = getConstant(Constants.GC_COUNT_GWT) != 0;
//sun.reflect.Reflection.registerMethodsToFilter(MethodHandleImpl.class, "init");
// The JVM calls MethodHandleNatives.<clinit>. Cascade the <clinit> calls as needed:
MethodHandleImpl.initStatics();
} }
// All compile-time constants go here. // All compile-time constants go here.
@ -140,16 +87,8 @@ class MethodHandleNatives {
Constants() { } // static only Constants() { } // static only
// MethodHandleImpl // MethodHandleImpl
static final int // for getConstant static final int // for getConstant
GC_JVM_PUSH_LIMIT = 0, GC_COUNT_GWT = 4,
GC_JVM_STACK_MOVE_UNIT = 1, GC_LAMBDA_SUPPORT = 5;
GC_CONV_OP_IMPLEMENTED_MASK = 2,
GC_OP_ROT_ARGS_DOWN_LIMIT_BIAS = 3,
GC_COUNT_GWT = 4;
static final int
ETF_HANDLE_OR_METHOD_NAME = 0, // all available data (immediate MH or method)
ETF_DIRECT_HANDLE = 1, // ultimate method handle (will be a DMH, may be self)
ETF_METHOD_NAME = 2, // ultimate method as MemberName
ETF_REFLECT_METHOD = 3; // ultimate method as java.lang.reflect object (sans refClass)
// MemberName // MemberName
// The JVM uses values of -2 and above for vtable indexes. // The JVM uses values of -2 and above for vtable indexes.
@ -162,65 +101,11 @@ class MethodHandleNatives {
MN_IS_CONSTRUCTOR = 0x00020000, // constructor MN_IS_CONSTRUCTOR = 0x00020000, // constructor
MN_IS_FIELD = 0x00040000, // field MN_IS_FIELD = 0x00040000, // field
MN_IS_TYPE = 0x00080000, // nested type MN_IS_TYPE = 0x00080000, // nested type
MN_SEARCH_SUPERCLASSES = 0x00100000, // for MHN.getMembers MN_REFERENCE_KIND_SHIFT = 24, // refKind
MN_SEARCH_INTERFACES = 0x00200000, // for MHN.getMembers MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT,
VM_INDEX_UNINITIALIZED = -99; // The SEARCH_* bits are not for MN.flags but for the matchFlags argument of MHN.getMembers:
MN_SEARCH_SUPERCLASSES = 0x00100000,
// BoundMethodHandle MN_SEARCH_INTERFACES = 0x00200000;
/** Constants for decoding the vmargslot field, which contains 2 values. */
static final int
ARG_SLOT_PUSH_SHIFT = 16,
ARG_SLOT_MASK = (1<<ARG_SLOT_PUSH_SHIFT)-1;
// AdapterMethodHandle
/** Conversions recognized by the JVM.
* They must align with the constants in java.lang.invoke.AdapterMethodHandle,
* in the JVM file hotspot/src/share/vm/classfile/javaClasses.hpp.
*/
static final int
OP_RETYPE_ONLY = 0x0, // no argument changes; straight retype
OP_RETYPE_RAW = 0x1, // straight retype, trusted (void->int, Object->T)
OP_CHECK_CAST = 0x2, // ref-to-ref conversion; requires a Class argument
OP_PRIM_TO_PRIM = 0x3, // converts from one primitive to another
OP_REF_TO_PRIM = 0x4, // unboxes a wrapper to produce a primitive
OP_PRIM_TO_REF = 0x5, // boxes a primitive into a wrapper
OP_SWAP_ARGS = 0x6, // swap arguments (vminfo is 2nd arg)
OP_ROT_ARGS = 0x7, // rotate arguments (vminfo is displaced arg)
OP_DUP_ARGS = 0x8, // duplicates one or more arguments (at TOS)
OP_DROP_ARGS = 0x9, // remove one or more argument slots
OP_COLLECT_ARGS = 0xA, // combine arguments using an auxiliary function
OP_SPREAD_ARGS = 0xB, // expand in place a varargs array (of known size)
OP_FOLD_ARGS = 0xC, // combine but do not remove arguments; prepend result
//OP_UNUSED_13 = 0xD, // unused code, perhaps for reified argument lists
CONV_OP_LIMIT = 0xE; // limit of CONV_OP enumeration
/** Shift and mask values for decoding the AMH.conversion field.
* These numbers are shared with the JVM for creating AMHs.
*/
static final int
CONV_OP_MASK = 0xF00, // this nybble contains the conversion op field
CONV_TYPE_MASK = 0x0F, // fits T_ADDRESS and below
CONV_VMINFO_MASK = 0x0FF, // LSB is reserved for JVM use
CONV_VMINFO_SHIFT = 0, // position of bits in CONV_VMINFO_MASK
CONV_OP_SHIFT = 8, // position of bits in CONV_OP_MASK
CONV_DEST_TYPE_SHIFT = 12, // byte 2 has the adapter BasicType (if needed)
CONV_SRC_TYPE_SHIFT = 16, // byte 2 has the source BasicType (if needed)
CONV_STACK_MOVE_SHIFT = 20, // high 12 bits give signed SP change
CONV_STACK_MOVE_MASK = (1 << (32 - CONV_STACK_MOVE_SHIFT)) - 1;
/** Which conv-ops are implemented by the JVM? */
static final int DEFAULT_CONV_OP_IMPLEMENTED_MASK =
// Value to use if the corresponding JVM query fails.
((1<<OP_RETYPE_ONLY)
|(1<<OP_RETYPE_RAW)
|(1<<OP_CHECK_CAST)
|(1<<OP_PRIM_TO_PRIM)
|(1<<OP_REF_TO_PRIM)
|(1<<OP_SWAP_ARGS)
|(1<<OP_ROT_ARGS)
|(1<<OP_DUP_ARGS)
|(1<<OP_DROP_ARGS)
//|(1<<OP_SPREAD_ARGS)
);
/** /**
* Basic types as encoded in the JVM. These code values are not * Basic types as encoded in the JVM. These code values are not
@ -242,10 +127,55 @@ class MethodHandleNatives {
//T_ADDRESS = 15 //T_ADDRESS = 15
T_ILLEGAL = 99; T_ILLEGAL = 99;
/**
* Constant pool entry types.
*/
static final byte
CONSTANT_Utf8 = 1,
CONSTANT_Integer = 3,
CONSTANT_Float = 4,
CONSTANT_Long = 5,
CONSTANT_Double = 6,
CONSTANT_Class = 7,
CONSTANT_String = 8,
CONSTANT_Fieldref = 9,
CONSTANT_Methodref = 10,
CONSTANT_InterfaceMethodref = 11,
CONSTANT_NameAndType = 12,
CONSTANT_MethodHandle = 15, // JSR 292
CONSTANT_MethodType = 16, // JSR 292
CONSTANT_InvokeDynamic = 18,
CONSTANT_LIMIT = 19; // Limit to tags found in classfiles
/**
* Access modifier flags.
*/
static final char
ACC_PUBLIC = 0x0001,
ACC_PRIVATE = 0x0002,
ACC_PROTECTED = 0x0004,
ACC_STATIC = 0x0008,
ACC_FINAL = 0x0010,
ACC_SYNCHRONIZED = 0x0020,
ACC_VOLATILE = 0x0040,
ACC_TRANSIENT = 0x0080,
ACC_NATIVE = 0x0100,
ACC_INTERFACE = 0x0200,
ACC_ABSTRACT = 0x0400,
ACC_STRICT = 0x0800,
ACC_SYNTHETIC = 0x1000,
ACC_ANNOTATION = 0x2000,
ACC_ENUM = 0x4000,
// aliases:
ACC_SUPER = ACC_SYNCHRONIZED,
ACC_BRIDGE = ACC_VOLATILE,
ACC_VARARGS = ACC_TRANSIENT;
/** /**
* Constant pool reference-kind codes, as used by CONSTANT_MethodHandle CP entries. * Constant pool reference-kind codes, as used by CONSTANT_MethodHandle CP entries.
*/ */
static final int static final byte
REF_NONE = 0, // null value
REF_getField = 1, REF_getField = 1,
REF_getStatic = 2, REF_getStatic = 2,
REF_putField = 3, REF_putField = 3,
@ -254,9 +184,67 @@ class MethodHandleNatives {
REF_invokeStatic = 6, REF_invokeStatic = 6,
REF_invokeSpecial = 7, REF_invokeSpecial = 7,
REF_newInvokeSpecial = 8, REF_newInvokeSpecial = 8,
REF_invokeInterface = 9; REF_invokeInterface = 9,
REF_LIMIT = 10;
} }
static boolean refKindIsValid(int refKind) {
return (refKind > REF_NONE && refKind < REF_LIMIT);
}
static boolean refKindIsField(byte refKind) {
assert(refKindIsValid(refKind));
return (refKind <= REF_putStatic);
}
static boolean refKindIsGetter(byte refKind) {
assert(refKindIsValid(refKind));
return (refKind <= REF_getStatic);
}
static boolean refKindIsSetter(byte refKind) {
return refKindIsField(refKind) && !refKindIsGetter(refKind);
}
static boolean refKindIsMethod(byte refKind) {
return !refKindIsField(refKind) && (refKind != REF_newInvokeSpecial);
}
static boolean refKindHasReceiver(byte refKind) {
assert(refKindIsValid(refKind));
return (refKind & 1) != 0;
}
static boolean refKindIsStatic(byte refKind) {
return !refKindHasReceiver(refKind) && (refKind != REF_newInvokeSpecial);
}
static boolean refKindDoesDispatch(byte refKind) {
assert(refKindIsValid(refKind));
return (refKind == REF_invokeVirtual ||
refKind == REF_invokeInterface);
}
static {
final int HR_MASK = ((1 << REF_getField) |
(1 << REF_putField) |
(1 << REF_invokeVirtual) |
(1 << REF_invokeSpecial) |
(1 << REF_invokeInterface)
);
for (byte refKind = REF_NONE+1; refKind < REF_LIMIT; refKind++) {
assert(refKindHasReceiver(refKind) == (((1<<refKind) & HR_MASK) != 0)) : refKind;
}
}
static String refKindName(byte refKind) {
assert(refKindIsValid(refKind));
return REFERENCE_KIND_NAME[refKind];
}
private static String[] REFERENCE_KIND_NAME = {
null,
"getField",
"getStatic",
"putField",
"putStatic",
"invokeVirtual",
"invokeStatic",
"invokeSpecial",
"newInvokeSpecial",
"invokeInterface"
};
private static native int getNamedCon(int which, Object[] name); private static native int getNamedCon(int which, Object[] name);
static boolean verifyConstants() { static boolean verifyConstants() {
Object[] box = { null }; Object[] box = { null };
@ -275,18 +263,13 @@ class MethodHandleNatives {
continue; continue;
} }
throw new InternalError(err); throw new InternalError(err);
} catch (Exception ex) { } catch (NoSuchFieldException | IllegalAccessException ex) {
if (ex instanceof NoSuchFieldException) {
String err = (name+": JVM has "+vmval+" which Java does not define"); String err = (name+": JVM has "+vmval+" which Java does not define");
// ignore exotic ops the JVM cares about; we just wont issue them // ignore exotic ops the JVM cares about; we just wont issue them
if (name.startsWith("OP_") || name.startsWith("GC_")) { //System.err.println("warning: "+err);
System.err.println("warning: "+err);
continue; continue;
} }
} }
throw new InternalError(name+": access failed, got "+ex);
}
}
return true; return true;
} }
static { static {
@ -299,18 +282,21 @@ class MethodHandleNatives {
/** /**
* The JVM is linking an invokedynamic instruction. Create a reified call site for it. * The JVM is linking an invokedynamic instruction. Create a reified call site for it.
*/ */
static CallSite makeDynamicCallSite(MethodHandle bootstrapMethod, static MemberName linkCallSite(Object callerObj,
String name, MethodType type, Object bootstrapMethodObj,
Object info, Object nameObj, Object typeObj,
MemberName callerMethod, int callerBCI) { Object staticArguments,
return CallSite.makeSite(bootstrapMethod, name, type, info, callerMethod, callerBCI); Object[] appendixResult) {
} MethodHandle bootstrapMethod = (MethodHandle)bootstrapMethodObj;
Class<?> caller = (Class<?>)callerObj;
/** String name = nameObj.toString().intern();
* Called by the JVM to check the length of a spread array. MethodType type = (MethodType)typeObj;
*/ appendixResult[0] = CallSite.makeSite(bootstrapMethod,
static void checkSpreadArgument(Object av, int n) { name,
MethodHandleStatics.checkSpreadArgument(av, n); type,
staticArguments,
caller);
return Invokers.linkToCallSiteMethod(type);
} }
/** /**
@ -321,71 +307,64 @@ class MethodHandleNatives {
} }
/** /**
* The JVM wants to use a MethodType with inexact invoke. Give the runtime fair warning. * The JVM wants to link a call site that requires a dynamic type check.
* Name is a type-checking invoker, invokeExact or invoke.
* Return a JVM method (MemberName) to handle the invoking.
* The method assumes the following arguments on the stack:
* 0: the method handle being invoked
* 1-N: the arguments to the method handle invocation
* N+1: an implicitly added type argument (the given MethodType)
*/ */
static void notifyGenericMethodType(MethodType type) { static MemberName linkMethod(Class<?> callerClass, int refKind,
type.form().notifyGenericMethodType(); Class<?> defc, String name, Object type,
Object[] appendixResult) {
if (!TRACE_METHOD_LINKAGE)
return linkMethodImpl(callerClass, refKind, defc, name, type, appendixResult);
return linkMethodTracing(callerClass, refKind, defc, name, type, appendixResult);
} }
static MemberName linkMethodImpl(Class<?> callerClass, int refKind,
/** Class<?> defc, String name, Object type,
* The JVM wants to raise an exception. Here's the path. Object[] appendixResult) {
*/ if (defc != MethodHandle.class || refKind != REF_invokeVirtual)
static void raiseException(int code, Object actual, Object required) { throw new LinkageError("no such method "+defc.getName()+"."+name+type);
String message = null; switch (name) {
switch (code) { case "invoke":
case 190: // arraylength return Invokers.genericInvokerMethod(callerClass, type, appendixResult);
case "invokeExact":
return Invokers.exactInvokerMethod(callerClass, type, appendixResult);
}
throw new UnsupportedOperationException("linkMethod "+name);
}
// Tracing logic:
static MemberName linkMethodTracing(Class<?> callerClass, int refKind,
Class<?> defc, String name, Object type,
Object[] appendixResult) {
System.out.println("linkMethod "+defc.getName()+"."+
name+type+"/"+Integer.toHexString(refKind));
try { try {
String reqLength = ""; MemberName res = linkMethodImpl(callerClass, refKind, defc, name, type, appendixResult);
if (required instanceof AdapterMethodHandle) { System.out.println("linkMethod => "+res+" + "+appendixResult[0]);
int conv = ((AdapterMethodHandle)required).getConversion(); return res;
int spChange = AdapterMethodHandle.extractStackMove(conv); } catch (Throwable ex) {
reqLength = " of length "+(spChange+1); System.out.println("linkMethod => throw "+ex);
} throw ex;
int actualLength = actual == null ? 0 : java.lang.reflect.Array.getLength(actual);
message = "required array"+reqLength+", but encountered wrong length "+actualLength;
break;
} catch (IllegalArgumentException ex) {
}
required = Object[].class; // should have been an array
code = 192; // checkcast
break;
case 191: // athrow
// JVM is asking us to wrap an exception which happened during resolving
if (required == BootstrapMethodError.class) {
throw new BootstrapMethodError((Throwable) actual);
}
break;
}
// disregard the identity of the actual object, if it is not a class:
if (message == null) {
if (!(actual instanceof Class) && !(actual instanceof MethodType))
actual = actual.getClass();
if (actual != null)
message = "required "+required+" but encountered "+actual;
else
message = "required "+required;
}
switch (code) {
case 190: // arraylength
throw new ArrayIndexOutOfBoundsException(message);
case 50: //_aaload
throw new ClassCastException(message);
case 192: // checkcast
throw new ClassCastException(message);
default:
throw new InternalError("unexpected code "+code+": "+message);
} }
} }
/** /**
* The JVM is resolving a CONSTANT_MethodHandle CP entry. And it wants our help. * The JVM is resolving a CONSTANT_MethodHandle CP entry. And it wants our help.
* It will make an up-call to this method. (Do not change the name or signature.) * It will make an up-call to this method. (Do not change the name or signature.)
* The type argument is a Class for field requests and a MethodType for non-fields.
* <p>
* Recent versions of the JVM may also pass a resolved MemberName for the type.
* In that case, the name is ignored and may be null.
*/ */
static MethodHandle linkMethodHandleConstant(Class<?> callerClass, int refKind, static MethodHandle linkMethodHandleConstant(Class<?> callerClass, int refKind,
Class<?> defc, String name, Object type) { Class<?> defc, String name, Object type) {
try { try {
Lookup lookup = IMPL_LOOKUP.in(callerClass); Lookup lookup = IMPL_LOOKUP.in(callerClass);
return lookup.linkMethodHandleConstant(refKind, defc, name, type); assert(refKindIsValid(refKind));
return lookup.linkMethodHandleConstant((byte) refKind, defc, name, type);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
Error err = new IncompatibleClassChangeError(); Error err = new IncompatibleClassChangeError();
err.initCause(ex); err.initCause(ex);

View file

@ -27,6 +27,7 @@ package java.lang.invoke;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import sun.misc.Unsafe;
/** /**
* This class consists exclusively of static names internal to the * This class consists exclusively of static names internal to the
@ -38,16 +39,30 @@ import java.security.PrivilegedAction;
private MethodHandleStatics() { } // do not instantiate private MethodHandleStatics() { } // do not instantiate
static final Unsafe UNSAFE = Unsafe.getUnsafe();
static final boolean DEBUG_METHOD_HANDLE_NAMES; static final boolean DEBUG_METHOD_HANDLE_NAMES;
static final boolean DUMP_CLASS_FILES;
static final boolean TRACE_INTERPRETER;
static final boolean TRACE_METHOD_LINKAGE;
static final Integer COMPILE_THRESHOLD;
static { static {
final Object[] values = { false }; final Object[] values = { false, false, false, false, null };
AccessController.doPrivileged(new PrivilegedAction<Void>() { AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() { public Void run() {
values[0] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES"); values[0] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES");
values[1] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DUMP_CLASS_FILES");
values[2] = Boolean.getBoolean("java.lang.invoke.MethodHandle.TRACE_INTERPRETER");
values[3] = Boolean.getBoolean("java.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE");
values[4] = Integer.getInteger("java.lang.invoke.MethodHandle.COMPILE_THRESHOLD");
return null; return null;
} }
}); });
DEBUG_METHOD_HANDLE_NAMES = (Boolean) values[0]; DEBUG_METHOD_HANDLE_NAMES = (Boolean) values[0];
DUMP_CLASS_FILES = (Boolean) values[1];
TRACE_INTERPRETER = (Boolean) values[2];
TRACE_METHOD_LINKAGE = (Boolean) values[3];
COMPILE_THRESHOLD = (Integer) values[4];
} }
/*non-public*/ static String getNameString(MethodHandle target, MethodType type) { /*non-public*/ static String getNameString(MethodHandle target, MethodType type) {
@ -55,7 +70,7 @@ import java.security.PrivilegedAction;
type = target.type(); type = target.type();
MemberName name = null; MemberName name = null;
if (target != null) if (target != null)
name = MethodHandleNatives.getMethodName(target); name = target.internalMemberName();
if (name == null) if (name == null)
return "invoke" + type; return "invoke" + type;
return name.getName() + type; return name.getName() + type;
@ -77,20 +92,6 @@ import java.security.PrivilegedAction;
return str + target.type(); return str + target.type();
} }
static void checkSpreadArgument(Object av, int n) {
if (av == null) {
if (n == 0) return;
} else if (av instanceof Object[]) {
int len = ((Object[])av).length;
if (len == n) return;
} else {
int len = java.lang.reflect.Array.getLength(av);
if (len == n) return;
}
// fall through to error:
throw newIllegalArgumentException("Array is not of length "+n);
}
// handy shared exception makers (they simplify the common case code) // handy shared exception makers (they simplify the common case code)
/*non-public*/ static RuntimeException newIllegalStateException(String message) { /*non-public*/ static RuntimeException newIllegalStateException(String message) {
return new IllegalStateException(message); return new IllegalStateException(message);
@ -110,6 +111,9 @@ import java.security.PrivilegedAction;
/*non-public*/ static Error uncaughtException(Exception ex) { /*non-public*/ static Error uncaughtException(Exception ex) {
throw new InternalError("uncaught exception", ex); throw new InternalError("uncaught exception", ex);
} }
static Error NYI() {
throw new AssertionError("NYI");
}
private static String message(String message, Object obj) { private static String message(String message, Object obj) {
if (obj != null) message = message + ": " + obj; if (obj != null) message = message + ": " + obj;
return message; return message;

View file

@ -26,7 +26,6 @@
package java.lang.invoke; package java.lang.invoke;
import java.lang.reflect.*; import java.lang.reflect.*;
import sun.invoke.WrapperInstance;
import sun.invoke.util.ValueConversions; import sun.invoke.util.ValueConversions;
import sun.invoke.util.VerifyAccess; import sun.invoke.util.VerifyAccess;
import sun.invoke.util.Wrapper; import sun.invoke.util.Wrapper;
@ -174,6 +173,8 @@ public class MethodHandles {
* Both {@code MT} and the field type {@code FT} are documented as a parameter named {@code type}. * Both {@code MT} and the field type {@code FT} are documented as a parameter named {@code type}.
* The formal parameter {@code this} stands for the self-reference of type {@code C}; * The formal parameter {@code this} stands for the self-reference of type {@code C};
* if it is present, it is always the leading argument to the method handle invocation. * if it is present, it is always the leading argument to the method handle invocation.
* (In the case of some {@code protected} members, {@code this} may be
* restricted in type to the lookup class; see below.)
* The name {@code arg} stands for all the other method handle arguments. * The name {@code arg} stands for all the other method handle arguments.
* In the code examples for the Core Reflection API, the name {@code thisOrNull} * In the code examples for the Core Reflection API, the name {@code thisOrNull}
* stands for a null reference if the accessed method or field is static, * stands for a null reference if the accessed method or field is static,
@ -244,6 +245,18 @@ public class MethodHandles {
* is exactly equivalent to executing the compiled and resolved call to {@code M}. * is exactly equivalent to executing the compiled and resolved call to {@code M}.
* The same point is true of fields and constructors. * The same point is true of fields and constructors.
* <p> * <p>
* If the desired member is {@code protected}, the usual JVM rules apply,
* including the requirement that the lookup class must be either be in the
* same package as the desired member, or must inherit that member.
* (See the Java Virtual Machine Specification, sections 4.9.2, 5.4.3.5, and 6.4.)
* In addition, if the desired member is a non-static field or method
* in a different package, the resulting method handle may only be applied
* to objects of the lookup class or one of its subclasses.
* This requirement is enforced by narrowing the type of the leading
* {@code this} parameter from {@code C}
* (which will necessarily be a superclass of the lookup class)
* to the lookup class itself.
* <p>
* In some cases, access between nested classes is obtained by the Java compiler by creating * In some cases, access between nested classes is obtained by the Java compiler by creating
* an wrapper method to access a private method of another class * an wrapper method to access a private method of another class
* in the same top-level declaration. * in the same top-level declaration.
@ -582,19 +595,9 @@ public class MethodHandles {
*/ */
public public
MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
MemberName method = resolveOrFail(refc, name, type, true); MemberName method = resolveOrFail(REF_invokeStatic, refc, name, type);
checkSecurityManager(refc, method); // stack walk magic: do not refactor checkSecurityManager(refc, method); // stack walk magic: do not refactor
return accessStatic(refc, method); return getDirectMethod(REF_invokeStatic, refc, method);
}
private
MethodHandle accessStatic(Class<?> refc, MemberName method) throws IllegalAccessException {
checkMethod(refc, method, true);
return MethodHandleImpl.findMethod(method, false, lookupClassOrNull());
}
private
MethodHandle resolveStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
MemberName method = resolveOrFail(refc, name, type, true);
return accessStatic(refc, method);
} }
/** /**
@ -609,6 +612,11 @@ public class MethodHandles {
* (The dispatching action is identical with that performed by an * (The dispatching action is identical with that performed by an
* {@code invokevirtual} or {@code invokeinterface} instruction.) * {@code invokevirtual} or {@code invokeinterface} instruction.)
* <p> * <p>
* The first argument will be of type {@code refc} if the lookup
* class has full privileges to access the member. Otherwise
* the member must be {@code protected} and the first argument
* will be restricted in type to the lookup class.
* <p>
* The returned method handle will have * The returned method handle will have
* {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
* the method's variable arity modifier bit ({@code 0x0080}) is set. * the method's variable arity modifier bit ({@code 0x0080}) is set.
@ -636,18 +644,22 @@ public class MethodHandles {
* @throws NullPointerException if any argument is null * @throws NullPointerException if any argument is null
*/ */
public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
MemberName method = resolveOrFail(refc, name, type, false); if (refc == MethodHandle.class) {
MethodHandle mh = findVirtualForMH(name, type);
if (mh != null) return mh;
}
byte refKind = (refc.isInterface() ? REF_invokeInterface : REF_invokeVirtual);
MemberName method = resolveOrFail(refKind, refc, name, type);
checkSecurityManager(refc, method); // stack walk magic: do not refactor checkSecurityManager(refc, method); // stack walk magic: do not refactor
return accessVirtual(refc, method); return getDirectMethod(refKind, refc, method);
} }
private MethodHandle resolveVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { private MethodHandle findVirtualForMH(String name, MethodType type) {
MemberName method = resolveOrFail(refc, name, type, false); // these names require special lookups because of the implicit MethodType argument
return accessVirtual(refc, method); if ("invoke".equals(name))
} return invoker(type);
private MethodHandle accessVirtual(Class<?> refc, MemberName method) throws IllegalAccessException { if ("invokeExact".equals(name))
checkMethod(refc, method, false); return exactInvoker(type);
MethodHandle mh = MethodHandleImpl.findMethod(method, true, lookupClassOrNull()); return null;
return restrictProtectedReceiver(method, mh);
} }
/** /**
@ -678,36 +690,9 @@ public class MethodHandles {
*/ */
public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
String name = "<init>"; String name = "<init>";
MemberName ctor = resolveOrFail(refc, name, type, false, false, lookupClassOrNull()); MemberName ctor = resolveOrFail(REF_newInvokeSpecial, refc, name, type);
checkSecurityManager(refc, ctor); // stack walk magic: do not refactor checkSecurityManager(refc, ctor); // stack walk magic: do not refactor
return accessConstructor(refc, ctor); return getDirectConstructor(refc, ctor);
}
private MethodHandle accessConstructor(Class<?> refc, MemberName ctor) throws IllegalAccessException {
assert(ctor.isConstructor());
checkAccess(refc, ctor);
MethodHandle rawMH = MethodHandleImpl.findMethod(ctor, false, lookupClassOrNull());
MethodHandle allocMH = MethodHandleImpl.makeAllocator(rawMH);
return fixVarargs(allocMH, rawMH);
}
private MethodHandle resolveConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
String name = "<init>";
MemberName ctor = resolveOrFail(refc, name, type, false, false, lookupClassOrNull());
return accessConstructor(refc, ctor);
}
/** Return a version of MH which matches matchMH w.r.t. isVarargsCollector. */
private static MethodHandle fixVarargs(MethodHandle mh, MethodHandle matchMH) {
boolean va1 = mh.isVarargsCollector();
boolean va2 = matchMH.isVarargsCollector();
if (va1 == va2) {
return mh;
} else if (va2) {
MethodType type = mh.type();
int arity = type.parameterCount();
return mh.asVarargsCollector(type.parameterType(arity-1));
} else {
return mh.asFixedArity();
}
} }
/** /**
@ -747,21 +732,10 @@ public class MethodHandles {
public MethodHandle findSpecial(Class<?> refc, String name, MethodType type, public MethodHandle findSpecial(Class<?> refc, String name, MethodType type,
Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException { Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
checkSpecialCaller(specialCaller); checkSpecialCaller(specialCaller);
MemberName method = resolveOrFail(refc, name, type, false, false, specialCaller); Lookup specialLookup = this.in(specialCaller);
MemberName method = specialLookup.resolveOrFail(REF_invokeSpecial, refc, name, type);
checkSecurityManager(refc, method); // stack walk magic: do not refactor checkSecurityManager(refc, method); // stack walk magic: do not refactor
return accessSpecial(refc, method, specialCaller); return specialLookup.getDirectMethod(REF_invokeSpecial, refc, method);
}
private MethodHandle accessSpecial(Class<?> refc, MemberName method,
Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
checkMethod(refc, method, false);
MethodHandle mh = MethodHandleImpl.findMethod(method, false, specialCaller);
return restrictReceiver(method, mh, specialCaller);
}
private MethodHandle resolveSpecial(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
Class<?> specialCaller = lookupClass();
checkSpecialCaller(specialCaller);
MemberName method = resolveOrFail(refc, name, type, false, false, specialCaller);
return accessSpecial(refc, method, specialCaller);
} }
/** /**
@ -782,13 +756,9 @@ public class MethodHandles {
* @throws NullPointerException if any argument is null * @throws NullPointerException if any argument is null
*/ */
public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
MemberName field = resolveOrFail(refc, name, type, false); MemberName field = resolveOrFail(REF_getField, refc, name, type);
checkSecurityManager(refc, field); // stack walk magic: do not refactor checkSecurityManager(refc, field); // stack walk magic: do not refactor
return makeAccessor(refc, field, false, false, 0); return getDirectField(REF_getField, refc, field);
}
private MethodHandle resolveGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
MemberName field = resolveOrFail(refc, name, type, false);
return makeAccessor(refc, field, false, false, 0);
} }
/** /**
@ -809,13 +779,9 @@ public class MethodHandles {
* @throws NullPointerException if any argument is null * @throws NullPointerException if any argument is null
*/ */
public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
MemberName field = resolveOrFail(refc, name, type, false); MemberName field = resolveOrFail(REF_putField, refc, name, type);
checkSecurityManager(refc, field); // stack walk magic: do not refactor checkSecurityManager(refc, field); // stack walk magic: do not refactor
return makeAccessor(refc, field, false, true, 0); return getDirectField(REF_putField, refc, field);
}
private MethodHandle resolveSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
MemberName field = resolveOrFail(refc, name, type, false);
return makeAccessor(refc, field, false, true, 0);
} }
/** /**
@ -835,13 +801,9 @@ public class MethodHandles {
* @throws NullPointerException if any argument is null * @throws NullPointerException if any argument is null
*/ */
public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
MemberName field = resolveOrFail(refc, name, type, true); MemberName field = resolveOrFail(REF_getStatic, refc, name, type);
checkSecurityManager(refc, field); // stack walk magic: do not refactor checkSecurityManager(refc, field); // stack walk magic: do not refactor
return makeAccessor(refc, field, false, false, 1); return getDirectField(REF_getStatic, refc, field);
}
private MethodHandle resolveStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
MemberName field = resolveOrFail(refc, name, type, true);
return makeAccessor(refc, field, false, false, 1);
} }
/** /**
@ -861,13 +823,9 @@ public class MethodHandles {
* @throws NullPointerException if any argument is null * @throws NullPointerException if any argument is null
*/ */
public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
MemberName field = resolveOrFail(refc, name, type, true); MemberName field = resolveOrFail(REF_putStatic, refc, name, type);
checkSecurityManager(refc, field); // stack walk magic: do not refactor checkSecurityManager(refc, field); // stack walk magic: do not refactor
return makeAccessor(refc, field, false, true, 1); return getDirectField(REF_putStatic, refc, field);
}
private MethodHandle resolveStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
MemberName field = resolveOrFail(refc, name, type, true);
return makeAccessor(refc, field, false, true, 1);
} }
/** /**
@ -918,14 +876,10 @@ return mh1;
*/ */
public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
Class<? extends Object> refc = receiver.getClass(); // may get NPE Class<? extends Object> refc = receiver.getClass(); // may get NPE
MemberName method = resolveOrFail(refc, name, type, false); MemberName method = resolveOrFail(REF_invokeSpecial, refc, name, type);
checkSecurityManager(refc, method); // stack walk magic: do not refactor checkSecurityManager(refc, method); // stack walk magic: do not refactor
checkMethod(refc, method, false); MethodHandle mh = getDirectMethodNoRestrict(REF_invokeSpecial, refc, method);
MethodHandle dmh = MethodHandleImpl.findMethod(method, true, lookupClassOrNull()); return mh.bindReceiver(receiver).setVarargs(method);
MethodHandle bmh = MethodHandleImpl.bindReceiver(dmh, receiver);
if (bmh == null)
throw method.makeAccessException("no access", this);
return fixVarargs(bmh, dmh);
} }
/** /**
@ -951,12 +905,12 @@ return mh1;
*/ */
public MethodHandle unreflect(Method m) throws IllegalAccessException { public MethodHandle unreflect(Method m) throws IllegalAccessException {
MemberName method = new MemberName(m); MemberName method = new MemberName(m);
byte refKind = method.getReferenceKind();
if (refKind == REF_invokeSpecial)
refKind = REF_invokeVirtual;
assert(method.isMethod()); assert(method.isMethod());
if (m.isAccessible()) Lookup lookup = m.isAccessible() ? IMPL_LOOKUP : this;
return MethodHandleImpl.findMethod(method, true, /*no lookupClass*/ null); return lookup.getDirectMethod(refKind, method.getDeclaringClass(), method);
checkMethod(method.getDeclaringClass(), method, method.isStatic());
MethodHandle mh = MethodHandleImpl.findMethod(method, true, lookupClassOrNull());
return restrictProtectedReceiver(method, mh);
} }
/** /**
@ -982,12 +936,11 @@ return mh1;
*/ */
public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException { public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException {
checkSpecialCaller(specialCaller); checkSpecialCaller(specialCaller);
MemberName method = new MemberName(m); Lookup specialLookup = this.in(specialCaller);
MemberName method = new MemberName(m, true);
assert(method.isMethod()); assert(method.isMethod());
// ignore m.isAccessible: this is a new kind of access // ignore m.isAccessible: this is a new kind of access
checkMethod(m.getDeclaringClass(), method, false); return specialLookup.getDirectMethod(REF_invokeSpecial, method.getDeclaringClass(), method);
MethodHandle mh = MethodHandleImpl.findMethod(method, false, lookupClassOrNull());
return restrictReceiver(method, mh, specialCaller);
} }
/** /**
@ -1015,15 +968,8 @@ return mh1;
public MethodHandle unreflectConstructor(Constructor c) throws IllegalAccessException { public MethodHandle unreflectConstructor(Constructor c) throws IllegalAccessException {
MemberName ctor = new MemberName(c); MemberName ctor = new MemberName(c);
assert(ctor.isConstructor()); assert(ctor.isConstructor());
MethodHandle rawCtor; Lookup lookup = c.isAccessible() ? IMPL_LOOKUP : this;
if (c.isAccessible()) { return lookup.getDirectConstructor(ctor.getDeclaringClass(), ctor);
rawCtor = MethodHandleImpl.findMethod(ctor, false, /*no lookupClass*/ null);
} else {
checkAccess(c.getDeclaringClass(), ctor);
rawCtor = MethodHandleImpl.findMethod(ctor, false, lookupClassOrNull());
}
MethodHandle allocator = MethodHandleImpl.makeAllocator(rawCtor);
return fixVarargs(allocator, rawCtor);
} }
/** /**
@ -1041,7 +987,15 @@ return mh1;
* @throws NullPointerException if the argument is null * @throws NullPointerException if the argument is null
*/ */
public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { public MethodHandle unreflectGetter(Field f) throws IllegalAccessException {
return makeAccessor(f.getDeclaringClass(), new MemberName(f), f.isAccessible(), false, -1); return unreflectField(f, false);
}
private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException {
MemberName field = new MemberName(f, isSetter);
assert(isSetter
? MethodHandleNatives.refKindIsSetter(field.getReferenceKind())
: MethodHandleNatives.refKindIsGetter(field.getReferenceKind()));
Lookup lookup = f.isAccessible() ? IMPL_LOOKUP : this;
return lookup.getDirectField(field.getReferenceKind(), f.getDeclaringClass(), field);
} }
/** /**
@ -1059,33 +1013,22 @@ return mh1;
* @throws NullPointerException if the argument is null * @throws NullPointerException if the argument is null
*/ */
public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
return makeAccessor(f.getDeclaringClass(), new MemberName(f), f.isAccessible(), true, -1); return unreflectField(f, true);
} }
/// Helper methods, all package-private. /// Helper methods, all package-private.
MemberName resolveOrFail(Class<?> refc, String name, Class<?> type, boolean isStatic) throws NoSuchFieldException, IllegalAccessException { MemberName resolveOrFail(byte refKind, Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
checkSymbolicClass(refc); // do this before attempting to resolve checkSymbolicClass(refc); // do this before attempting to resolve
name.getClass(); type.getClass(); // NPE name.getClass(); type.getClass(); // NPE
int mods = (isStatic ? Modifier.STATIC : 0); return IMPL_NAMES.resolveOrFail(refKind, new MemberName(refc, name, type, refKind), lookupClassOrNull(),
return IMPL_NAMES.resolveOrFail(new MemberName(refc, name, type, mods), true, lookupClassOrNull(),
NoSuchFieldException.class); NoSuchFieldException.class);
} }
MemberName resolveOrFail(Class<?> refc, String name, MethodType type, boolean isStatic) throws NoSuchMethodException, IllegalAccessException { MemberName resolveOrFail(byte refKind, Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
checkSymbolicClass(refc); // do this before attempting to resolve checkSymbolicClass(refc); // do this before attempting to resolve
name.getClass(); type.getClass(); // NPE name.getClass(); type.getClass(); // NPE
int mods = (isStatic ? Modifier.STATIC : 0); return IMPL_NAMES.resolveOrFail(refKind, new MemberName(refc, name, type, refKind), lookupClassOrNull(),
return IMPL_NAMES.resolveOrFail(new MemberName(refc, name, type, mods), true, lookupClassOrNull(),
NoSuchMethodException.class);
}
MemberName resolveOrFail(Class<?> refc, String name, MethodType type, boolean isStatic,
boolean searchSupers, Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
checkSymbolicClass(refc); // do this before attempting to resolve
name.getClass(); type.getClass(); // NPE
int mods = (isStatic ? Modifier.STATIC : 0);
return IMPL_NAMES.resolveOrFail(new MemberName(refc, name, type, mods), searchSupers, specialCaller,
NoSuchMethodException.class); NoSuchMethodException.class);
} }
@ -1141,7 +1084,8 @@ return mh1;
// SecurityManager.checkMemberAccess [0] // SecurityManager.checkMemberAccess [0]
} }
void checkMethod(Class<?> refc, MemberName m, boolean wantStatic) throws IllegalAccessException { void checkMethod(byte refKind, Class<?> refc, MemberName m) throws IllegalAccessException {
boolean wantStatic = (refKind == REF_invokeStatic);
String message; String message;
if (m.isConstructor()) if (m.isConstructor())
message = "expected a method, not a constructor"; message = "expected a method, not a constructor";
@ -1150,26 +1094,43 @@ return mh1;
else if (wantStatic != m.isStatic()) else if (wantStatic != m.isStatic())
message = wantStatic ? "expected a static method" : "expected a non-static method"; message = wantStatic ? "expected a static method" : "expected a non-static method";
else else
{ checkAccess(refc, m); return; } { checkAccess(refKind, refc, m); return; }
throw m.makeAccessException(message, this); throw m.makeAccessException(message, this);
} }
void checkAccess(Class<?> refc, MemberName m) throws IllegalAccessException { void checkField(byte refKind, Class<?> refc, MemberName m) throws IllegalAccessException {
boolean wantStatic = !MethodHandleNatives.refKindHasReceiver(refKind);
String message;
if (wantStatic != m.isStatic())
message = wantStatic ? "expected a static field" : "expected a non-static field";
else
{ checkAccess(refKind, refc, m); return; }
throw m.makeAccessException(message, this);
}
void checkAccess(byte refKind, Class<?> refc, MemberName m) throws IllegalAccessException {
assert(m.referenceKindIsConsistentWith(refKind) &&
MethodHandleNatives.refKindIsValid(refKind) &&
(MethodHandleNatives.refKindIsField(refKind) == m.isField()));
int allowedModes = this.allowedModes; int allowedModes = this.allowedModes;
if (allowedModes == TRUSTED) return; if (allowedModes == TRUSTED) return;
int mods = m.getModifiers(); int mods = m.getModifiers();
if (Modifier.isFinal(mods) &&
MethodHandleNatives.refKindIsSetter(refKind))
throw m.makeAccessException("unexpected set of a final field", this);
if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers()) && allowedModes != 0) if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers()) && allowedModes != 0)
return; // common case return; // common case
int requestedModes = fixmods(mods); // adjust 0 => PACKAGE int requestedModes = fixmods(mods); // adjust 0 => PACKAGE
if ((requestedModes & allowedModes) != 0 if ((requestedModes & allowedModes) != 0) {
&& VerifyAccess.isMemberAccessible(refc, m.getDeclaringClass(), if (VerifyAccess.isMemberAccessible(refc, m.getDeclaringClass(),
mods, lookupClass(), allowedModes)) mods, lookupClass(), allowedModes))
return; return;
if (((requestedModes & ~allowedModes) & PROTECTED) != 0 } else {
&& (allowedModes & PACKAGE) != 0
&& VerifyAccess.isSamePackage(m.getDeclaringClass(), lookupClass()))
// Protected members can also be checked as if they were package-private. // Protected members can also be checked as if they were package-private.
if ((requestedModes & PROTECTED) != 0 && (allowedModes & PACKAGE) != 0
&& VerifyAccess.isSamePackage(m.getDeclaringClass(), lookupClass()))
return; return;
}
throw m.makeAccessException(accessFailedMessage(refc, m), this); throw m.makeAccessException(accessFailedMessage(refc, m), this);
} }
@ -1198,7 +1159,8 @@ return mh1;
private static final boolean ALLOW_NESTMATE_ACCESS = false; private static final boolean ALLOW_NESTMATE_ACCESS = false;
void checkSpecialCaller(Class<?> specialCaller) throws IllegalAccessException { private void checkSpecialCaller(Class<?> specialCaller) throws IllegalAccessException {
int allowedModes = this.allowedModes;
if (allowedModes == TRUSTED) return; if (allowedModes == TRUSTED) return;
if ((allowedModes & PRIVATE) == 0 if ((allowedModes & PRIVATE) == 0
|| (specialCaller != lookupClass() || (specialCaller != lookupClass()
@ -1208,7 +1170,7 @@ return mh1;
makeAccessException("no private access for invokespecial", this); makeAccessException("no private access for invokespecial", this);
} }
MethodHandle restrictProtectedReceiver(MemberName method, MethodHandle mh) throws IllegalAccessException { private boolean restrictProtectedReceiver(MemberName method) {
// The accessing class only has the right to use a protected member // The accessing class only has the right to use a protected member
// on itself or a subclass. Enforce that restriction, from JVMS 5.4.4, etc. // on itself or a subclass. Enforce that restriction, from JVMS 5.4.4, etc.
if (!method.isProtected() || method.isStatic() if (!method.isProtected() || method.isStatic()
@ -1217,52 +1179,82 @@ return mh1;
|| VerifyAccess.isSamePackage(method.getDeclaringClass(), lookupClass()) || VerifyAccess.isSamePackage(method.getDeclaringClass(), lookupClass())
|| (ALLOW_NESTMATE_ACCESS && || (ALLOW_NESTMATE_ACCESS &&
VerifyAccess.isSamePackageMember(method.getDeclaringClass(), lookupClass()))) VerifyAccess.isSamePackageMember(method.getDeclaringClass(), lookupClass())))
return mh; return false;
else return true;
return restrictReceiver(method, mh, lookupClass());
} }
MethodHandle restrictReceiver(MemberName method, MethodHandle mh, Class<?> caller) throws IllegalAccessException { private MethodHandle restrictReceiver(MemberName method, MethodHandle mh, Class<?> caller) throws IllegalAccessException {
assert(!method.isStatic()); assert(!method.isStatic());
Class<?> defc = method.getDeclaringClass(); // receiver type of mh is too wide // receiver type of mh is too wide; narrow to caller
if (defc.isInterface() || !defc.isAssignableFrom(caller)) { if (!method.getDeclaringClass().isAssignableFrom(caller)) {
throw method.makeAccessException("caller class must be a subclass below the method", caller); throw method.makeAccessException("caller class must be a subclass below the method", caller);
} }
MethodType rawType = mh.type(); MethodType rawType = mh.type();
if (rawType.parameterType(0) == caller) return mh; if (rawType.parameterType(0) == caller) return mh;
MethodType narrowType = rawType.changeParameterType(0, caller); MethodType narrowType = rawType.changeParameterType(0, caller);
MethodHandle narrowMH = MethodHandleImpl.convertArguments(mh, narrowType, rawType, 0); return mh.viewAsType(narrowType);
return fixVarargs(narrowMH, mh);
} }
MethodHandle makeAccessor(Class<?> refc, MemberName field, private MethodHandle getDirectMethod(byte refKind, Class<?> refc, MemberName method) throws IllegalAccessException {
boolean trusted, boolean isSetter, return getDirectMethodCommon(refKind, refc, method,
int checkStatic) throws IllegalAccessException { (refKind == REF_invokeSpecial ||
assert(field.isField()); (MethodHandleNatives.refKindHasReceiver(refKind) &&
if (checkStatic >= 0 && (checkStatic != 0) != field.isStatic()) restrictProtectedReceiver(method))));
throw field.makeAccessException((checkStatic != 0) }
? "expected a static field" private MethodHandle getDirectMethodNoRestrict(byte refKind, Class<?> refc, MemberName method) throws IllegalAccessException {
: "expected a non-static field", this); return getDirectMethodCommon(refKind, refc, method, false);
if (trusted) }
return MethodHandleImpl.accessField(field, isSetter, /*no lookupClass*/ null); private MethodHandle getDirectMethodCommon(byte refKind, Class<?> refc, MemberName method,
checkAccess(refc, field); boolean doRestrict) throws IllegalAccessException {
MethodHandle mh = MethodHandleImpl.accessField(field, isSetter, lookupClassOrNull()); checkMethod(refKind, refc, method);
return restrictProtectedReceiver(field, mh); if (method.isMethodHandleInvoke())
return fakeMethodHandleInvoke(method);
MethodHandle mh = DirectMethodHandle.make(refc, method);
mh = mh.setVarargs(method);
if (doRestrict)
mh = restrictReceiver(method, mh, lookupClass());
return mh;
}
private MethodHandle fakeMethodHandleInvoke(MemberName method) {
return throwException(method.getReturnType(), UnsupportedOperationException.class);
}
private MethodHandle getDirectField(byte refKind, Class<?> refc, MemberName field) throws IllegalAccessException {
checkField(refKind, refc, field);
MethodHandle mh = DirectMethodHandle.make(refc, field);
boolean doRestrict = (MethodHandleNatives.refKindHasReceiver(refKind) &&
restrictProtectedReceiver(field));
if (doRestrict)
mh = restrictReceiver(field, mh, lookupClass());
return mh;
}
private MethodHandle getDirectConstructor(Class<?> refc, MemberName ctor) throws IllegalAccessException {
assert(ctor.isConstructor());
checkAccess(REF_newInvokeSpecial, refc, ctor);
return DirectMethodHandle.make(ctor).setVarargs(ctor);
} }
/** Hook called from the JVM (via MethodHandleNatives) to link MH constants: /** Hook called from the JVM (via MethodHandleNatives) to link MH constants:
*/ */
/*non-public*/ /*non-public*/
MethodHandle linkMethodHandleConstant(int refKind, Class<?> defc, String name, Object type) throws ReflectiveOperationException { MethodHandle linkMethodHandleConstant(byte refKind, Class<?> defc, String name, Object type) throws ReflectiveOperationException {
switch (refKind) { MemberName resolved = null;
case REF_getField: return resolveGetter( defc, name, (Class<?>) type ); if (type instanceof MemberName) {
case REF_getStatic: return resolveStaticGetter( defc, name, (Class<?>) type ); resolved = (MemberName) type;
case REF_putField: return resolveSetter( defc, name, (Class<?>) type ); if (!resolved.isResolved()) throw new InternalError("unresolved MemberName");
case REF_putStatic: return resolveStaticSetter( defc, name, (Class<?>) type ); assert(name == null || name.equals(resolved.getName()));
case REF_invokeVirtual: return resolveVirtual( defc, name, (MethodType) type ); }
case REF_invokeStatic: return resolveStatic( defc, name, (MethodType) type ); if (MethodHandleNatives.refKindIsField(refKind)) {
case REF_invokeSpecial: return resolveSpecial( defc, name, (MethodType) type ); MemberName field = (resolved != null) ? resolved
case REF_newInvokeSpecial: return resolveConstructor( defc, (MethodType) type ); : resolveOrFail(refKind, defc, name, (Class<?>) type);
case REF_invokeInterface: return resolveVirtual( defc, name, (MethodType) type ); return getDirectField(refKind, defc, field);
} else if (MethodHandleNatives.refKindIsMethod(refKind)) {
MemberName method = (resolved != null) ? resolved
: resolveOrFail(refKind, defc, name, (MethodType) type);
return getDirectMethod(refKind, defc, method);
} else if (refKind == REF_newInvokeSpecial) {
assert(name == null || name.equals("<init>"));
MemberName ctor = (resolved != null) ? resolved
: resolveOrFail(REF_newInvokeSpecial, defc, name, (MethodType) type);
return getDirectConstructor(defc, ctor);
} }
// oops // oops
throw new ReflectiveOperationException("bad MethodHandle constant #"+refKind+" "+name+" : "+type); throw new ReflectiveOperationException("bad MethodHandle constant #"+refKind+" "+name+" : "+type);
@ -1281,7 +1273,7 @@ return mh1;
*/ */
public static public static
MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException {
return MethodHandleImpl.accessArrayElement(arrayClass, false); return MethodHandleImpl.makeArrayElementAccessor(arrayClass, false);
} }
/** /**
@ -1295,7 +1287,7 @@ return mh1;
*/ */
public static public static
MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException {
return MethodHandleImpl.accessArrayElement(arrayClass, true); return MethodHandleImpl.makeArrayElementAccessor(arrayClass, true);
} }
/// method handle invocation (reflective style) /// method handle invocation (reflective style)
@ -1422,75 +1414,9 @@ publicLookup().findVirtual(MethodHandle.class, "invoke", type)
return type.invokers().generalInvoker(); return type.invokers().generalInvoker();
} }
/** static /*non-public*/
* Perform value checking, exactly as if for an adapted method handle. MethodHandle basicInvoker(MethodType type) {
* It is assumed that the given value is either null, of type T0, return type.form().basicInvoker();
* or (if T0 is primitive) of the wrapper class corresponding to T0.
* The following checks and conversions are made:
* <ul>
* <li>If T0 and T1 are references, then a cast to T1 is applied.
* (The types do not need to be related in any particular way.)
* <li>If T0 and T1 are primitives, then a widening or narrowing
* conversion is applied, if one exists.
* <li>If T0 is a primitive and T1 a reference, and
* T0 has a wrapper class TW, a boxing conversion to TW is applied,
* possibly followed by a reference conversion.
* T1 must be TW or a supertype.
* <li>If T0 is a reference and T1 a primitive, and
* T1 has a wrapper class TW, an unboxing conversion is applied,
* possibly preceded by a reference conversion.
* T0 must be TW or a supertype.
* <li>If T1 is void, the return value is discarded
* <li>If T0 is void and T1 a reference, a null value is introduced.
* <li>If T0 is void and T1 a primitive, a zero value is introduced.
* </ul>
* If the value is discarded, null will be returned.
* @param valueType
* @param value
* @return the value, converted if necessary
* @throws java.lang.ClassCastException if a cast fails
*/
// FIXME: This is used in just one place. Refactor away.
static
<T0, T1> T1 checkValue(Class<T0> t0, Class<T1> t1, Object value)
throws ClassCastException
{
if (t0 == t1) {
// no conversion needed; just reassert the same type
if (t0.isPrimitive())
return Wrapper.asPrimitiveType(t1).cast(value);
else
return Wrapper.OBJECT.convert(value, t1);
}
boolean prim0 = t0.isPrimitive(), prim1 = t1.isPrimitive();
if (!prim0) {
// check contract with caller
Wrapper.OBJECT.convert(value, t0);
if (!prim1) {
return Wrapper.OBJECT.convert(value, t1);
}
// convert reference to primitive by unboxing
Wrapper w1 = Wrapper.forPrimitiveType(t1);
return w1.convert(value, t1);
}
// check contract with caller:
Wrapper.asWrapperType(t0).cast(value);
Wrapper w1 = Wrapper.forPrimitiveType(t1);
return w1.convert(value, t1);
}
// FIXME: Delete this. It is used only for insertArguments & bindTo.
// Replace by a more standard check.
static
Object checkValue(Class<?> T1, Object value)
throws ClassCastException
{
Class<?> T0;
if (value == null)
T0 = Object.class;
else
T0 = value.getClass();
return checkValue(T0, T1, value);
} }
/// method handle modification (creation from other method handles) /// method handle modification (creation from other method handles)
@ -1541,7 +1467,10 @@ publicLookup().findVirtual(MethodHandle.class, "invoke", type)
*/ */
public static public static
MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) {
return MethodHandleImpl.convertArguments(target, newType, 2); if (!target.type().isCastableTo(newType)) {
throw new WrongMethodTypeException("cannot explicitly cast "+target+" to "+newType);
}
return MethodHandleImpl.makePairwiseConvert(target, newType, 2);
} }
/** /**
@ -1605,11 +1534,8 @@ assert((int)twice.invokeExact(21) == 42);
*/ */
public static public static
MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) {
MethodType oldType = target.type(); checkReorder(reorder, newType, target.type());
checkReorder(reorder, newType, oldType); return target.permuteArguments(newType, reorder);
return MethodHandleImpl.permuteArguments(target,
newType, oldType,
reorder);
} }
private static void checkReorder(int[] reorder, MethodType newType, MethodType oldType) { private static void checkReorder(int[] reorder, MethodType newType, MethodType oldType) {
@ -1678,8 +1604,7 @@ assert((int)twice.invokeExact(21) == 42);
else if (type.isPrimitive()) else if (type.isPrimitive())
return ValueConversions.identity(Wrapper.forPrimitiveType(type)); return ValueConversions.identity(Wrapper.forPrimitiveType(type));
else else
return AdapterMethodHandle.makeRetypeRaw( return MethodHandleImpl.makeReferenceIdentity(type);
MethodType.methodType(type, type), ValueConversions.identity());
} }
/** /**
@ -1725,18 +1650,26 @@ assert((int)twice.invokeExact(21) == 42);
MethodHandle result = target; MethodHandle result = target;
for (int i = 0; i < insCount; i++) { for (int i = 0; i < insCount; i++) {
Object value = values[i]; Object value = values[i];
Class<?> valueType = oldType.parameterType(pos+i); Class<?> ptype = oldType.parameterType(pos+i);
value = checkValue(valueType, value); if (ptype.isPrimitive()) {
if (pos == 0 && !valueType.isPrimitive()) { char btype = 'I';
// At least for now, make bound method handles a special case. Wrapper w = Wrapper.forPrimitiveType(ptype);
MethodHandle bmh = MethodHandleImpl.bindReceiver(result, value); switch (w) {
if (bmh != null) { case LONG: btype = 'J'; break;
result = bmh; case FLOAT: btype = 'F'; break;
case DOUBLE: btype = 'D'; break;
}
// perform unboxing and/or primitive conversion
value = w.convert(value, ptype);
result = result.bindArgument(pos, btype, value);
continue; continue;
} }
// else fall through to general adapter machinery value = ptype.cast(value); // throw CCE if needed
if (pos == 0) {
result = result.bindReceiver(value);
} else {
result = result.bindArgument(pos, 'L', value);
} }
result = MethodHandleImpl.bindArgument(result, pos, value);
} }
return result; return result;
} }
@ -1786,16 +1719,17 @@ assertEquals("yz", (String) d0.invokeExact(123, "x", "y", "z"));
public static public static
MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) { MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) {
MethodType oldType = target.type(); // get NPE MethodType oldType = target.type(); // get NPE
if (valueTypes.size() == 0) return target; int dropped = valueTypes.size();
MethodType.checkSlotCount(dropped);
if (dropped == 0) return target;
int outargs = oldType.parameterCount(); int outargs = oldType.parameterCount();
int inargs = outargs + valueTypes.size(); int inargs = outargs + dropped;
if (pos < 0 || pos >= inargs) if (pos < 0 || pos >= inargs)
throw newIllegalArgumentException("no argument type to remove"); throw newIllegalArgumentException("no argument type to remove");
ArrayList<Class<?>> ptypes = ArrayList<Class<?>> ptypes = new ArrayList<>(oldType.parameterList());
new ArrayList<Class<?>>(oldType.parameterList());
ptypes.addAll(pos, valueTypes); ptypes.addAll(pos, valueTypes);
MethodType newType = MethodType.methodType(oldType.returnType(), ptypes); MethodType newType = MethodType.methodType(oldType.returnType(), ptypes);
return MethodHandleImpl.dropArguments(target, newType, pos); return target.dropArguments(newType, pos, dropped);
} }
/** /**
@ -1939,7 +1873,7 @@ assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY
if (filterType.parameterCount() != 1 if (filterType.parameterCount() != 1
|| filterType.returnType() != targetType.parameterType(pos)) || filterType.returnType() != targetType.parameterType(pos))
throw newIllegalArgumentException("target and filter types do not match", targetType, filterType); throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
return MethodHandleImpl.filterArgument(target, pos, filter); return MethodHandleImpl.makeCollectArguments(target, filter, pos, false);
} }
/** /**
@ -2011,10 +1945,7 @@ System.out.println((int) f0.invokeExact("x", "y")); // 2
throw newIllegalArgumentException("target and filter types do not match", target, filter); throw newIllegalArgumentException("target and filter types do not match", target, filter);
// result = fold( lambda(retval, arg...) { filter(retval) }, // result = fold( lambda(retval, arg...) { filter(retval) },
// lambda( arg...) { target(arg...) } ) // lambda( arg...) { target(arg...) } )
MethodType newType = targetType.changeReturnType(filterType.returnType()); return MethodHandleImpl.makeCollectArguments(filter, target, 0, false);
MethodHandle result = null;
assert(AdapterMethodHandle.canCollectArguments(filterType, targetType, 0, false));
return AdapterMethodHandle.makeCollectArguments(filter, target, 0, false);
} }
/** /**
@ -2112,9 +2043,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
if (!ok) if (!ok)
throw misMatchedTypes("target and combiner types", targetType, combinerType); throw misMatchedTypes("target and combiner types", targetType, combinerType);
MethodType newType = targetType.dropParameterTypes(foldPos, afterInsertPos); MethodType newType = targetType.dropParameterTypes(foldPos, afterInsertPos);
MethodHandle res = MethodHandleImpl.foldArguments(target, newType, foldPos, combiner); return MethodHandleImpl.makeCollectArguments(target, combiner, foldPos, true);
if (res == null) throw newIllegalArgumentException("cannot fold from "+newType+" to " +targetType);
return res;
} }
/** /**
@ -2255,6 +2184,8 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
*/ */
public static public static
MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) { MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) {
if (!Throwable.class.isAssignableFrom(exType))
throw new ClassCastException(exType.getName());
return MethodHandleImpl.throwException(MethodType.methodType(returnType, exType)); return MethodHandleImpl.throwException(MethodType.methodType(returnType, exType));
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,12 +26,14 @@
package java.lang.invoke; package java.lang.invoke;
import sun.invoke.util.Wrapper; import sun.invoke.util.Wrapper;
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import sun.invoke.util.BytecodeDescriptor; import sun.invoke.util.BytecodeDescriptor;
import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandleStatics.*;
import sun.invoke.util.VerifyType;
/** /**
* A method type represents the arguments and return type accepted and * A method type represents the arguments and return type accepted and
@ -107,6 +109,8 @@ class MethodType implements java.io.Serializable {
/*trusted*/ Class<?> rtype() { return rtype; } /*trusted*/ Class<?> rtype() { return rtype; }
/*trusted*/ Class<?>[] ptypes() { return ptypes; } /*trusted*/ Class<?>[] ptypes() { return ptypes; }
void setForm(MethodTypeForm f) { form = f; }
private static void checkRtype(Class<?> rtype) { private static void checkRtype(Class<?> rtype) {
rtype.equals(rtype); // null check rtype.equals(rtype); // null check
} }
@ -126,7 +130,7 @@ class MethodType implements java.io.Serializable {
checkSlotCount(ptypes.length + slots); checkSlotCount(ptypes.length + slots);
return slots; return slots;
} }
private static void checkSlotCount(int count) { static void checkSlotCount(int count) {
if ((count & 0xFF) != count) if ((count & 0xFF) != count)
throw newIllegalArgumentException("bad parameter count "+count); throw newIllegalArgumentException("bad parameter count "+count);
} }
@ -135,8 +139,7 @@ class MethodType implements java.io.Serializable {
return new IndexOutOfBoundsException(num.toString()); return new IndexOutOfBoundsException(num.toString());
} }
static final HashMap<MethodType,MethodType> internTable static final WeakInternSet internTable = new WeakInternSet();
= new HashMap<MethodType, MethodType>();
static final Class<?>[] NO_PTYPES = {}; static final Class<?>[] NO_PTYPES = {};
@ -238,31 +241,17 @@ class MethodType implements java.io.Serializable {
ptypes = NO_PTYPES; trusted = true; ptypes = NO_PTYPES; trusted = true;
} }
MethodType mt1 = new MethodType(rtype, ptypes); MethodType mt1 = new MethodType(rtype, ptypes);
MethodType mt0; MethodType mt0 = internTable.get(mt1);
synchronized (internTable) {
mt0 = internTable.get(mt1);
if (mt0 != null) if (mt0 != null)
return mt0; return mt0;
}
if (!trusted) if (!trusted)
// defensively copy the array passed in by the user // defensively copy the array passed in by the user
mt1 = new MethodType(rtype, ptypes.clone()); mt1 = new MethodType(rtype, ptypes.clone());
// promote the object to the Real Thing, and reprobe // promote the object to the Real Thing, and reprobe
MethodTypeForm form = MethodTypeForm.findForm(mt1); MethodTypeForm form = MethodTypeForm.findForm(mt1);
mt1.form = form; mt1.form = form;
if (form.erasedType == mt1) { return internTable.add(mt1);
// This is a principal (erased) type; show it to the JVM.
MethodHandleNatives.init(mt1);
} }
synchronized (internTable) {
mt0 = internTable.get(mt1);
if (mt0 != null)
return mt0;
internTable.put(mt1, mt1);
}
return mt1;
}
private static final MethodType[] objectOnlyTypes = new MethodType[20]; private static final MethodType[] objectOnlyTypes = new MethodType[20];
/** /**
@ -394,6 +383,32 @@ class MethodType implements java.io.Serializable {
return insertParameterTypes(parameterCount(), ptypesToInsert); return insertParameterTypes(parameterCount(), ptypesToInsert);
} }
/**
* Finds or creates a method type with modified parameter types.
* Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
* @param start the position (zero-based) of the first replaced parameter type(s)
* @param end the position (zero-based) after the last replaced parameter type(s)
* @param ptypesToInsert zero or more new parameter types to insert into the parameter list
* @return the same type, except with the selected parameter(s) replaced
* @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
* or if {@code end} is negative or greater than {@code parameterCount()}
* or if {@code start} is greater than {@code end}
* @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
* or if the resulting method type would have more than 255 parameter slots
* @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
*/
/*non-public*/ MethodType replaceParameterTypes(int start, int end, Class<?>... ptypesToInsert) {
if (start == end)
return insertParameterTypes(start, ptypesToInsert);
int len = ptypes.length;
if (!(0 <= start && start <= end && end <= len))
throw newIndexOutOfBoundsException("start="+start+" end="+end);
int ilen = ptypesToInsert.length;
if (ilen == 0)
return dropParameterTypes(start, end);
return dropParameterTypes(start, end).insertParameterTypes(start, ptypesToInsert);
}
/** /**
* Finds or creates a method type with some parameter types omitted. * Finds or creates a method type with some parameter types omitted.
* Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
@ -473,6 +488,23 @@ class MethodType implements java.io.Serializable {
return form.erasedType(); return form.erasedType();
} }
/**
* Erases all reference types to {@code Object}, and all subword types to {@code int}.
* This is the reduced type polymorphism used by private methods
* such as {@link MethodHandle#invokeBasic invokeBasic}.
* @return a version of the original type with all reference and subword types replaced
*/
/*non-public*/ MethodType basicType() {
return form.basicType();
}
/**
* @return a version of the original type with MethodHandle prepended as the first argument
*/
/*non-public*/ MethodType invokerType() {
return insertParameterTypes(0, MethodHandle.class);
}
/** /**
* Converts all types, both reference and primitive, to {@code Object}. * Converts all types, both reference and primitive, to {@code Object}.
* Convenience method for {@link #genericMethodType(int) genericMethodType}. * Convenience method for {@link #genericMethodType(int) genericMethodType}.
@ -567,6 +599,11 @@ class MethodType implements java.io.Serializable {
return Collections.unmodifiableList(Arrays.asList(ptypes)); return Collections.unmodifiableList(Arrays.asList(ptypes));
} }
/*non-public*/ Class<?> lastParameterType() {
int len = ptypes.length;
return len == 0 ? void.class : ptypes[len-1];
}
/** /**
* Presents the parameter types as an array (a convenience method). * Presents the parameter types as an array (a convenience method).
* Changes to the array will not result in changes to the type. * Changes to the array will not result in changes to the type.
@ -635,6 +672,26 @@ class MethodType implements java.io.Serializable {
} }
/*non-public*/
boolean isViewableAs(MethodType newType) {
if (!VerifyType.isNullConversion(returnType(), newType.returnType()))
return false;
int argc = parameterCount();
if (argc != newType.parameterCount())
return false;
for (int i = 0; i < argc; i++) {
if (!VerifyType.isNullConversion(newType.parameterType(i), parameterType(i)))
return false;
}
return true;
}
/*non-public*/
boolean isCastableTo(MethodType newType) {
int argc = parameterCount();
if (argc != newType.parameterCount())
return false;
return true;
}
/*non-public*/ /*non-public*/
boolean isConvertibleTo(MethodType newType) { boolean isConvertibleTo(MethodType newType) {
if (!canConvert(returnType(), newType.returnType())) if (!canConvert(returnType(), newType.returnType()))
@ -818,6 +875,10 @@ class MethodType implements java.io.Serializable {
return BytecodeDescriptor.unparse(this); return BytecodeDescriptor.unparse(this);
} }
/*non-public*/ static String toFieldDescriptorString(Class<?> cls) {
return BytecodeDescriptor.unparse(cls);
}
/// Serialization. /// Serialization.
/** /**
@ -890,18 +951,17 @@ s.writeObject(this.parameterArray());
// store them into the implementation-specific final fields. // store them into the implementation-specific final fields.
checkRtype(rtype); checkRtype(rtype);
checkPtypes(ptypes); checkPtypes(ptypes);
unsafe.putObject(this, rtypeOffset, rtype); UNSAFE.putObject(this, rtypeOffset, rtype);
unsafe.putObject(this, ptypesOffset, ptypes); UNSAFE.putObject(this, ptypesOffset, ptypes);
} }
// Support for resetting final fields while deserializing // Support for resetting final fields while deserializing
private static final sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
private static final long rtypeOffset, ptypesOffset; private static final long rtypeOffset, ptypesOffset;
static { static {
try { try {
rtypeOffset = unsafe.objectFieldOffset rtypeOffset = UNSAFE.objectFieldOffset
(MethodType.class.getDeclaredField("rtype")); (MethodType.class.getDeclaredField("rtype"));
ptypesOffset = unsafe.objectFieldOffset ptypesOffset = UNSAFE.objectFieldOffset
(MethodType.class.getDeclaredField("ptypes")); (MethodType.class.getDeclaredField("ptypes"));
} catch (Exception ex) { } catch (Exception ex) {
throw new Error(ex); throw new Error(ex);
@ -919,4 +979,269 @@ s.writeObject(this.parameterArray());
// Verify all operands, and make sure ptypes is unshared: // Verify all operands, and make sure ptypes is unshared:
return methodType(rtype, ptypes); return methodType(rtype, ptypes);
} }
/**
* Weak intern set based on implementation of the <tt>HashSet</tt> and
* <tt>WeakHashMap</tt>, with <em>weak values</em>. Note: <tt>null</tt>
* values will yield <tt>NullPointerException</tt>
* Refer to implementation of WeakInternSet for details.
*
* @see java.util.HashMap
* @see java.util.HashSet
* @see java.util.WeakHashMap
* @see java.lang.ref.WeakReference
*/
private static class WeakInternSet {
// The default initial capacity -- MUST be a power of two.
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// The maximum capacity, used if a higher value is implicitly specified
// by either of the constructors with arguments.
// MUST be a power of two <= 1<<30.
private static final int MAXIMUM_CAPACITY = 1 << 30;
// The load factor used when none specified in constructor.
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
// The table, resized as necessary. Length MUST Always be a power of two.
private Entry[] table;
// The number of entries contained in this set.
private int size;
// The next size value at which to resize (capacity * load factor).
private int threshold;
// The load factor for the hash table.
private final float loadFactor;
// Reference queue for cleared WeakEntries
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
private Entry[] newTable(int n) {
return new Entry[n];
}
/**
* Constructs a new, empty <tt>WeakInternSet</tt> with the default initial
* capacity (16) and load factor (0.75).
*/
WeakInternSet() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = DEFAULT_INITIAL_CAPACITY;
table = newTable(DEFAULT_INITIAL_CAPACITY);
}
/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because hashing uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits.
* @param h preliminary hash code value
* @return supplemental hash code value
*/
private static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
/**
* Checks for equality of non-null reference x and possibly-null y. By
* default uses Object.equals.
* @param x first object to compare
* @param y second object to compare
* @return <tt>true</tt> if objects are equal
*/
private static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}
/**
* Returns index for hash code h.
* @param h raw hash code
* @param length length of table (power of 2)
* @return index in table
*/
private static int indexFor(int h, int length) {
return h & (length-1);
}
/**
* Expunges stale entries from the table.
*/
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
Entry entry = (Entry) x;
int i = indexFor(entry.hash, table.length);
Entry prev = table[i];
Entry p = prev;
while (p != null) {
Entry next = p.next;
if (p == entry) {
if (prev == entry)
table[i] = next;
else
prev.next = next;
entry.next = null;
size--;
break;
}
prev = p;
p = next;
}
}
}
}
/**
* Returns the table after first expunging stale entries.
* @return an expunged hash table
*/
private Entry[] getTable() {
expungeStaleEntries();
return table;
}
/**
* Returns the entry to which the specified value is mapped,
* or {@code null} if this set contains no entry for the value.
*
* <p>More formally, if this set contains an entry for value
* {@code entry} to a value {@code value} such that
* {@code entry.equals(value)}, then this method returns {@code entry};
* otherwise it returns {@code null}.
*
* @param value value to search for in set
* @return interned value if in set, otherwise <tt>null</tt>
*/
synchronized MethodType get(MethodType value) {
int h = hash(value.hashCode());
Entry[] tab = getTable();
int index = indexFor(h, tab.length);
Entry e = tab[index];
MethodType g;
while (e != null) {
if (e.hash == h && eq(value, g = e.get()))
return g;
e = e.next;
}
return null;
}
/**
* Attempts to add the specified value to the set and returns same value.
* If the set previously contained an entry for this value, the old
* value is left untouched and returned as the result.
*
* @param value value to be added
* @return the previous entry associated with <tt>value</tt>, or
* <tt>value</tt> if there was no previous entry found
*/
synchronized MethodType add(MethodType value) {
int h = hash(value.hashCode());
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
MethodType g;
for (Entry e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(value, g = e.get())) {
return g;
}
}
Entry e = tab[i];
tab[i] = new Entry(value, queue, h, e);
if (++size >= threshold)
resize(tab.length * 2);
return value;
}
/**
* Rehashes the contents of this set into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this set reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the set, but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant)
*/
private void resize(int newCapacity) {
Entry[] oldTable = getTable();
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = newTable(newCapacity);
transfer(oldTable, newTable);
table = newTable;
/*
* If ignoring null elements and processing ref queue caused massive
* shrinkage, then restore old table. This should be rare, but avoids
* unbounded expansion of garbage-filled tables.
*/
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
}
/**
* Transfers all entries from src to dest tables
* @param src original table
* @param dest new table
*/
private void transfer(Entry[] src, Entry[] dest) {
for (int j = 0; j < src.length; ++j) {
Entry e = src[j];
src[j] = null;
while (e != null) {
Entry next = e.next;
MethodType key = e.get();
if (key == null) {
e.next = null; // Help GC
size--;
} else {
int i = indexFor(e.hash, dest.length);
e.next = dest[i];
dest[i] = e;
}
e = next;
}
}
}
/**
* The entries in this hash table extend WeakReference, using its main ref
* field as the key.
*/
private static class Entry extends WeakReference<MethodType> {
final int hash;
Entry next;
/**
* Creates new entry.
*/
Entry(MethodType key,
ReferenceQueue<Object> queue,
int hash, Entry next) {
super(key, queue);
this.hash = hash;
this.next = next;
}
}
}
} }

View file

@ -27,6 +27,7 @@ package java.lang.invoke;
import sun.invoke.util.Wrapper; import sun.invoke.util.Wrapper;
import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandleStatics.*;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
/** /**
* Shared information for a group of method types, which differ * Shared information for a group of method types, which differ
@ -41,27 +42,70 @@ import static java.lang.invoke.MethodHandleStatics.*;
* No more than half of these are likely to be loaded at once. * No more than half of these are likely to be loaded at once.
* @author John Rose * @author John Rose
*/ */
class MethodTypeForm { final class MethodTypeForm {
final int[] argToSlotTable, slotToArgTable; final int[] argToSlotTable, slotToArgTable;
final long argCounts; // packed slot & value counts final long argCounts; // packed slot & value counts
final long primCounts; // packed prim & double counts final long primCounts; // packed prim & double counts
final int vmslots; // total number of parameter slots final int vmslots; // total number of parameter slots
private Object vmlayout; // vm-specific information for calls
final MethodType erasedType; // the canonical erasure final MethodType erasedType; // the canonical erasure
final MethodType basicType; // the canonical erasure, with primitives simplified
/*lazy*/ MethodType primsAsBoxes; // replace prims by wrappers
/*lazy*/ MethodType primArgsAsBoxes; // wrap args only; make raw return
/*lazy*/ MethodType primsAsInts; // replace prims by int/long
/*lazy*/ MethodType primsAsLongs; // replace prims by long
/*lazy*/ MethodType primsAtEnd; // reorder primitives to the end
// Cached adapter information: // Cached adapter information:
/*lazy*/ MethodHandle genericInvoker; // hook for inexact invoke /*lazy*/ MethodHandle genericInvoker; // JVM hook for inexact invoke
/*lazy*/ MethodHandle basicInvoker; // cached instance of MH.invokeBasic
/*lazy*/ MethodHandle namedFunctionInvoker; // cached helper for LF.NamedFunction
// Cached lambda form information, for basic types only:
final LambdaForm[] lambdaForms;
// Indexes into lambdaForms:
static final int
LF_INVVIRTUAL = 0, // DMH invokeVirtual
LF_INVSTATIC = 1,
LF_INVSPECIAL = 2,
LF_NEWINVSPECIAL = 3,
LF_INVINTERFACE = 4,
LF_INVSTATIC_INIT = 5, // DMH invokeStatic with <clinit> barrier
LF_INTERPRET = 6, // LF interpreter
LF_COUNTER = 7, // CMH wrapper
LF_REINVOKE = 8, // other wrapper
LF_EX_LINKER = 9, // invokeExact_MT
LF_EX_INVOKER = 10, // invokeExact MH
LF_GEN_LINKER = 11,
LF_GEN_INVOKER = 12,
LF_CS_LINKER = 13, // linkToCallSite_CS
LF_LIMIT = 14;
public MethodType erasedType() { public MethodType erasedType() {
return erasedType; return erasedType;
} }
public MethodType basicType() {
return basicType;
}
public LambdaForm cachedLambdaForm(int which) {
return lambdaForms[which];
}
public LambdaForm setCachedLambdaForm(int which, LambdaForm form) {
// Should we perform some sort of CAS, to avoid racy duplication?
return lambdaForms[which] = form;
}
public MethodHandle basicInvoker() {
assert(erasedType == basicType) : "erasedType: " + erasedType + " != basicType: " + basicType; // primitives must be flattened also
MethodHandle invoker = basicInvoker;
if (invoker != null) return invoker;
invoker = basicType.invokers().makeBasicInvoker();
basicInvoker = invoker;
return invoker;
}
/**
* Build an MTF for a given type, which must have all references erased to Object.
* This MTF will stand for that type and all un-erased variations.
* Eagerly compute some basic properties of the type, common to all variations.
*/
protected MethodTypeForm(MethodType erasedType) { protected MethodTypeForm(MethodType erasedType) {
this.erasedType = erasedType; this.erasedType = erasedType;
@ -75,26 +119,41 @@ class MethodTypeForm {
// Walk the argument types, looking for primitives. // Walk the argument types, looking for primitives.
int pac = 0, lac = 0, prc = 0, lrc = 0; int pac = 0, lac = 0, prc = 0, lrc = 0;
Class<?> epts[] = ptypes; Class<?>[] epts = ptypes;
Class<?>[] bpts = epts;
for (int i = 0; i < epts.length; i++) { for (int i = 0; i < epts.length; i++) {
Class<?> pt = epts[i]; Class<?> pt = epts[i];
if (pt != Object.class) { if (pt != Object.class) {
assert(pt.isPrimitive());
++pac; ++pac;
if (hasTwoArgSlots(pt)) ++lac; Wrapper w = Wrapper.forPrimitiveType(pt);
if (w.isDoubleWord()) ++lac;
if (w.isSubwordOrInt() && pt != int.class) {
if (bpts == epts)
bpts = bpts.clone();
bpts[i] = int.class;
}
} }
} }
pslotCount += lac; // #slots = #args + #longs pslotCount += lac; // #slots = #args + #longs
Class<?> rt = erasedType.returnType(); Class<?> rt = erasedType.returnType();
Class<?> bt = rt;
if (rt != Object.class) { if (rt != Object.class) {
++prc; // even void.class counts as a prim here ++prc; // even void.class counts as a prim here
if (hasTwoArgSlots(rt)) ++lrc; Wrapper w = Wrapper.forPrimitiveType(rt);
if (w.isDoubleWord()) ++lrc;
if (w.isSubwordOrInt() && rt != int.class)
bt = int.class;
// adjust #slots, #args // adjust #slots, #args
if (rt == void.class) if (rt == void.class)
rtypeCount = rslotCount = 0; rtypeCount = rslotCount = 0;
else else
rslotCount += lrc; rslotCount += lrc;
} }
if (epts == bpts && bt == rt) {
this.basicType = erasedType;
} else {
this.basicType = MethodType.makeImpl(bt, bpts, true);
}
if (lac != 0) { if (lac != 0) {
int slot = ptypeCount + lac; int slot = ptypeCount + lac;
slotToArgTab = new int[slot+1]; slotToArgTab = new int[slot+1];
@ -102,7 +161,8 @@ class MethodTypeForm {
argToSlotTab[0] = slot; // argument "-1" is past end of slots argToSlotTab[0] = slot; // argument "-1" is past end of slots
for (int i = 0; i < epts.length; i++) { for (int i = 0; i < epts.length; i++) {
Class<?> pt = epts[i]; Class<?> pt = epts[i];
if (hasTwoArgSlots(pt)) --slot; Wrapper w = Wrapper.forBasicType(pt);
if (w.isDoubleWord()) --slot;
--slot; --slot;
slotToArgTab[slot] = i+1; // "+1" see argSlotToParameter note slotToArgTab[slot] = i+1; // "+1" see argSlotToParameter note
argToSlotTab[1+i] = slot; argToSlotTab[1+i] = slot;
@ -130,164 +190,13 @@ class MethodTypeForm {
// send a few bits down to the JVM: // send a few bits down to the JVM:
this.vmslots = parameterSlotCount(); this.vmslots = parameterSlotCount();
// short circuit some no-op canonicalizations: if (basicType == erasedType) {
if (!hasPrimitives()) { lambdaForms = new LambdaForm[LF_LIMIT];
primsAsBoxes = erasedType; } else {
primArgsAsBoxes = erasedType; lambdaForms = null; // could be basicType.form().lambdaForms;
primsAsInts = erasedType;
primsAsLongs = erasedType;
primsAtEnd = erasedType;
} }
} }
/** Turn all primitive types to corresponding wrapper types.
*/
public MethodType primsAsBoxes() {
MethodType ct = primsAsBoxes;
if (ct != null) return ct;
MethodType t = erasedType;
ct = canonicalize(erasedType, WRAP, WRAP);
if (ct == null) ct = t; // no prims to box
return primsAsBoxes = ct;
}
/** Turn all primitive argument types to corresponding wrapper types.
* Subword and void return types are promoted to int.
*/
public MethodType primArgsAsBoxes() {
MethodType ct = primArgsAsBoxes;
if (ct != null) return ct;
MethodType t = erasedType;
ct = canonicalize(erasedType, RAW_RETURN, WRAP);
if (ct == null) ct = t; // no prims to box
return primArgsAsBoxes = ct;
}
/** Turn all primitive types to either int or long.
* Floating point return types are not changed, because
* they may require special calling sequences.
* A void return value is turned to int.
*/
public MethodType primsAsInts() {
MethodType ct = primsAsInts;
if (ct != null) return ct;
MethodType t = erasedType;
ct = canonicalize(t, RAW_RETURN, INTS);
if (ct == null) ct = t; // no prims to int-ify
return primsAsInts = ct;
}
/** Turn all primitive types to either int or long.
* Floating point return types are not changed, because
* they may require special calling sequences.
* A void return value is turned to int.
*/
public MethodType primsAsLongs() {
MethodType ct = primsAsLongs;
if (ct != null) return ct;
MethodType t = erasedType;
ct = canonicalize(t, RAW_RETURN, LONGS);
if (ct == null) ct = t; // no prims to int-ify
return primsAsLongs = ct;
}
/** Stably sort parameters into 3 buckets: ref, int, long. */
public MethodType primsAtEnd() {
MethodType ct = primsAtEnd;
if (ct != null) return ct;
MethodType t = erasedType;
int pac = primitiveParameterCount();
if (pac == 0)
return primsAtEnd = t;
int argc = parameterCount();
int lac = longPrimitiveParameterCount();
if (pac == argc && (lac == 0 || lac == argc))
return primsAtEnd = t;
// known to have a mix of 2 or 3 of ref, int, long
int[] reorder = primsAtEndOrder(t);
ct = reorderParameters(t, reorder, null);
//System.out.println("t="+t+" / reorder="+java.util.Arrays.toString(reorder)+" => "+ct);
return primsAtEnd = ct;
}
/** Compute a new ordering of parameters so that all references
* are before all ints or longs, and all ints are before all longs.
* For this ordering, doubles count as longs, and all other primitive
* values count as ints.
* As a special case, if the parameters are already in the specified
* order, this method returns a null reference, rather than an array
* specifying a null permutation.
* <p>
* For example, the type {@code (int,boolean,int,Object,String)void}
* produces the order {@code {3,4,0,1,2}}, the type
* {@code (long,int,String)void} produces {@code {2,1,2}}, and
* the type {@code (Object,int)Object} produces {@code null}.
*/
public static int[] primsAtEndOrder(MethodType mt) {
MethodTypeForm form = mt.form();
if (form.primsAtEnd == form.erasedType)
// quick check shows no reordering is necessary
return null;
int argc = form.parameterCount();
int[] paramOrder = new int[argc];
// 3-way bucket sort:
int pac = form.primitiveParameterCount();
int lac = form.longPrimitiveParameterCount();
int rfill = 0, ifill = argc - pac, lfill = argc - lac;
Class<?>[] ptypes = mt.ptypes();
boolean changed = false;
for (int i = 0; i < ptypes.length; i++) {
Class<?> pt = ptypes[i];
int ord;
if (!pt.isPrimitive()) ord = rfill++;
else if (!hasTwoArgSlots(pt)) ord = ifill++;
else ord = lfill++;
if (ord != i) changed = true;
assert(paramOrder[ord] == 0);
paramOrder[ord] = i;
}
assert(rfill == argc - pac && ifill == argc - lac && lfill == argc);
if (!changed) {
form.primsAtEnd = form.erasedType;
return null;
}
return paramOrder;
}
/** Put the existing parameters of mt into a new order, given by newParamOrder.
* The third argument is logically appended to mt.parameterArray,
* so that elements of newParamOrder can index either pre-existing or
* new parameter types.
*/
public static MethodType reorderParameters(MethodType mt, int[] newParamOrder, Class<?>[] moreParams) {
if (newParamOrder == null) return mt; // no-op reordering
Class<?>[] ptypes = mt.ptypes();
Class<?>[] ntypes = new Class<?>[newParamOrder.length];
int maxParam = ptypes.length + (moreParams == null ? 0 : moreParams.length);
boolean changed = (ntypes.length != ptypes.length);
for (int i = 0; i < newParamOrder.length; i++) {
int param = newParamOrder[i];
if (param != i) changed = true;
Class<?> nt;
if (param < ptypes.length) nt = ptypes[param];
else if (param == maxParam) nt = mt.returnType();
else nt = moreParams[param - ptypes.length];
ntypes[i] = nt;
}
if (!changed) return mt;
return MethodType.makeImpl(mt.returnType(), ntypes, true);
}
private static boolean hasTwoArgSlots(Class<?> type) {
return type == long.class || type == double.class;
}
private static long pack(int a, int b, int c, int d) { private static long pack(int a, int b, int c, int d) {
assert(((a|b|c|d) & ~0xFFFF) == 0); assert(((a|b|c|d) & ~0xFFFF) == 0);
long hw = ((a << 16) | b), lw = ((c << 16) | d); long hw = ((a << 16) | b), lw = ((c << 16) | d);
@ -325,11 +234,11 @@ class MethodTypeForm {
public boolean hasPrimitives() { public boolean hasPrimitives() {
return primCounts != 0; return primCounts != 0;
} }
// public boolean hasNonVoidPrimitives() { public boolean hasNonVoidPrimitives() {
// if (primCounts == 0) return false; if (primCounts == 0) return false;
// if (primitiveParameterCount() != 0) return true; if (primitiveParameterCount() != 0) return true;
// return (primitiveReturnCount() != 0 && returnCount() != 0); return (primitiveReturnCount() != 0 && returnCount() != 0);
// } }
public boolean hasLongPrimitives() { public boolean hasLongPrimitives() {
return (longPrimitiveParameterCount() | longPrimitiveReturnCount()) != 0; return (longPrimitiveParameterCount() | longPrimitiveReturnCount()) != 0;
} }
@ -455,18 +364,6 @@ class MethodTypeForm {
return cs; return cs;
} }
/*non-public*/ void notifyGenericMethodType() {
if (genericInvoker != null) return;
try {
// Trigger adapter creation.
genericInvoker = InvokeGeneric.generalInvokerOf(erasedType);
} catch (Exception ex) {
Error err = new InternalError("Exception while resolving inexact invoke", ex);
err.initCause(ex);
throw err;
}
}
@Override @Override
public String toString() { public String toString() {
return "Form"+erasedType; return "Form"+erasedType;

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.invoke;
import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A method handle whose behavior is determined only by its LambdaForm.
* @author jrose
*/
final class SimpleMethodHandle extends MethodHandle {
SimpleMethodHandle(MethodType type, LambdaForm form) {
super(type, form);
}
@Override
MethodHandle bindArgument(int pos, char basicType, Object value) {
MethodType type2 = type().dropParameterTypes(pos, pos+1);
LambdaForm form2 = internalForm().bind(1+pos, BoundMethodHandle.SpeciesData.EMPTY);
return BoundMethodHandle.bindSingle(type2, form2, basicType, value);
}
@Override
MethodHandle dropArguments(MethodType srcType, int pos, int drops) {
LambdaForm newForm = internalForm().addArguments(pos, srcType.parameterList().subList(pos, pos+drops));
return new SimpleMethodHandle(srcType, newForm);
}
@Override
MethodHandle permuteArguments(MethodType newType, int[] reorder) {
LambdaForm form2 = internalForm().permuteArguments(1, reorder, basicTypes(newType.parameterList()));
return new SimpleMethodHandle(newType, form2);
}
@Override
MethodHandle copyWith(MethodType mt, LambdaForm lf) {
return new SimpleMethodHandle(mt, lf);
}
}

View file

@ -191,6 +191,13 @@
* (If a string constant were passed instead, by badly generated code, that cast would then fail, * (If a string constant were passed instead, by badly generated code, that cast would then fail,
* resulting in a {@code BootstrapMethodError}.) * resulting in a {@code BootstrapMethodError}.)
* <p> * <p>
* Note that, as a consequence of the above rules, the bootstrap method may accept a primitive
* argument, if it can be represented by a constant pool entry.
* However, arguments of type {@code boolean}, {@code byte}, {@code short}, or {@code char}
* cannot be created for bootstrap methods, since such constants cannot be directly
* represented in the constant pool, and the invocation of the bootstrap method will
* not perform the necessary narrowing primitive conversions.
* <p>
* Extra bootstrap method arguments are intended to allow language implementors * Extra bootstrap method arguments are intended to allow language implementors
* to safely and compactly encode metadata. * to safely and compactly encode metadata.
* In principle, the name and extra arguments are redundant, * In principle, the name and extra arguments are redundant,

View file

@ -118,42 +118,15 @@ public class ValueConversions {
return primitiveConversion(Wrapper.DOUBLE, x, cast).doubleValue(); return primitiveConversion(Wrapper.DOUBLE, x, cast).doubleValue();
} }
/// Converting references to "raw" values. private static MethodType unboxType(Wrapper wrap) {
/// A raw primitive value is always an int or long. return MethodType.methodType(wrap.primitiveType(), Object.class, boolean.class);
static int unboxByteRaw(Object x, boolean cast) {
return unboxByte(x, cast);
}
static int unboxShortRaw(Object x, boolean cast) {
return unboxShort(x, cast);
}
static int unboxBooleanRaw(Object x, boolean cast) {
return unboxBoolean(x, cast) ? 1 : 0;
}
static int unboxCharacterRaw(Object x, boolean cast) {
return unboxCharacter(x, cast);
}
static int unboxFloatRaw(Object x, boolean cast) {
return Float.floatToIntBits(unboxFloat(x, cast));
}
static long unboxDoubleRaw(Object x, boolean cast) {
return Double.doubleToRawLongBits(unboxDouble(x, cast));
}
private static MethodType unboxType(Wrapper wrap, boolean raw) {
return MethodType.methodType(rawWrapper(wrap, raw).primitiveType(), Object.class, boolean.class);
} }
private static final EnumMap<Wrapper, MethodHandle>[] private static final EnumMap<Wrapper, MethodHandle>[]
UNBOX_CONVERSIONS = newWrapperCaches(4); UNBOX_CONVERSIONS = newWrapperCaches(2);
private static MethodHandle unbox(Wrapper wrap, boolean raw, boolean cast) { private static MethodHandle unbox(Wrapper wrap, boolean cast) {
EnumMap<Wrapper, MethodHandle> cache = UNBOX_CONVERSIONS[(cast?1:0)+(raw?2:0)]; EnumMap<Wrapper, MethodHandle> cache = UNBOX_CONVERSIONS[(cast?1:0)];
MethodHandle mh = cache.get(wrap); MethodHandle mh = cache.get(wrap);
if (mh != null) { if (mh != null) {
return mh; return mh;
@ -163,19 +136,15 @@ public class ValueConversions {
case OBJECT: case OBJECT:
mh = IDENTITY; break; mh = IDENTITY; break;
case VOID: case VOID:
mh = raw ? ALWAYS_ZERO : IGNORE; break; mh = IGNORE; break;
case INT: case LONG:
// these guys don't need separate raw channels
if (raw) mh = unbox(wrap, false, cast);
break;
} }
if (mh != null) { if (mh != null) {
cache.put(wrap, mh); cache.put(wrap, mh);
return mh; return mh;
} }
// look up the method // look up the method
String name = "unbox" + wrap.simpleName() + (raw ? "Raw" : ""); String name = "unbox" + wrap.wrapperSimpleName();
MethodType type = unboxType(wrap, raw); MethodType type = unboxType(wrap);
try { try {
mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
@ -187,32 +156,30 @@ public class ValueConversions {
return mh; return mh;
} }
throw new IllegalArgumentException("cannot find unbox adapter for " + wrap throw new IllegalArgumentException("cannot find unbox adapter for " + wrap
+ (cast ? " (cast)" : "") + (raw ? " (raw)" : "")); + (cast ? " (cast)" : ""));
} }
public static MethodHandle unboxCast(Wrapper type) { public static MethodHandle unboxCast(Wrapper type) {
return unbox(type, false, true); return unbox(type, true);
}
public static MethodHandle unboxRaw(Wrapper type) {
return unbox(type, true, false);
} }
public static MethodHandle unbox(Class<?> type) { public static MethodHandle unbox(Class<?> type) {
return unbox(Wrapper.forPrimitiveType(type), false, false); return unbox(Wrapper.forPrimitiveType(type), false);
} }
public static MethodHandle unboxCast(Class<?> type) { public static MethodHandle unboxCast(Class<?> type) {
return unbox(Wrapper.forPrimitiveType(type), false, true); return unbox(Wrapper.forPrimitiveType(type), true);
}
public static MethodHandle unboxRaw(Class<?> type) {
return unbox(Wrapper.forPrimitiveType(type), true, false);
} }
static private final Integer ZERO_INT = 0, ONE_INT = 1; static private final Integer ZERO_INT = 0, ONE_INT = 1;
/// Primitive conversions /// Primitive conversions
/**
* Produce a Number which represents the given value {@code x}
* according to the primitive type of the given wrapper {@code wrap}.
* Caller must invoke intValue, byteValue, longValue (etc.) on the result
* to retrieve the desired primitive value.
*/
public static Number primitiveConversion(Wrapper wrap, Object x, boolean cast) { public static Number primitiveConversion(Wrapper wrap, Object x, boolean cast) {
// Maybe merge this code with Wrapper.convert/cast. // Maybe merge this code with Wrapper.convert/cast.
Number res = null; Number res = null;
@ -237,6 +204,27 @@ public class ValueConversions {
return res; return res;
} }
/**
* The JVM verifier allows boolean, byte, short, or char to widen to int.
* Support exactly this conversion, from a boxed value type Boolean,
* Byte, Short, Character, or Integer.
*/
public static int widenSubword(Object x) {
if (x instanceof Integer)
return (int) x;
else if (x instanceof Boolean)
return fromBoolean((boolean) x);
else if (x instanceof Character)
return (char) x;
else if (x instanceof Short)
return (short) x;
else if (x instanceof Byte)
return (byte) x;
else
// Fail with a ClassCastException.
return (int) x;
}
/// Converting primitives to references /// Converting primitives to references
static Integer boxInteger(int x) { static Integer boxInteger(int x) {
@ -271,53 +259,17 @@ public class ValueConversions {
return x; return x;
} }
/// Converting raw primitives to references private static MethodType boxType(Wrapper wrap) {
static Byte boxByteRaw(int x) {
return boxByte((byte)x);
}
static Short boxShortRaw(int x) {
return boxShort((short)x);
}
static Boolean boxBooleanRaw(int x) {
return boxBoolean(x != 0);
}
static Character boxCharacterRaw(int x) {
return boxCharacter((char)x);
}
static Float boxFloatRaw(int x) {
return boxFloat(Float.intBitsToFloat(x));
}
static Double boxDoubleRaw(long x) {
return boxDouble(Double.longBitsToDouble(x));
}
// a raw void value is (arbitrarily) a garbage int
static Void boxVoidRaw(int x) {
return null;
}
private static MethodType boxType(Wrapper wrap, boolean raw) {
// be exact, since return casts are hard to compose // be exact, since return casts are hard to compose
Class<?> boxType = wrap.wrapperType(); Class<?> boxType = wrap.wrapperType();
return MethodType.methodType(boxType, rawWrapper(wrap, raw).primitiveType()); return MethodType.methodType(boxType, wrap.primitiveType());
}
private static Wrapper rawWrapper(Wrapper wrap, boolean raw) {
if (raw) return wrap.isDoubleWord() ? Wrapper.LONG : Wrapper.INT;
return wrap;
} }
private static final EnumMap<Wrapper, MethodHandle>[] private static final EnumMap<Wrapper, MethodHandle>[]
BOX_CONVERSIONS = newWrapperCaches(4); BOX_CONVERSIONS = newWrapperCaches(2);
private static MethodHandle box(Wrapper wrap, boolean exact, boolean raw) { private static MethodHandle box(Wrapper wrap, boolean exact) {
EnumMap<Wrapper, MethodHandle> cache = BOX_CONVERSIONS[(exact?1:0)+(raw?2:0)]; EnumMap<Wrapper, MethodHandle> cache = BOX_CONVERSIONS[(exact?1:0)];
MethodHandle mh = cache.get(wrap); MethodHandle mh = cache.get(wrap);
if (mh != null) { if (mh != null) {
return mh; return mh;
@ -327,11 +279,7 @@ public class ValueConversions {
case OBJECT: case OBJECT:
mh = IDENTITY; break; mh = IDENTITY; break;
case VOID: case VOID:
if (!raw) mh = ZERO_OBJECT; mh = ZERO_OBJECT;
break;
case INT: case LONG:
// these guys don't need separate raw channels
if (raw) mh = box(wrap, exact, false);
break; break;
} }
if (mh != null) { if (mh != null) {
@ -339,8 +287,8 @@ public class ValueConversions {
return mh; return mh;
} }
// look up the method // look up the method
String name = "box" + wrap.simpleName() + (raw ? "Raw" : ""); String name = "box" + wrap.wrapperSimpleName();
MethodType type = boxType(wrap, raw); MethodType type = boxType(wrap);
if (exact) { if (exact) {
try { try {
mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type);
@ -348,160 +296,26 @@ public class ValueConversions {
mh = null; mh = null;
} }
} else { } else {
mh = box(wrap, !exact, raw).asType(type.erase()); mh = box(wrap, !exact).asType(type.erase());
} }
if (mh != null) { if (mh != null) {
cache.put(wrap, mh); cache.put(wrap, mh);
return mh; return mh;
} }
throw new IllegalArgumentException("cannot find box adapter for " throw new IllegalArgumentException("cannot find box adapter for "
+ wrap + (exact ? " (exact)" : "") + (raw ? " (raw)" : "")); + wrap + (exact ? " (exact)" : ""));
} }
public static MethodHandle box(Class<?> type) { public static MethodHandle box(Class<?> type) {
boolean exact = false; boolean exact = false;
// e.g., boxShort(short)Short if exact, // e.g., boxShort(short)Short if exact,
// e.g., boxShort(short)Object if !exact // e.g., boxShort(short)Object if !exact
return box(Wrapper.forPrimitiveType(type), exact, false); return box(Wrapper.forPrimitiveType(type), exact);
}
public static MethodHandle boxRaw(Class<?> type) {
boolean exact = false;
// e.g., boxShortRaw(int)Short if exact
// e.g., boxShortRaw(int)Object if !exact
return box(Wrapper.forPrimitiveType(type), exact, true);
} }
public static MethodHandle box(Wrapper type) { public static MethodHandle box(Wrapper type) {
boolean exact = false; boolean exact = false;
return box(type, exact, false); return box(type, exact);
}
public static MethodHandle boxRaw(Wrapper type) {
boolean exact = false;
return box(type, exact, true);
}
/// Kludges for when raw values get accidentally boxed.
static int unboxRawInteger(Object x) {
if (x instanceof Integer)
return (int) x;
else
return (int) unboxLong(x, false);
}
static Integer reboxRawInteger(Object x) {
if (x instanceof Integer)
return (Integer) x;
else
return (int) unboxLong(x, false);
}
static Byte reboxRawByte(Object x) {
if (x instanceof Byte) return (Byte) x;
return boxByteRaw(unboxRawInteger(x));
}
static Short reboxRawShort(Object x) {
if (x instanceof Short) return (Short) x;
return boxShortRaw(unboxRawInteger(x));
}
static Boolean reboxRawBoolean(Object x) {
if (x instanceof Boolean) return (Boolean) x;
return boxBooleanRaw(unboxRawInteger(x));
}
static Character reboxRawCharacter(Object x) {
if (x instanceof Character) return (Character) x;
return boxCharacterRaw(unboxRawInteger(x));
}
static Float reboxRawFloat(Object x) {
if (x instanceof Float) return (Float) x;
return boxFloatRaw(unboxRawInteger(x));
}
static Long reboxRawLong(Object x) {
return (Long) x; //never a rebox
}
static Double reboxRawDouble(Object x) {
if (x instanceof Double) return (Double) x;
return boxDoubleRaw(unboxLong(x, true));
}
private static MethodType reboxType(Wrapper wrap) {
Class<?> boxType = wrap.wrapperType();
return MethodType.methodType(boxType, Object.class);
}
private static final EnumMap<Wrapper, MethodHandle>[]
REBOX_CONVERSIONS = newWrapperCaches(1);
/**
* Because we normalize primitive types to reduce the number of signatures,
* primitives are sometimes manipulated under an "erased" type,
* either int (for types other than long/double) or long (for all types).
* When the erased primitive value is then boxed into an Integer or Long,
* the final boxed primitive is sometimes required. This transformation
* is called a "rebox". It takes an Integer or Long and produces some
* other boxed value, typed (inexactly) as an Object
*/
public static MethodHandle rebox(Wrapper wrap) {
EnumMap<Wrapper, MethodHandle> cache = REBOX_CONVERSIONS[0];
MethodHandle mh = cache.get(wrap);
if (mh != null) {
return mh;
}
// slow path
switch (wrap) {
case OBJECT:
mh = IDENTITY; break;
case VOID:
throw new IllegalArgumentException("cannot rebox a void");
}
if (mh != null) {
cache.put(wrap, mh);
return mh;
}
// look up the method
String name = "reboxRaw" + wrap.simpleName();
MethodType type = reboxType(wrap);
try {
mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type);
mh = mh.asType(IDENTITY.type());
} catch (ReflectiveOperationException ex) {
mh = null;
}
if (mh != null) {
cache.put(wrap, mh);
return mh;
}
throw new IllegalArgumentException("cannot find rebox adapter for " + wrap);
}
public static MethodHandle rebox(Class<?> type) {
return rebox(Wrapper.forPrimitiveType(type));
}
/// Width-changing conversions between int and long.
static long widenInt(int x) {
return (long) x;
}
static Long widenBoxedInt(Integer x) {
return (long)(int)x;
}
static int narrowLong(long x) {
return (int) x;
}
static Integer narrowBoxedLong(Long x) {
return (int)(long) x;
} }
/// Constant functions /// Constant functions
@ -553,7 +367,7 @@ public class ValueConversions {
case OBJECT: case OBJECT:
case INT: case LONG: case FLOAT: case DOUBLE: case INT: case LONG: case FLOAT: case DOUBLE:
try { try {
mh = IMPL_LOOKUP.findStatic(THIS_CLASS, "zero"+wrap.simpleName(), type); mh = IMPL_LOOKUP.findStatic(THIS_CLASS, "zero"+wrap.wrapperSimpleName(), type);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
mh = null; mh = null;
} }
@ -564,12 +378,9 @@ public class ValueConversions {
return mh; return mh;
} }
// use the raw method // use zeroInt and cast the result
Wrapper rawWrap = wrap.rawPrimitive(); if (wrap.isSubwordOrInt() && wrap != Wrapper.INT) {
if (mh == null && rawWrap != wrap) { mh = MethodHandles.explicitCastArguments(zeroConstantFunction(Wrapper.INT), type);
mh = MethodHandles.explicitCastArguments(zeroConstantFunction(rawWrap), type);
}
if (mh != null) {
cache.put(wrap, mh); cache.put(wrap, mh);
return mh; return mh;
} }
@ -657,7 +468,7 @@ public class ValueConversions {
return t.cast(x); return t.cast(x);
} }
private static final MethodHandle IDENTITY, IDENTITY_I, IDENTITY_J, CAST_REFERENCE, ALWAYS_NULL, ALWAYS_ZERO, ZERO_OBJECT, IGNORE, EMPTY, NEW_ARRAY; private static final MethodHandle IDENTITY, CAST_REFERENCE, ALWAYS_NULL, ALWAYS_ZERO, ZERO_OBJECT, IGNORE, EMPTY, NEW_ARRAY;
static { static {
try { try {
MethodType idType = MethodType.genericMethodType(1); MethodType idType = MethodType.genericMethodType(1);
@ -666,8 +477,6 @@ public class ValueConversions {
MethodType ignoreType = idType.changeReturnType(void.class); MethodType ignoreType = idType.changeReturnType(void.class);
MethodType zeroObjectType = MethodType.genericMethodType(0); MethodType zeroObjectType = MethodType.genericMethodType(0);
IDENTITY = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", idType); IDENTITY = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", idType);
IDENTITY_I = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", MethodType.methodType(int.class, int.class));
IDENTITY_J = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", MethodType.methodType(long.class, long.class));
//CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType); //CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType);
CAST_REFERENCE = IMPL_LOOKUP.findStatic(THIS_CLASS, "castReference", castType); CAST_REFERENCE = IMPL_LOOKUP.findStatic(THIS_CLASS, "castReference", castType);
ALWAYS_NULL = IMPL_LOOKUP.findStatic(THIS_CLASS, "alwaysNull", idType); ALWAYS_NULL = IMPL_LOOKUP.findStatic(THIS_CLASS, "alwaysNull", idType);
@ -723,7 +532,6 @@ public class ValueConversions {
if (exact) { if (exact) {
MethodType xmt = MethodType.methodType(type, Object.class); MethodType xmt = MethodType.methodType(type, Object.class);
mh = MethodHandles.explicitCastArguments(mh, xmt); mh = MethodHandles.explicitCastArguments(mh, xmt);
//mh = AdapterMethodHandle.makeRetypeRaw(IMPL_TOKEN, xmt, mh);
} }
if (cache != null) if (cache != null)
cache.put(wrap, mh); cache.put(wrap, mh);
@ -735,8 +543,10 @@ public class ValueConversions {
} }
public static MethodHandle identity(Class<?> type) { public static MethodHandle identity(Class<?> type) {
// This stuff has been moved into MethodHandles: if (!type.isPrimitive())
// Reference identity has been moved into MethodHandles:
return MethodHandles.identity(type); return MethodHandles.identity(type);
return identity(Wrapper.findPrimitiveType(type));
} }
public static MethodHandle identity(Wrapper wrap) { public static MethodHandle identity(Wrapper wrap) {
@ -769,95 +579,203 @@ public class ValueConversions {
throw new IllegalArgumentException("cannot find identity for " + wrap); throw new IllegalArgumentException("cannot find identity for " + wrap);
} }
/// Float/non-float conversions. /// Primitive conversions.
// These are supported directly by the JVM, usually by a single instruction.
// In the case of narrowing to a subword, there may be a pair of instructions.
// In the case of booleans, there may be a helper routine to manage a 1-bit value.
// This is the full 8x8 matrix (minus the diagonal).
static float doubleToFloat(double x) { // narrow double to all other types:
static float doubleToFloat(double x) { // bytecode: d2f
return (float) x; return (float) x;
} }
static double floatToDouble(float x) { static long doubleToLong(double x) { // bytecode: d2l
return x;
}
// narrow double to integral type
static long doubleToLong(double x) {
return (long) x; return (long) x;
} }
static int doubleToInt(double x) { static int doubleToInt(double x) { // bytecode: d2i
return (int) x; return (int) x;
} }
static short doubleToShort(double x) { static short doubleToShort(double x) { // bytecodes: d2i, i2s
return (short) x; return (short) x;
} }
static char doubleToChar(double x) { static char doubleToChar(double x) { // bytecodes: d2i, i2c
return (char) x; return (char) x;
} }
static byte doubleToByte(double x) { static byte doubleToByte(double x) { // bytecodes: d2i, i2b
return (byte) x; return (byte) x;
} }
static boolean doubleToBoolean(double x) { static boolean doubleToBoolean(double x) {
return toBoolean((byte) x); return toBoolean((byte) x);
} }
// narrow float to integral type // widen float:
static long floatToLong(float x) { static double floatToDouble(float x) { // bytecode: f2d
return x;
}
// narrow float:
static long floatToLong(float x) { // bytecode: f2l
return (long) x; return (long) x;
} }
static int floatToInt(float x) { static int floatToInt(float x) { // bytecode: f2i
return (int) x; return (int) x;
} }
static short floatToShort(float x) { static short floatToShort(float x) { // bytecodes: f2i, i2s
return (short) x; return (short) x;
} }
static char floatToChar(float x) { static char floatToChar(float x) { // bytecodes: f2i, i2c
return (char) x; return (char) x;
} }
static byte floatToByte(float x) { static byte floatToByte(float x) { // bytecodes: f2i, i2b
return (byte) x; return (byte) x;
} }
static boolean floatToBoolean(float x) { static boolean floatToBoolean(float x) {
return toBoolean((byte) x); return toBoolean((byte) x);
} }
// widen integral type to double // widen long:
static double longToDouble(long x) { static double longToDouble(long x) { // bytecode: l2d
return x; return x;
} }
static double intToDouble(int x) { static float longToFloat(long x) { // bytecode: l2f
return x; return x;
} }
static double shortToDouble(short x) { // narrow long:
return x; static int longToInt(long x) { // bytecode: l2i
return (int) x;
} }
static double charToDouble(char x) { static short longToShort(long x) { // bytecodes: f2i, i2s
return x; return (short) x;
} }
static double byteToDouble(byte x) { static char longToChar(long x) { // bytecodes: f2i, i2c
return x; return (char) x;
} }
static double booleanToDouble(boolean x) { static byte longToByte(long x) { // bytecodes: f2i, i2b
return fromBoolean(x); return (byte) x;
}
static boolean longToBoolean(long x) {
return toBoolean((byte) x);
} }
// widen integral type to float // widen int:
static float longToFloat(long x) { static double intToDouble(int x) { // bytecode: i2d
return x; return x;
} }
static float intToFloat(int x) { static float intToFloat(int x) { // bytecode: i2f
return x; return x;
} }
static float shortToFloat(short x) { static long intToLong(int x) { // bytecode: i2l
return x; return x;
} }
static float charToFloat(char x) { // narrow int:
static short intToShort(int x) { // bytecode: i2s
return (short) x;
}
static char intToChar(int x) { // bytecode: i2c
return (char) x;
}
static byte intToByte(int x) { // bytecode: i2b
return (byte) x;
}
static boolean intToBoolean(int x) {
return toBoolean((byte) x);
}
// widen short:
static double shortToDouble(short x) { // bytecode: i2d (implicit 's2i')
return x; return x;
} }
static float byteToFloat(byte x) { static float shortToFloat(short x) { // bytecode: i2f (implicit 's2i')
return x; return x;
} }
static long shortToLong(short x) { // bytecode: i2l (implicit 's2i')
return x;
}
static int shortToInt(short x) { // (implicit 's2i')
return x;
}
// narrow short:
static char shortToChar(short x) { // bytecode: i2c (implicit 's2i')
return (char)x;
}
static byte shortToByte(short x) { // bytecode: i2b (implicit 's2i')
return (byte)x;
}
static boolean shortToBoolean(short x) {
return toBoolean((byte) x);
}
// widen char:
static double charToDouble(char x) { // bytecode: i2d (implicit 'c2i')
return x;
}
static float charToFloat(char x) { // bytecode: i2f (implicit 'c2i')
return x;
}
static long charToLong(char x) { // bytecode: i2l (implicit 'c2i')
return x;
}
static int charToInt(char x) { // (implicit 'c2i')
return x;
}
// narrow char:
static short charToShort(char x) { // bytecode: i2s (implicit 'c2i')
return (short)x;
}
static byte charToByte(char x) { // bytecode: i2b (implicit 'c2i')
return (byte)x;
}
static boolean charToBoolean(char x) {
return toBoolean((byte) x);
}
// widen byte:
static double byteToDouble(byte x) { // bytecode: i2d (implicit 'b2i')
return x;
}
static float byteToFloat(byte x) { // bytecode: i2f (implicit 'b2i')
return x;
}
static long byteToLong(byte x) { // bytecode: i2l (implicit 'b2i')
return x;
}
static int byteToInt(byte x) { // (implicit 'b2i')
return x;
}
static short byteToShort(byte x) { // bytecode: i2s (implicit 'b2i')
return (short)x;
}
static char byteToChar(byte x) { // bytecode: i2b (implicit 'b2i')
return (char)x;
}
// narrow byte to boolean:
static boolean byteToBoolean(byte x) {
return toBoolean(x);
}
// widen boolean to all types:
static double booleanToDouble(boolean x) {
return fromBoolean(x);
}
static float booleanToFloat(boolean x) { static float booleanToFloat(boolean x) {
return fromBoolean(x); return fromBoolean(x);
} }
static long booleanToLong(boolean x) {
return fromBoolean(x);
}
static int booleanToInt(boolean x) {
return fromBoolean(x);
}
static short booleanToShort(boolean x) {
return fromBoolean(x);
}
static char booleanToChar(boolean x) {
return (char)fromBoolean(x);
}
static byte booleanToByte(boolean x) {
return fromBoolean(x);
}
// helpers to force boolean into the conversion scheme:
static boolean toBoolean(byte x) { static boolean toBoolean(byte x) {
// see javadoc for MethodHandles.explicitCastArguments // see javadoc for MethodHandles.explicitCastArguments
return ((x & 1) != 0); return ((x & 1) != 0);
@ -868,62 +786,48 @@ public class ValueConversions {
} }
private static final EnumMap<Wrapper, MethodHandle>[] private static final EnumMap<Wrapper, MethodHandle>[]
CONVERT_FLOAT_FUNCTIONS = newWrapperCaches(4); CONVERT_PRIMITIVE_FUNCTIONS = newWrapperCaches(Wrapper.values().length);
static MethodHandle convertFloatFunction(Wrapper wrap, boolean toFloat, boolean doubleSize) { public static MethodHandle convertPrimitive(Wrapper wsrc, Wrapper wdst) {
EnumMap<Wrapper, MethodHandle> cache = CONVERT_FLOAT_FUNCTIONS[(toFloat?1:0)+(doubleSize?2:0)]; EnumMap<Wrapper, MethodHandle> cache = CONVERT_PRIMITIVE_FUNCTIONS[wsrc.ordinal()];
MethodHandle mh = cache.get(wrap); MethodHandle mh = cache.get(wdst);
if (mh != null) { if (mh != null) {
return mh; return mh;
} }
// slow path // slow path
Wrapper fwrap = (doubleSize ? Wrapper.DOUBLE : Wrapper.FLOAT); Class<?> src = wsrc.primitiveType();
Class<?> fix = wrap.primitiveType(); Class<?> dst = wdst.primitiveType();
Class<?> flt = (doubleSize ? double.class : float.class); MethodType type = src == void.class ? MethodType.methodType(dst) : MethodType.methodType(dst, src);
Class<?> src = toFloat ? fix : flt; if (wsrc == wdst) {
Class<?> dst = toFloat ? flt : fix; mh = identity(src);
if (src == dst) return identity(wrap); } else if (wsrc == Wrapper.VOID) {
MethodType type = MethodType.methodType(dst, src); mh = zeroConstantFunction(wdst);
switch (wrap) { } else if (wdst == Wrapper.VOID) {
case VOID: mh = MethodHandles.dropArguments(EMPTY, 0, src); // Defer back to MethodHandles.
mh = toFloat ? zeroConstantFunction(fwrap) : MethodHandles.dropArguments(EMPTY, 0, flt); } else if (wsrc == Wrapper.OBJECT) {
break; mh = unboxCast(dst);
case OBJECT: } else if (wdst == Wrapper.OBJECT) {
mh = toFloat ? unbox(flt) : box(flt); mh = box(src);
break; } else {
default: assert(src.isPrimitive() && dst.isPrimitive());
try { try {
mh = IMPL_LOOKUP.findStatic(THIS_CLASS, src.getSimpleName()+"To"+capitalize(dst.getSimpleName()), type); mh = IMPL_LOOKUP.findStatic(THIS_CLASS, src.getSimpleName()+"To"+capitalize(dst.getSimpleName()), type);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
mh = null; mh = null;
} }
break;
} }
if (mh != null) { if (mh != null) {
assert(mh.type() == type) : mh; assert(mh.type() == type) : mh;
cache.put(wrap, mh); cache.put(wdst, mh);
return mh; return mh;
} }
throw new IllegalArgumentException("cannot find float conversion constant for " + throw new IllegalArgumentException("cannot find primitive conversion function for " +
src.getSimpleName()+" -> "+dst.getSimpleName()); src.getSimpleName()+" -> "+dst.getSimpleName());
} }
public static MethodHandle convertFromFloat(Class<?> fixType) { public static MethodHandle convertPrimitive(Class<?> src, Class<?> dst) {
Wrapper wrap = Wrapper.forPrimitiveType(fixType); return convertPrimitive(Wrapper.forPrimitiveType(src), Wrapper.forPrimitiveType(dst));
return convertFloatFunction(wrap, false, false);
}
public static MethodHandle convertFromDouble(Class<?> fixType) {
Wrapper wrap = Wrapper.forPrimitiveType(fixType);
return convertFloatFunction(wrap, false, true);
}
public static MethodHandle convertToFloat(Class<?> fixType) {
Wrapper wrap = Wrapper.forPrimitiveType(fixType);
return convertFloatFunction(wrap, true, false);
}
public static MethodHandle convertToDouble(Class<?> fixType) {
Wrapper wrap = Wrapper.forPrimitiveType(fixType);
return convertFloatFunction(wrap, true, true);
} }
private static String capitalize(String x) { private static String capitalize(String x) {

View file

@ -168,6 +168,46 @@ public class VerifyAccess {
return false; return false;
} }
/**
* Decide if the given method type, attributed to a member or symbolic
* reference of a given reference class, is really visible to that class.
* @param type the supposed type of a member or symbolic reference of refc
* @param refc
*/
public static boolean isTypeVisible(Class<?> type, Class<?> refc) {
if (type == refc) return true; // easy check
while (type.isArray()) type = type.getComponentType();
if (type.isPrimitive() || type == Object.class) return true;
ClassLoader parent = type.getClassLoader();
if (parent == null) return true;
ClassLoader child = refc.getClassLoader();
if (child == null) return false;
if (parent == child || loadersAreRelated(parent, child, true))
return true;
// Do it the hard way: Look up the type name from the refc loader.
try {
Class<?> res = child.loadClass(type.getName());
return (type == res);
} catch (ClassNotFoundException ex) {
return false;
}
}
/**
* Decide if the given method type, attributed to a member or symbolic
* reference of a given reference class, is really visible to that class.
* @param type the supposed type of a member or symbolic reference of refc
* @param refc
*/
public static boolean isTypeVisible(java.lang.invoke.MethodType type, Class<?> refc) {
for (int n = -1, max = type.parameterCount(); n < max; n++) {
Class<?> ptype = (n < 0 ? type.returnType() : type.parameterType(n));
if (!isTypeVisible(ptype, refc))
return false;
}
return true;
}
/** /**
* Test if two classes have the same class loader and package qualifier. * Test if two classes have the same class loader and package qualifier.
* @param class1 * @param class1

View file

@ -122,8 +122,6 @@ public class VerifyType {
return isNullConversion(recv.returnType(), call.returnType()); return isNullConversion(recv.returnType(), call.returnType());
} }
//TO DO: isRawConversion
/** /**
* Determine if the JVM verifier allows a value of type call to be * Determine if the JVM verifier allows a value of type call to be
* passed to a formal parameter (or return variable) of type recv. * passed to a formal parameter (or return variable) of type recv.
@ -188,40 +186,6 @@ public class VerifyType {
return -1; return -1;
} }
public static int canPassRaw(Class<?> src, Class<?> dst) {
if (dst.isPrimitive()) {
if (dst == void.class)
// As above, return anything to a caller expecting void.
return 1;
if (src == void.class)
// Special permission for raw conversions: allow a void
// to be captured as a garbage int.
// Caller promises that the actual value will be disregarded.
return dst == int.class ? 1 : 0;
if (isNullType(src))
// Special permission for raw conversions: allow a null
// to be reinterpreted as anything. For objects, it is safe,
// and for primitives you get a garbage value (probably zero).
return 1;
if (!src.isPrimitive())
return 0;
Wrapper sw = Wrapper.forPrimitiveType(src);
Wrapper dw = Wrapper.forPrimitiveType(dst);
if (sw.stackSlots() == dw.stackSlots())
return 1; // can do a reinterpret-cast on a stacked primitive
if (sw.isSubwordOrInt() && dw == Wrapper.VOID)
return 1; // can drop an outgoing int value
return 0;
} else if (src.isPrimitive()) {
return 0;
}
// Both references.
if (isNullReferenceConversion(src, dst))
return 1;
return -1;
}
public static boolean isSpreadArgType(Class<?> spreadArg) { public static boolean isSpreadArgType(Class<?> spreadArg) {
return spreadArg.isArray(); return spreadArg.isArray();
} }

View file

@ -47,7 +47,8 @@ public enum Wrapper {
private final Object zero; private final Object zero;
private final Object emptyArray; private final Object emptyArray;
private final int format; private final int format;
private final String simpleName; private final String wrapperSimpleName;
private final String primitiveSimpleName;
private Wrapper(Class<?> wtype, Class<?> ptype, char tchar, Object zero, Object emptyArray, int format) { private Wrapper(Class<?> wtype, Class<?> ptype, char tchar, Object zero, Object emptyArray, int format) {
this.wrapperType = wtype; this.wrapperType = wtype;
@ -56,12 +57,13 @@ public enum Wrapper {
this.zero = zero; this.zero = zero;
this.emptyArray = emptyArray; this.emptyArray = emptyArray;
this.format = format; this.format = format;
this.simpleName = wtype.getSimpleName(); this.wrapperSimpleName = wtype.getSimpleName();
this.primitiveSimpleName = ptype.getSimpleName();
} }
/** For debugging, give the details of this wrapper. */ /** For debugging, give the details of this wrapper. */
public String detailString() { public String detailString() {
return simpleName+ return wrapperSimpleName+
java.util.Arrays.asList(wrapperType, primitiveType, java.util.Arrays.asList(wrapperType, primitiveType,
basicTypeChar, zero, basicTypeChar, zero,
"0x"+Integer.toHexString(format)); "0x"+Integer.toHexString(format));
@ -418,7 +420,11 @@ public enum Wrapper {
/** What is the simple name of the wrapper type? /** What is the simple name of the wrapper type?
*/ */
public String simpleName() { return simpleName; } public String wrapperSimpleName() { return wrapperSimpleName; }
/** What is the simple name of the primitive type?
*/
public String primitiveSimpleName() { return primitiveSimpleName; }
// /** Wrap a value in the given type, which may be either a primitive or wrapper type. // /** Wrap a value in the given type, which may be either a primitive or wrapper type.
// * Performs standard primitive conversions, including truncation and float conversions. // * Performs standard primitive conversions, including truncation and float conversions.
@ -456,26 +462,31 @@ public enum Wrapper {
// If the target type is an interface, perform no runtime check. // If the target type is an interface, perform no runtime check.
// (This loophole is safe, and is allowed by the JVM verifier.) // (This loophole is safe, and is allowed by the JVM verifier.)
// If the target type is a primitive, change it to a wrapper. // If the target type is a primitive, change it to a wrapper.
assert(!type.isPrimitive());
if (!type.isInterface())
type.cast(x);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
T result = (T) x; // unchecked warning is expected here T result = (T) x; // unchecked warning is expected here
return result; return result;
} }
Class<T> wtype = wrapperType(type); Class<T> wtype = wrapperType(type);
if (wtype.isInstance(x)) { if (wtype.isInstance(x)) {
@SuppressWarnings("unchecked") return wtype.cast(x);
T result = (T) x; // unchecked warning is expected here
return result;
} }
Class<?> sourceType = x.getClass(); // throw NPE if x is null
if (!isCast) { if (!isCast) {
Class<?> sourceType = x.getClass(); // throw NPE if x is null
Wrapper source = findWrapperType(sourceType); Wrapper source = findWrapperType(sourceType);
if (source == null || !this.isConvertibleFrom(source)) { if (source == null || !this.isConvertibleFrom(source)) {
throw newClassCastException(wtype, sourceType); throw newClassCastException(wtype, sourceType);
} }
} else if (x == null) {
@SuppressWarnings("unchecked")
T z = (T) zero;
return z;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
T result = (T) wrap(x); // unchecked warning is expected here T result = (T) wrap(x); // unchecked warning is expected here
assert result.getClass() == wtype; assert (result == null ? Void.class : result.getClass()) == wtype;
return result; return result;
} }
@ -523,7 +534,7 @@ public enum Wrapper {
case 'S': return Short.valueOf((short) xn.intValue()); case 'S': return Short.valueOf((short) xn.intValue());
case 'B': return Byte.valueOf((byte) xn.intValue()); case 'B': return Byte.valueOf((byte) xn.intValue());
case 'C': return Character.valueOf((char) xn.intValue()); case 'C': return Character.valueOf((char) xn.intValue());
case 'Z': return Boolean.valueOf(boolValue(xn.longValue())); case 'Z': return Boolean.valueOf(boolValue(xn.byteValue()));
} }
throw new InternalError("bad wrapper"); throw new InternalError("bad wrapper");
} }
@ -546,72 +557,11 @@ public enum Wrapper {
case 'S': return Short.valueOf((short) x); case 'S': return Short.valueOf((short) x);
case 'B': return Byte.valueOf((byte) x); case 'B': return Byte.valueOf((byte) x);
case 'C': return Character.valueOf((char) x); case 'C': return Character.valueOf((char) x);
case 'Z': return Boolean.valueOf(boolValue(x)); case 'Z': return Boolean.valueOf(boolValue((byte) x));
} }
throw new InternalError("bad wrapper"); throw new InternalError("bad wrapper");
} }
/** Wrap a value (a long or smaller value) in this wrapper's type.
* Does not perform floating point conversion.
* Produces a {@code Long} for {@code OBJECT}, although the exact type
* of the operand is not known.
* Returns null for {@code VOID}.
*/
public Object wrapRaw(long x) {
switch (basicTypeChar) {
case 'F': return Float.valueOf(Float.intBitsToFloat((int)x));
case 'D': return Double.valueOf(Double.longBitsToDouble(x));
case 'L': // same as 'J':
case 'J': return (Long) x;
}
// Other wrapping operations are just the same, given that the
// operand is already promoted to an int.
return wrap((int)x);
}
/** Produce bitwise value which encodes the given wrapped value.
* Does not perform floating point conversion.
* Returns zero for {@code VOID}.
*/
public long unwrapRaw(Object x) {
switch (basicTypeChar) {
case 'F': return Float.floatToRawIntBits((Float) x);
case 'D': return Double.doubleToRawLongBits((Double) x);
case 'L': throw newIllegalArgumentException("cannot unwrap from sobject type");
case 'V': return 0;
case 'I': return (int)(Integer) x;
case 'J': return (long)(Long) x;
case 'S': return (short)(Short) x;
case 'B': return (byte)(Byte) x;
case 'C': return (char)(Character) x;
case 'Z': return (boolean)(Boolean) x ? 1 : 0;
}
throw new InternalError("bad wrapper");
}
/** Report what primitive type holds this guy's raw value. */
public Class<?> rawPrimitiveType() {
return rawPrimitive().primitiveType();
}
/** Report, as a wrapper, what primitive type holds this guy's raw value.
* Returns self for INT, LONG, OBJECT; returns LONG for DOUBLE,
* else returns INT.
*/
public Wrapper rawPrimitive() {
switch (basicTypeChar) {
case 'S': case 'B':
case 'C': case 'Z':
case 'V':
case 'F':
return INT;
case 'D':
return LONG;
}
return this;
}
private static Number numberValue(Object x) { private static Number numberValue(Object x) {
if (x instanceof Number) return (Number)x; if (x instanceof Number) return (Number)x;
if (x instanceof Character) return (int)(Character)x; if (x instanceof Character) return (int)(Character)x;
@ -620,7 +570,10 @@ public enum Wrapper {
return (Number)x; return (Number)x;
} }
private static boolean boolValue(long bits) { // Parameter type of boolValue must be byte, because
// MethodHandles.explicitCastArguments defines boolean
// conversion as first converting to byte.
private static boolean boolValue(byte bits) {
bits &= 1; // simple 31-bit zero extension bits &= 1; // simple 31-bit zero extension
return (bits != 0); return (bits != 0);
} }

View file

@ -677,6 +677,14 @@ public final class Unsafe {
*/ */
public native Object staticFieldBase(Field f); public native Object staticFieldBase(Field f);
/**
* Detect if the given class may need to be initialized. This is often
* needed in conjunction with obtaining the static field base of a
* class.
* @return false only if a call to {@code ensureClassInitialized} would have no effect
*/
public native boolean shouldBeInitialized(Class<?> c);
/** /**
* Ensure the given class has been initialized. This is often * Ensure the given class has been initialized. This is often
* needed in conjunction with obtaining the static field base of a * needed in conjunction with obtaining the static field base of a

View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/*
7157574 method handles returned by reflective lookup API sometimes have wrong receiver type
When an inherited non-static field or method is looked up in a class C using Lookup.findVirtual(C...), etc., the JSR 292 API, the first argument of the resulting method handle must be the receiver ('this'), and must be the requested class (or more specific, in the case of findSpecial or a lookup of a protected method).
But currently, if a supertype T defines the looked-up method or field and C inherits it, the returned method handle might have the more specific initial type T.
The relevant javadoc (and 292 spec.) is as follows:
* The formal parameter {@code this} stands for the self-reference of type {@code C};
* if it is present, it is always the leading argument to the method handle invocation.
* (In the case of some {@code protected} members, {@code this} may be
* restricted in type to the lookup class; see below.)
Because of this bug, all of the assertions fail in the following example:
*/
/* @test
* @bug 7157574
* @summary method handles returned by reflective lookup API sometimes have wrong receiver type
*
* @run main Test7157574
*/
import java.lang.invoke.*;
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*;
public class Test7157574 {
interface Intf { void ig1(); void ig2(); void ig3(); void ig4(); void m1(); }
static abstract class Super implements Intf { public abstract void m2(); public int f2; }
static abstract class Sub extends Super { }
public static void main(String... av) throws Throwable {
MethodHandle m1 = lookup().findVirtual(Sub.class, "m1", methodType(void.class));
System.out.println(m1);
MethodHandle m2 = lookup().findVirtual(Sub.class, "m2", methodType(void.class));
System.out.println(m2);
MethodHandle f2 = lookup().findGetter(Sub.class, "f2", int.class);
System.out.println(f2);
MethodHandle f2s = lookup().findSetter(Sub.class, "f2", int.class);
System.out.println(f2s);
MethodHandle chc = lookup().findVirtual(Sub.class, "hashCode", methodType(int.class));
System.out.println(chc);
MethodHandle ihc = lookup().findVirtual(Intf.class, "hashCode", methodType(int.class));
System.out.println(ihc);
assertEquals(Sub.class, m1.type().parameterType(0));
assertEquals(Sub.class, m2.type().parameterType(0));
assertEquals(Sub.class, f2.type().parameterType(0));
assertEquals(Sub.class, f2s.type().parameterType(0));
assertEquals(Sub.class, chc.type().parameterType(0));
assertEquals(Intf.class, ihc.type().parameterType(0));
// test the MHs on a concrete version of Sub
class C extends Sub {
public void m1() { this.f2 = -1; }
public void m2() { this.f2 = -2; }
// Pack the vtable of Intf with leading junk:
private void ig() { throw new RuntimeException(); }
public void ig1() { ig(); }
public void ig2() { ig(); }
public void ig3() { ig(); }
public void ig4() { ig(); }
}
testConcrete(new C(), m1, m2, f2, f2s, chc, ihc);
}
private static void testConcrete(Sub s,
MethodHandle m1, MethodHandle m2,
MethodHandle f2, MethodHandle f2s,
MethodHandle chc, MethodHandle ihc
) throws Throwable {
s.f2 = 0;
m1.invokeExact(s);
assertEquals(-1, s.f2);
m2.invokeExact(s);
assertEquals(-2, s.f2);
s.f2 = 2;
assertEquals(2, (int) f2.invokeExact(s));
f2s.invokeExact(s, 0);
assertEquals(0, s.f2);
assertEquals(s.hashCode(), (int) chc.invokeExact(s));
assertEquals(s.hashCode(), (int) ihc.invokeExact((Intf)s));
}
private static void assertEquals(Object expect, Object observe) {
if (java.util.Objects.equals(expect, observe)) return;
String msg = ("expected "+expect+" but observed "+observe);
System.out.println("FAILED: "+msg);
throw new AssertionError(msg);
}
}

View file

@ -68,24 +68,6 @@ public class InvokeGenericTest {
public InvokeGenericTest() { public InvokeGenericTest() {
} }
@Before
public void checkImplementedPlatform() {
boolean platformOK = false;
Properties properties = System.getProperties();
String vers = properties.getProperty("java.vm.version");
String name = properties.getProperty("java.vm.name");
String arch = properties.getProperty("os.arch");
if ((arch.equals("amd64") || arch.equals("i386") || arch.equals("x86") ||
arch.equals("x86_64") || arch.equals("sparc") || arch.equals("sparcv9")) &&
(name.contains("Client") || name.contains("Server"))
) {
platformOK = true;
} else {
System.err.println("Skipping tests for unsupported platform: "+Arrays.asList(vers, name, arch));
}
assumeTrue(platformOK);
}
String testName; String testName;
static int allPosTests, allNegTests; static int allPosTests, allNegTests;
int posTests, negTests; int posTests, negTests;

View file

@ -54,7 +54,6 @@ import static org.junit.Assert.*;
/** /**
* @author jrose * @author jrose
*/ */
@SuppressWarnings("LocalVariableHidesMemberVariable")
public class JavaDocExamplesTest { public class JavaDocExamplesTest {
/** Wrapper for running the JUnit tests in this module. /** Wrapper for running the JUnit tests in this module.
* Put JUnit on the classpath! * Put JUnit on the classpath!

View file

@ -0,0 +1,143 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @summary BoundMethodHandle tests with primitive types
* @compile MaxTest.java
* @run junit/othervm test.java.lang.invoke.MaxTest
*/
package test.java.lang.invoke;
import static org.junit.Assert.assertEquals;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import org.junit.Test;
public class MaxTest {
static MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private MethodHandle getMax(Class<?> t) throws Throwable {
return LOOKUP.findStatic(Math.class, "max", MethodType.methodType(t, t, t));
}
static int ITERATION_COUNT = 40000;
static {
String iterations = System.getProperty(MaxTest.class.getSimpleName() + ".ITERATION_COUNT");
if (iterations == null) {
iterations = System.getProperty(MaxTest.class.getName() + ".ITERATION_COUNT");
}
if (iterations != null) {
ITERATION_COUNT = Integer.parseInt(iterations);
}
}
@Test
public void testMaxLong() throws Throwable {
final Class<?> C = long.class;
final long P = 23L;
final long Q = 42L;
final long R = Math.max(P, Q);
for (int i = 0; i < ITERATION_COUNT; ++i) {
MethodHandle h = getMax(C);
assertEquals((long) h.invokeExact(P, Q), R);
MethodHandle bh = MethodHandles.insertArguments(h, 0, P);
assertEquals((long) bh.invokeExact(Q), R);
MethodHandle bbh = MethodHandles.insertArguments(bh, 0, Q);
assertEquals((long) bbh.invokeExact(), R);
MethodHandle b2h = MethodHandles.insertArguments(h, 1, Q);
assertEquals((long) b2h.invokeExact(P), R);
MethodHandle bb2h = MethodHandles.insertArguments(b2h, 0, P);
assertEquals((long) bb2h.invokeExact(), R);
}
}
@Test
public void testMaxInt() throws Throwable {
final Class<?> C = int.class;
final int P = 23;
final int Q = 42;
final int R = Math.max(P, Q);
for (int i = 0; i < ITERATION_COUNT; ++i) {
MethodHandle h = getMax(C);
assertEquals((int) h.invokeExact(P, Q), R);
MethodHandle bh = MethodHandles.insertArguments(h, 0, P);
assertEquals((int) bh.invokeExact(Q), R);
MethodHandle bbh = MethodHandles.insertArguments(bh, 0, Q);
assertEquals((int) bbh.invokeExact(), R);
MethodHandle b2h = MethodHandles.insertArguments(h, 1, Q);
assertEquals((int) b2h.invokeExact(P), R);
MethodHandle bb2h = MethodHandles.insertArguments(b2h, 0, P);
assertEquals((int) bb2h.invokeExact(), R);
}
}
@Test
public void testMaxFloat() throws Throwable {
final Class<?> C = float.class;
final float P = 23F;
final float Q = 42F;
final float R = Math.max(P, Q);
final float D = 0.1F;
for (int i = 0; i < ITERATION_COUNT; ++i) {
MethodHandle h = getMax(C);
assertEquals((float) h.invokeExact(P, Q), R, D);
MethodHandle bh = MethodHandles.insertArguments(h, 0, P);
assertEquals((float) bh.invokeExact(Q), R, D);
MethodHandle bbh = MethodHandles.insertArguments(bh, 0, Q);
assertEquals((float) bbh.invokeExact(), R, D);
MethodHandle b2h = MethodHandles.insertArguments(h, 1, Q);
assertEquals((float) b2h.invokeExact(P), R, D);
MethodHandle bb2h = MethodHandles.insertArguments(b2h, 0, P);
assertEquals((float) bb2h.invokeExact(), R, D);
}
}
@Test
public void testMaxDouble() throws Throwable {
final Class<?> C = double.class;
final double P = 23F;
final double Q = 42F;
final double R = Math.max(P, Q);
final double D = 0.1;
for (int i = 0; i < ITERATION_COUNT; ++i) {
MethodHandle h = getMax(C);
assertEquals((double) h.invokeExact(P, Q), R, D);
MethodHandle bh = MethodHandles.insertArguments(h, 0, P);
assertEquals((double) bh.invokeExact(Q), R, D);
MethodHandle bbh = MethodHandles.insertArguments(bh, 0, Q);
assertEquals((double) bbh.invokeExact(), R, D);
MethodHandle b2h = MethodHandles.insertArguments(h, 1, Q);
assertEquals((double) b2h.invokeExact(P), R, D);
MethodHandle bb2h = MethodHandles.insertArguments(b2h, 0, P);
assertEquals((double) bb2h.invokeExact(), R, D);
}
}
}

View file

@ -25,12 +25,13 @@
/* @test /* @test
* @summary unit tests for java.lang.invoke.MethodHandles * @summary unit tests for java.lang.invoke.MethodHandles
* @compile -source 7 -target 7 MethodHandlesTest.java * @compile MethodHandlesTest.java remote/RemoteExample.java
* @run junit/othervm test.java.lang.invoke.MethodHandlesTest * @run junit/othervm test.java.lang.invoke.MethodHandlesTest
*/ */
package test.java.lang.invoke; package test.java.lang.invoke;
import test.java.lang.invoke.remote.RemoteExample;
import java.lang.invoke.*; import java.lang.invoke.*;
import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*; import java.lang.reflect.*;
@ -57,8 +58,13 @@ public class MethodHandlesTest {
// Set this true during development if you want to fast-forward to // Set this true during development if you want to fast-forward to
// a particular new, non-working test. Tests which are known to // a particular new, non-working test. Tests which are known to
// work (or have recently worked) test this flag and return on true. // work (or have recently worked) test this flag and return on true.
static boolean CAN_SKIP_WORKING = false; static final boolean CAN_SKIP_WORKING;
//static { CAN_SKIP_WORKING = true; } static {
String vstr = System.getProperty(THIS_CLASS.getSimpleName()+".CAN_SKIP_WORKING");
if (vstr == null)
vstr = System.getProperty(THIS_CLASS.getName()+".CAN_SKIP_WORKING");
CAN_SKIP_WORKING = Boolean.parseBoolean(vstr);
}
// Set 'true' to do about 15x fewer tests, especially those redundant with RicochetTest. // Set 'true' to do about 15x fewer tests, especially those redundant with RicochetTest.
// This might be useful with -Xcomp stress tests that compile all method handles. // This might be useful with -Xcomp stress tests that compile all method handles.
@ -71,62 +77,6 @@ public class MethodHandlesTest {
} finally { printCounts(); verbosity -= 9; } } finally { printCounts(); verbosity -= 9; }
} }
// current failures
@Test //@Ignore("failure in call to makeRawRetypeOnly in ToGeneric")
public void testFail_1() throws Throwable {
// AMH.<init>: IllegalArgumentException: bad adapter (conversion=0xfffab300): adapter pushes too many parameters
testSpreadArguments(int.class, 0, 6);
}
@Test //@Ignore("failure in JVM when expanding the stack using asm stub for _adapter_spread_args")
public void testFail_2() throws Throwable {
// if CONV_OP_IMPLEMENTED_MASK includes OP_SPREAD_ARGS, this crashes:
testSpreadArguments(Object.class, 0, 2);
}
@Test //@Ignore("IllArgEx failure in call to ToGeneric.make")
public void testFail_3() throws Throwable {
// ToGeneric.<init>: UnsupportedOperationException: NYI: primitive parameters must follow references; entryType = (int,java.lang.Object)java.lang.Object
testSpreadArguments(int.class, 1, 2);
}
@Test //@Ignore("IllArgEx failure in call to ToGeneric.make")
public void testFail_4() throws Throwable {
// ToGeneric.<init>: UnsupportedOperationException: NYI: primitive parameters must follow references; entryType = (int,java.lang.Object)java.lang.Object
testCollectArguments(int.class, 1, 2);
}
@Test //@Ignore("cannot collect leading primitive types")
public void testFail_5() throws Throwable {
// ToGeneric.<init>: UnsupportedOperationException: NYI: primitive parameters must follow references; entryType = (int,java.lang.Object)java.lang.Object
testInvokers(MethodType.genericMethodType(2).changeParameterType(0, int.class));
}
@Test //@Ignore("should not insert arguments beyond MethodHandlePushLimit")
public void testFail_6() throws Throwable {
// ValueConversions.varargsArray: UnsupportedOperationException: NYI: cannot form a varargs array of length 13
testInsertArguments(0, 0, MAX_ARG_INCREASE+10);
}
@Test //@Ignore("permuteArguments has trouble with double slots")
public void testFail_7() throws Throwable {
testPermuteArguments(new Object[]{10, 200L},
new Class<?>[]{Integer.class, long.class},
new int[]{1,0});
testPermuteArguments(new Object[]{10, 200L, 5000L},
new Class<?>[]{Integer.class, long.class, long.class},
new int[]{2,0,1}); //rot
testPermuteArguments(new Object[]{10, 200L, 5000L},
new Class<?>[]{Integer.class, long.class, long.class},
new int[]{1,2,0}); //rot
testPermuteArguments(new Object[]{10, 200L, 5000L},
new Class<?>[]{Integer.class, long.class, long.class},
new int[]{2,1,0}); //swap
testPermuteArguments(new Object[]{10, 200L, 5000L},
new Class<?>[]{Integer.class, long.class, long.class},
new int[]{0,1,2,2}); //dup
testPermuteArguments(new Object[]{10, 200L, 5000L},
new Class<?>[]{Integer.class, long.class, long.class},
new int[]{2,0,1,2});
testPermuteArguments(new Object[]{10, 200L, 5000L},
new Class<?>[]{Integer.class, long.class, long.class},
new int[]{2,2,0,1});
//testPermuteArguments(4, Integer.class, 2, long.class, 6);
}
static final int MAX_ARG_INCREASE = 3; static final int MAX_ARG_INCREASE = 3;
public MethodHandlesTest() { public MethodHandlesTest() {
@ -180,7 +130,7 @@ public class MethodHandlesTest {
static Object logEntry(String name, Object... args) { static Object logEntry(String name, Object... args) {
return Arrays.asList(name, Arrays.asList(args)); return Arrays.asList(name, Arrays.asList(args));
} }
static Object called(String name, Object... args) { public static Object called(String name, Object... args) {
Object entry = logEntry(name, args); Object entry = logEntry(name, args);
calledLog.add(entry); calledLog.add(entry);
return entry; return entry;
@ -280,6 +230,8 @@ public class MethodHandlesTest {
{ param = c; break; } { param = c; break; }
} }
} }
if (param.isInterface() && param.isAssignableFrom(List.class))
return Arrays.asList("#"+nextArg());
if (param.isInterface() || param.isAssignableFrom(String.class)) if (param.isInterface() || param.isAssignableFrom(String.class))
return "#"+nextArg(); return "#"+nextArg();
else else
@ -399,6 +351,8 @@ public class MethodHandlesTest {
static final Lookup PRIVATE = MethodHandles.lookup(); static final Lookup PRIVATE = MethodHandles.lookup();
// This lookup is good for package-private members but not private ones. // This lookup is good for package-private members but not private ones.
static final Lookup PACKAGE = PackageSibling.lookup(); static final Lookup PACKAGE = PackageSibling.lookup();
// This lookup is good for public members and protected members of PubExample
static final Lookup SUBCLASS = RemoteExample.lookup();
// This lookup is good only for public members. // This lookup is good only for public members.
static final Lookup PUBLIC = MethodHandles.publicLookup(); static final Lookup PUBLIC = MethodHandles.publicLookup();
@ -412,9 +366,11 @@ public class MethodHandlesTest {
@Override public String toString() { return name; } @Override public String toString() { return name; }
public void v0() { called("v0", this); } public void v0() { called("v0", this); }
protected void pro_v0() { called("pro_v0", this); }
void pkg_v0() { called("pkg_v0", this); } void pkg_v0() { called("pkg_v0", this); }
private void pri_v0() { called("pri_v0", this); } private void pri_v0() { called("pri_v0", this); }
public static void s0() { called("s0"); } public static void s0() { called("s0"); }
protected static void pro_s0() { called("pro_s0"); }
static void pkg_s0() { called("pkg_s0"); } static void pkg_s0() { called("pkg_s0"); }
private static void pri_s0() { called("pri_s0"); } private static void pri_s0() { called("pri_s0"); }
@ -434,12 +390,21 @@ public class MethodHandlesTest {
// for testing findConstructor: // for testing findConstructor:
public Example(String x, int y) { this.name = x+y; called("Example.<init>", x, y); } public Example(String x, int y) { this.name = x+y; called("Example.<init>", x, y); }
public Example(int x, String y) { this.name = x+y; called("Example.<init>", x, y); } public Example(int x, String y) { this.name = x+y; called("Example.<init>", x, y); }
public Example(int x, int y) { this.name = x+""+y; called("Example.<init>", x, y); }
public Example(int x, long y) { this.name = x+""+y; called("Example.<init>", x, y); }
public Example(int x, float y) { this.name = x+""+y; called("Example.<init>", x, y); }
public Example(int x, double y) { this.name = x+""+y; called("Example.<init>", x, y); }
public Example(int x, int y, int z) { this.name = x+""+y+""+z; called("Example.<init>", x, y, z); }
public Example(int x, int y, int z, int a) { this.name = x+""+y+""+z+""+a; called("Example.<init>", x, y, z, a); }
static final Lookup EXAMPLE = MethodHandles.lookup(); // for testing findSpecial static final Lookup EXAMPLE = MethodHandles.lookup(); // for testing findSpecial
} }
static final Lookup EXAMPLE = Example.EXAMPLE; static final Lookup EXAMPLE = Example.EXAMPLE;
public static class PubExample extends Example { public static class PubExample extends Example {
public PubExample() { super("PubExample#"+nextArg()); } public PubExample() { this("PubExample"); }
protected PubExample(String prefix) { super(prefix+"#"+nextArg()); }
protected void pro_v0() { called("Pub/pro_v0", this); }
protected static void pro_s0() { called("Pub/pro_s0"); }
} }
static class SubExample extends Example { static class SubExample extends Example {
@Override public void v0() { called("Sub/v0", this); } @Override public void v0() { called("Sub/v0", this); }
@ -457,12 +422,14 @@ public class MethodHandlesTest {
@Override public String toString() { return name; } @Override public String toString() { return name; }
} }
} }
static interface SubIntExample extends IntExample { }
static final Object[][][] ACCESS_CASES = { static final Object[][][] ACCESS_CASES = {
{ { false, PUBLIC }, { false, PACKAGE }, { false, PRIVATE }, { false, EXAMPLE } }, //[0]: all false { { false, PUBLIC }, { false, SUBCLASS }, { false, PACKAGE }, { false, PRIVATE }, { false, EXAMPLE } }, //[0]: all false
{ { false, PUBLIC }, { false, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[1]: only PRIVATE { { false, PUBLIC }, { false, SUBCLASS }, { false, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[1]: only PRIVATE
{ { false, PUBLIC }, { true, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[2]: PUBLIC false { { false, PUBLIC }, { false, SUBCLASS }, { true, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[2]: PUBLIC false
{ { true, PUBLIC }, { true, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[3]: all true { { false, PUBLIC }, { true, SUBCLASS }, { true, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[3]: subclass OK
{ { true, PUBLIC }, { true, SUBCLASS }, { true, PACKAGE }, { true, PRIVATE }, { true, EXAMPLE } }, //[4]: all true
}; };
static Object[][] accessCases(Class<?> defc, String name, boolean isSpecial) { static Object[][] accessCases(Class<?> defc, String name, boolean isSpecial) {
@ -471,11 +438,13 @@ public class MethodHandlesTest {
cases = ACCESS_CASES[1]; // PRIVATE only cases = ACCESS_CASES[1]; // PRIVATE only
} else if (name.contains("pkg_") || !Modifier.isPublic(defc.getModifiers())) { } else if (name.contains("pkg_") || !Modifier.isPublic(defc.getModifiers())) {
cases = ACCESS_CASES[2]; // not PUBLIC cases = ACCESS_CASES[2]; // not PUBLIC
} else if (name.contains("pro_")) {
cases = ACCESS_CASES[3]; // PUBLIC class, protected member
} else { } else {
assertTrue(name.indexOf('_') < 0); assertTrue(name.indexOf('_') < 0 || name.contains("fin_"));
boolean pubc = Modifier.isPublic(defc.getModifiers()); boolean pubc = Modifier.isPublic(defc.getModifiers());
if (pubc) if (pubc)
cases = ACCESS_CASES[3]; // all access levels cases = ACCESS_CASES[4]; // all access levels
else else
cases = ACCESS_CASES[2]; // PACKAGE but not PUBLIC cases = ACCESS_CASES[2]; // PACKAGE but not PUBLIC
} }
@ -487,6 +456,13 @@ public class MethodHandlesTest {
return accessCases(defc, name, false); return accessCases(defc, name, false);
} }
static Lookup maybeMoveIn(Lookup lookup, Class<?> defc) {
if (lookup == PUBLIC || lookup == SUBCLASS || lookup == PACKAGE)
// external views stay external
return lookup;
return lookup.in(defc);
}
@Test @Test
public void testFindStatic() throws Throwable { public void testFindStatic() throws Throwable {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
@ -495,6 +471,8 @@ public class MethodHandlesTest {
testFindStatic(Example.class, void.class, "s0"); testFindStatic(Example.class, void.class, "s0");
testFindStatic(Example.class, void.class, "pkg_s0"); testFindStatic(Example.class, void.class, "pkg_s0");
testFindStatic(Example.class, void.class, "pri_s0"); testFindStatic(Example.class, void.class, "pri_s0");
testFindStatic(Example.class, void.class, "pro_s0");
testFindStatic(PubExample.class, void.class, "Pub/pro_s0");
testFindStatic(Example.class, Object.class, "s1", Object.class); testFindStatic(Example.class, Object.class, "s1", Object.class);
testFindStatic(Example.class, Object.class, "s2", int.class); testFindStatic(Example.class, Object.class, "s2", int.class);
@ -505,6 +483,7 @@ public class MethodHandlesTest {
testFindStatic(Example.class, Object.class, "s7", float.class, double.class); testFindStatic(Example.class, Object.class, "s7", float.class, double.class);
testFindStatic(false, PRIVATE, Example.class, void.class, "bogus"); testFindStatic(false, PRIVATE, Example.class, void.class, "bogus");
testFindStatic(false, PRIVATE, Example.class, void.class, "v0");
} }
void testFindStatic(Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable { void testFindStatic(Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
@ -517,14 +496,16 @@ public class MethodHandlesTest {
} }
void testFindStatic(boolean positive, Lookup lookup, Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable { void testFindStatic(boolean positive, Lookup lookup, Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
countTest(positive); countTest(positive);
String methodName = name.substring(1 + name.indexOf('/')); // foo/bar => foo
MethodType type = MethodType.methodType(ret, params); MethodType type = MethodType.methodType(ret, params);
MethodHandle target = null; MethodHandle target = null;
Exception noAccess = null; Exception noAccess = null;
try { try {
if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type); if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type);
target = lookup.in(defc).findStatic(defc, name, type); target = maybeMoveIn(lookup, defc).findStatic(defc, methodName, type);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
noAccess = ex; noAccess = ex;
if (verbosity >= 5) ex.printStackTrace(System.out);
if (name.contains("bogus")) if (name.contains("bogus"))
assertTrue(noAccess instanceof NoSuchMethodException); assertTrue(noAccess instanceof NoSuchMethodException);
else else
@ -537,7 +518,7 @@ public class MethodHandlesTest {
assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null); assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null);
if (!positive) return; // negative test failed as expected if (!positive) return; // negative test failed as expected
assertEquals(type, target.type()); assertEquals(type, target.type());
assertNameStringContains(target, name); assertNameStringContains(target, methodName);
Object[] args = randomArgs(params); Object[] args = randomArgs(params);
printCalled(target, name, args); printCalled(target, name, args);
target.invokeWithArguments(args); target.invokeWithArguments(args);
@ -571,7 +552,12 @@ public class MethodHandlesTest {
testFindVirtual(Example.class, Object.class, "v2", Object.class, int.class); testFindVirtual(Example.class, Object.class, "v2", Object.class, int.class);
testFindVirtual(Example.class, Object.class, "v2", int.class, Object.class); testFindVirtual(Example.class, Object.class, "v2", int.class, Object.class);
testFindVirtual(Example.class, Object.class, "v2", int.class, int.class); testFindVirtual(Example.class, Object.class, "v2", int.class, int.class);
testFindVirtual(Example.class, void.class, "pro_v0");
testFindVirtual(PubExample.class, void.class, "Pub/pro_v0");
testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "bogus"); testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "bogus");
testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "s0");
// test dispatch // test dispatch
testFindVirtual(SubExample.class, SubExample.class, void.class, "Sub/v0"); testFindVirtual(SubExample.class, SubExample.class, void.class, "Sub/v0");
testFindVirtual(SubExample.class, Example.class, void.class, "Sub/v0"); testFindVirtual(SubExample.class, Example.class, void.class, "Sub/v0");
@ -602,9 +588,10 @@ public class MethodHandlesTest {
Exception noAccess = null; Exception noAccess = null;
try { try {
if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type); if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type);
target = lookup.in(defc).findVirtual(defc, methodName, type); target = maybeMoveIn(lookup, defc).findVirtual(defc, methodName, type);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
noAccess = ex; noAccess = ex;
if (verbosity >= 5) ex.printStackTrace(System.out);
if (name.contains("bogus")) if (name.contains("bogus"))
assertTrue(noAccess instanceof NoSuchMethodException); assertTrue(noAccess instanceof NoSuchMethodException);
else else
@ -616,12 +603,20 @@ public class MethodHandlesTest {
if (positive && noAccess != null) throw noAccess; if (positive && noAccess != null) throw noAccess;
assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null); assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null);
if (!positive) return; // negative test failed as expected if (!positive) return; // negative test failed as expected
Class<?>[] paramsWithSelf = cat(array(Class[].class, (Class)defc), params); Class<?> selfc = defc;
// predict receiver type narrowing:
if (lookup == SUBCLASS &&
name.contains("pro_") &&
selfc.isAssignableFrom(lookup.lookupClass())) {
selfc = lookup.lookupClass();
if (name.startsWith("Pub/")) name = "Rem/"+name.substring(4);
}
Class<?>[] paramsWithSelf = cat(array(Class[].class, (Class)selfc), params);
MethodType typeWithSelf = MethodType.methodType(ret, paramsWithSelf); MethodType typeWithSelf = MethodType.methodType(ret, paramsWithSelf);
assertEquals(typeWithSelf, target.type()); assertEquals(typeWithSelf, target.type());
assertNameStringContains(target, methodName); assertNameStringContains(target, methodName);
Object[] argsWithSelf = randomArgs(paramsWithSelf); Object[] argsWithSelf = randomArgs(paramsWithSelf);
if (rcvc != defc) argsWithSelf[0] = randomArg(rcvc); if (selfc.isAssignableFrom(rcvc) && rcvc != selfc) argsWithSelf[0] = randomArg(rcvc);
printCalled(target, name, argsWithSelf); printCalled(target, name, argsWithSelf);
target.invokeWithArguments(argsWithSelf); target.invokeWithArguments(argsWithSelf);
assertCalled(name, argsWithSelf); assertCalled(name, argsWithSelf);
@ -635,6 +630,7 @@ public class MethodHandlesTest {
startTest("findSpecial"); startTest("findSpecial");
testFindSpecial(SubExample.class, Example.class, void.class, "v0"); testFindSpecial(SubExample.class, Example.class, void.class, "v0");
testFindSpecial(SubExample.class, Example.class, void.class, "pkg_v0"); testFindSpecial(SubExample.class, Example.class, void.class, "pkg_v0");
testFindSpecial(RemoteExample.class, PubExample.class, void.class, "Pub/pro_v0");
// Do some negative testing: // Do some negative testing:
testFindSpecial(false, EXAMPLE, SubExample.class, Example.class, void.class, "bogus"); testFindSpecial(false, EXAMPLE, SubExample.class, Example.class, void.class, "bogus");
testFindSpecial(false, PRIVATE, SubExample.class, Example.class, void.class, "bogus"); testFindSpecial(false, PRIVATE, SubExample.class, Example.class, void.class, "bogus");
@ -647,23 +643,34 @@ public class MethodHandlesTest {
void testFindSpecial(Class<?> specialCaller, void testFindSpecial(Class<?> specialCaller,
Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable { Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
if (specialCaller == RemoteExample.class) {
testFindSpecial(false, EXAMPLE, specialCaller, defc, ret, name, params);
testFindSpecial(false, PRIVATE, specialCaller, defc, ret, name, params);
testFindSpecial(false, PACKAGE, specialCaller, defc, ret, name, params);
testFindSpecial(true, SUBCLASS, specialCaller, defc, ret, name, params);
testFindSpecial(false, PUBLIC, specialCaller, defc, ret, name, params);
return;
}
testFindSpecial(true, EXAMPLE, specialCaller, defc, ret, name, params); testFindSpecial(true, EXAMPLE, specialCaller, defc, ret, name, params);
testFindSpecial(true, PRIVATE, specialCaller, defc, ret, name, params); testFindSpecial(true, PRIVATE, specialCaller, defc, ret, name, params);
testFindSpecial(false, PACKAGE, specialCaller, defc, ret, name, params); testFindSpecial(false, PACKAGE, specialCaller, defc, ret, name, params);
testFindSpecial(false, SUBCLASS, specialCaller, defc, ret, name, params);
testFindSpecial(false, PUBLIC, specialCaller, defc, ret, name, params); testFindSpecial(false, PUBLIC, specialCaller, defc, ret, name, params);
} }
void testFindSpecial(boolean positive, Lookup lookup, Class<?> specialCaller, void testFindSpecial(boolean positive, Lookup lookup, Class<?> specialCaller,
Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable { Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
countTest(positive); countTest(positive);
String methodName = name.substring(1 + name.indexOf('/')); // foo/bar => foo
MethodType type = MethodType.methodType(ret, params); MethodType type = MethodType.methodType(ret, params);
MethodHandle target = null; MethodHandle target = null;
Exception noAccess = null; Exception noAccess = null;
try { try {
if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type); if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type);
if (verbosity >= 5) System.out.println(" lookup => "+lookup.in(specialCaller)); if (verbosity >= 5) System.out.println(" lookup => "+maybeMoveIn(lookup, specialCaller));
target = lookup.in(specialCaller).findSpecial(defc, name, type, specialCaller); target = maybeMoveIn(lookup, specialCaller).findSpecial(defc, methodName, type, specialCaller);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
noAccess = ex; noAccess = ex;
if (verbosity >= 5) ex.printStackTrace(System.out);
if (name.contains("bogus")) if (name.contains("bogus"))
assertTrue(noAccess instanceof NoSuchMethodException); assertTrue(noAccess instanceof NoSuchMethodException);
else else
@ -680,7 +687,7 @@ public class MethodHandlesTest {
assertEquals(type, target.type().dropParameterTypes(0,1)); assertEquals(type, target.type().dropParameterTypes(0,1));
Class<?>[] paramsWithSelf = cat(array(Class[].class, (Class)specialCaller), params); Class<?>[] paramsWithSelf = cat(array(Class[].class, (Class)specialCaller), params);
MethodType typeWithSelf = MethodType.methodType(ret, paramsWithSelf); MethodType typeWithSelf = MethodType.methodType(ret, paramsWithSelf);
assertNameStringContains(target, name); assertNameStringContains(target, methodName);
Object[] args = randomArgs(paramsWithSelf); Object[] args = randomArgs(paramsWithSelf);
printCalled(target, name, args); printCalled(target, name, args);
target.invokeWithArguments(args); target.invokeWithArguments(args);
@ -693,7 +700,13 @@ public class MethodHandlesTest {
startTest("findConstructor"); startTest("findConstructor");
testFindConstructor(true, EXAMPLE, Example.class); testFindConstructor(true, EXAMPLE, Example.class);
testFindConstructor(true, EXAMPLE, Example.class, int.class); testFindConstructor(true, EXAMPLE, Example.class, int.class);
testFindConstructor(true, EXAMPLE, Example.class, int.class, int.class);
testFindConstructor(true, EXAMPLE, Example.class, int.class, long.class);
testFindConstructor(true, EXAMPLE, Example.class, int.class, float.class);
testFindConstructor(true, EXAMPLE, Example.class, int.class, double.class);
testFindConstructor(true, EXAMPLE, Example.class, String.class); testFindConstructor(true, EXAMPLE, Example.class, String.class);
testFindConstructor(true, EXAMPLE, Example.class, int.class, int.class, int.class);
testFindConstructor(true, EXAMPLE, Example.class, int.class, int.class, int.class, int.class);
} }
void testFindConstructor(boolean positive, Lookup lookup, void testFindConstructor(boolean positive, Lookup lookup,
Class<?> defc, Class<?>... params) throws Throwable { Class<?> defc, Class<?>... params) throws Throwable {
@ -757,9 +770,10 @@ public class MethodHandlesTest {
Exception noAccess = null; Exception noAccess = null;
try { try {
if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type); if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type);
target = lookup.in(defc).bind(receiver, methodName, type); target = maybeMoveIn(lookup, defc).bind(receiver, methodName, type);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
noAccess = ex; noAccess = ex;
if (verbosity >= 5) ex.printStackTrace(System.out);
if (name.contains("bogus")) if (name.contains("bogus"))
assertTrue(noAccess instanceof NoSuchMethodException); assertTrue(noAccess instanceof NoSuchMethodException);
else else
@ -786,6 +800,7 @@ public class MethodHandlesTest {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("unreflect"); startTest("unreflect");
testUnreflect(Example.class, true, void.class, "s0"); testUnreflect(Example.class, true, void.class, "s0");
testUnreflect(Example.class, true, void.class, "pro_s0");
testUnreflect(Example.class, true, void.class, "pkg_s0"); testUnreflect(Example.class, true, void.class, "pkg_s0");
testUnreflect(Example.class, true, void.class, "pri_s0"); testUnreflect(Example.class, true, void.class, "pri_s0");
@ -804,6 +819,9 @@ public class MethodHandlesTest {
testUnreflect(Example.class, false, Object.class, "v2", Object.class, int.class); testUnreflect(Example.class, false, Object.class, "v2", Object.class, int.class);
testUnreflect(Example.class, false, Object.class, "v2", int.class, Object.class); testUnreflect(Example.class, false, Object.class, "v2", int.class, Object.class);
testUnreflect(Example.class, false, Object.class, "v2", int.class, int.class); testUnreflect(Example.class, false, Object.class, "v2", int.class, int.class);
// Test a public final member in another package:
testUnreflect(RemoteExample.class, false, void.class, "Rem/fin_v0");
} }
void testUnreflect(Class<?> defc, boolean isStatic, Class<?> ret, String name, Class<?>... params) throws Throwable { void testUnreflect(Class<?> defc, boolean isStatic, Class<?> ret, String name, Class<?>... params) throws Throwable {
@ -820,8 +838,9 @@ public class MethodHandlesTest {
boolean positive, Lookup lookup, boolean positive, Lookup lookup,
Class<?> defc, Class<?> rcvc, Class<?> ret, String name, Class<?>... params) throws Throwable { Class<?> defc, Class<?> rcvc, Class<?> ret, String name, Class<?>... params) throws Throwable {
countTest(positive); countTest(positive);
String methodName = name.substring(1 + name.indexOf('/')); // foo/bar => foo
MethodType type = MethodType.methodType(ret, params); MethodType type = MethodType.methodType(ret, params);
Method rmethod = defc.getDeclaredMethod(name, params); Method rmethod = defc.getDeclaredMethod(methodName, params);
MethodHandle target = null; MethodHandle target = null;
Exception noAccess = null; Exception noAccess = null;
boolean isStatic = (rcvc == null); boolean isStatic = (rcvc == null);
@ -829,11 +848,12 @@ public class MethodHandlesTest {
try { try {
if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type); if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type);
if (isSpecial) if (isSpecial)
target = lookup.in(specialCaller).unreflectSpecial(rmethod, specialCaller); target = maybeMoveIn(lookup, specialCaller).unreflectSpecial(rmethod, specialCaller);
else else
target = lookup.in(defc).unreflect(rmethod); target = maybeMoveIn(lookup, defc).unreflect(rmethod);
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
noAccess = ex; noAccess = ex;
if (verbosity >= 5) ex.printStackTrace(System.out);
if (name.contains("bogus")) if (name.contains("bogus"))
assertTrue(noAccess instanceof NoSuchMethodException); assertTrue(noAccess instanceof NoSuchMethodException);
else else
@ -960,7 +980,7 @@ public class MethodHandlesTest {
} }
} }
static final int TEST_UNREFLECT = 1, TEST_FIND_FIELD = 2, TEST_FIND_STATIC = 3, TEST_SETTER = 0x10; static final int TEST_UNREFLECT = 1, TEST_FIND_FIELD = 2, TEST_FIND_STATIC = 3, TEST_SETTER = 0x10, TEST_BOUND = 0x20, TEST_NPE = 0x40;
static boolean testModeMatches(int testMode, boolean isStatic) { static boolean testModeMatches(int testMode, boolean isStatic) {
switch (testMode) { switch (testMode) {
case TEST_FIND_STATIC: return isStatic; case TEST_FIND_STATIC: return isStatic;
@ -972,16 +992,20 @@ public class MethodHandlesTest {
@Test @Test
public void testUnreflectGetter() throws Throwable { public void testUnreflectGetter() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("unreflectGetter"); startTest("unreflectGetter");
testGetter(TEST_UNREFLECT); testGetter(TEST_UNREFLECT);
} }
@Test @Test
public void testFindGetter() throws Throwable { public void testFindGetter() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("findGetter"); startTest("findGetter");
testGetter(TEST_FIND_FIELD); testGetter(TEST_FIND_FIELD);
testGetter(TEST_FIND_FIELD | TEST_BOUND);
} }
@Test @Test
public void testFindStaticGetter() throws Throwable { public void testFindStaticGetter() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("findStaticGetter"); startTest("findStaticGetter");
testGetter(TEST_FIND_STATIC); testGetter(TEST_FIND_STATIC);
} }
@ -990,6 +1014,8 @@ public class MethodHandlesTest {
for (Object[] c : HasFields.CASES) { for (Object[] c : HasFields.CASES) {
boolean positive = (c[1] != Error.class); boolean positive = (c[1] != Error.class);
testGetter(positive, lookup, c[0], c[1], testMode); testGetter(positive, lookup, c[0], c[1], testMode);
if (positive)
testGetter(positive, lookup, c[0], c[1], testMode | TEST_NPE);
} }
testGetter(true, lookup, testGetter(true, lookup,
new Object[]{ true, System.class, "out", java.io.PrintStream.class }, new Object[]{ true, System.class, "out", java.io.PrintStream.class },
@ -1005,12 +1031,15 @@ public class MethodHandlesTest {
testAccessor(positive, lookup, fieldRef, value, testMode); testAccessor(positive, lookup, fieldRef, value, testMode);
} }
public void testAccessor(boolean positive, MethodHandles.Lookup lookup, public void testAccessor(boolean positive0, MethodHandles.Lookup lookup,
Object fieldRef, Object value, int testMode0) throws Throwable { Object fieldRef, Object value, int testMode0) throws Throwable {
if (verbosity >= 4) if (verbosity >= 4)
System.out.println("testAccessor"+Arrays.asList(positive, lookup, fieldRef, value, testMode0)); System.out.println("testAccessor"+Arrays.deepToString(new Object[]{positive0, lookup, fieldRef, value, testMode0}));
boolean isGetter = ((testMode0 & TEST_SETTER) == 0); boolean isGetter = ((testMode0 & TEST_SETTER) == 0);
int testMode = testMode0 & ~TEST_SETTER; boolean doBound = ((testMode0 & TEST_BOUND) != 0);
boolean testNPE = ((testMode0 & TEST_NPE) != 0);
int testMode = testMode0 & ~(TEST_SETTER | TEST_BOUND | TEST_NPE);
boolean positive = positive0 && !testNPE;
boolean isStatic; boolean isStatic;
Class<?> fclass; Class<?> fclass;
String fname; String fname;
@ -1035,6 +1064,7 @@ public class MethodHandlesTest {
} }
if (!testModeMatches(testMode, isStatic)) return; if (!testModeMatches(testMode, isStatic)) return;
if (f == null && testMode == TEST_UNREFLECT) return; if (f == null && testMode == TEST_UNREFLECT) return;
if (testNPE && isStatic) return;
countTest(positive); countTest(positive);
MethodType expType; MethodType expType;
if (isGetter) if (isGetter)
@ -1045,7 +1075,7 @@ public class MethodHandlesTest {
Exception noAccess = null; Exception noAccess = null;
MethodHandle mh; MethodHandle mh;
try { try {
switch (testMode0) { switch (testMode0 & ~(TEST_BOUND | TEST_NPE)) {
case TEST_UNREFLECT: mh = lookup.unreflectGetter(f); break; case TEST_UNREFLECT: mh = lookup.unreflectGetter(f); break;
case TEST_FIND_FIELD: mh = lookup.findGetter(fclass, fname, ftype); break; case TEST_FIND_FIELD: mh = lookup.findGetter(fclass, fname, ftype); break;
case TEST_FIND_STATIC: mh = lookup.findStaticGetter(fclass, fname, ftype); break; case TEST_FIND_STATIC: mh = lookup.findStaticGetter(fclass, fname, ftype); break;
@ -1061,6 +1091,7 @@ public class MethodHandlesTest {
} catch (ReflectiveOperationException ex) { } catch (ReflectiveOperationException ex) {
mh = null; mh = null;
noAccess = ex; noAccess = ex;
if (verbosity >= 5) ex.printStackTrace(System.out);
if (fname.contains("bogus")) if (fname.contains("bogus"))
assertTrue(noAccess instanceof NoSuchFieldException); assertTrue(noAccess instanceof NoSuchFieldException);
else else
@ -1070,15 +1101,19 @@ public class MethodHandlesTest {
System.out.println("find"+(isStatic?"Static":"")+(isGetter?"Getter":"Setter")+" "+fclass.getName()+"."+fname+"/"+ftype System.out.println("find"+(isStatic?"Static":"")+(isGetter?"Getter":"Setter")+" "+fclass.getName()+"."+fname+"/"+ftype
+" => "+mh +" => "+mh
+(noAccess == null ? "" : " !! "+noAccess)); +(noAccess == null ? "" : " !! "+noAccess));
if (positive && noAccess != null) throw new RuntimeException(noAccess); if (positive && !testNPE && noAccess != null) throw new RuntimeException(noAccess);
assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, mh != null); assertEquals(positive0 ? "positive test" : "negative test erroneously passed", positive0, mh != null);
if (!positive) return; // negative test failed as expected if (!positive && !testNPE) return; // negative access test failed as expected
assertEquals((isStatic ? 0 : 1)+(isGetter ? 0 : 1), mh.type().parameterCount()); assertEquals((isStatic ? 0 : 1)+(isGetter ? 0 : 1), mh.type().parameterCount());
assertSame(mh.type(), expType); assertSame(mh.type(), expType);
assertNameStringContains(mh, fname); //assertNameStringContains(mh, fname); // This does not hold anymore with LFs
HasFields fields = new HasFields(); HasFields fields = new HasFields();
HasFields fieldsForMH = fields;
if (testNPE) fieldsForMH = null; // perturb MH argument to elicit expected error
if (doBound)
mh = mh.bindTo(fieldsForMH);
Object sawValue; Object sawValue;
Class<?> vtype = ftype; Class<?> vtype = ftype;
if (ftype != int.class) vtype = Object.class; if (ftype != int.class) vtype = Object.class;
@ -1094,19 +1129,28 @@ public class MethodHandlesTest {
if (f != null && f.getDeclaringClass() == HasFields.class) { if (f != null && f.getDeclaringClass() == HasFields.class) {
assertEquals(f.get(fields), value); // clean to start with assertEquals(f.get(fields), value); // clean to start with
} }
Throwable caughtEx = null;
if (isGetter) { if (isGetter) {
Object expValue = value; Object expValue = value;
for (int i = 0; i <= 1; i++) { for (int i = 0; i <= 1; i++) {
if (isStatic) { sawValue = null; // make DA rules happy under try/catch
try {
if (isStatic || doBound) {
if (ftype == int.class) if (ftype == int.class)
sawValue = (int) mh.invokeExact(); // do these exactly sawValue = (int) mh.invokeExact(); // do these exactly
else else
sawValue = mh.invokeExact(); sawValue = mh.invokeExact();
} else { } else {
if (ftype == int.class) if (ftype == int.class)
sawValue = (int) mh.invokeExact((Object) fields); sawValue = (int) mh.invokeExact((Object) fieldsForMH);
else else
sawValue = mh.invokeExact((Object) fields); sawValue = mh.invokeExact((Object) fieldsForMH);
}
} catch (RuntimeException ex) {
if (ex instanceof NullPointerException && testNPE) {
caughtEx = ex;
break;
}
} }
assertEquals(sawValue, expValue); assertEquals(sawValue, expValue);
if (f != null && f.getDeclaringClass() == HasFields.class if (f != null && f.getDeclaringClass() == HasFields.class
@ -1121,16 +1165,23 @@ public class MethodHandlesTest {
} else { } else {
for (int i = 0; i <= 1; i++) { for (int i = 0; i <= 1; i++) {
Object putValue = randomArg(ftype); Object putValue = randomArg(ftype);
if (isStatic) { try {
if (isStatic || doBound) {
if (ftype == int.class) if (ftype == int.class)
mh.invokeExact((int)putValue); // do these exactly mh.invokeExact((int)putValue); // do these exactly
else else
mh.invokeExact(putValue); mh.invokeExact(putValue);
} else { } else {
if (ftype == int.class) if (ftype == int.class)
mh.invokeExact((Object) fields, (int)putValue); mh.invokeExact((Object) fieldsForMH, (int)putValue);
else else
mh.invokeExact((Object) fields, putValue); mh.invokeExact((Object) fieldsForMH, putValue);
}
} catch (RuntimeException ex) {
if (ex instanceof NullPointerException && testNPE) {
caughtEx = ex;
break;
}
} }
if (f != null && f.getDeclaringClass() == HasFields.class) { if (f != null && f.getDeclaringClass() == HasFields.class) {
assertEquals(f.get(fields), putValue); assertEquals(f.get(fields), putValue);
@ -1140,21 +1191,33 @@ public class MethodHandlesTest {
if (f != null && f.getDeclaringClass() == HasFields.class) { if (f != null && f.getDeclaringClass() == HasFields.class) {
f.set(fields, value); // put it back f.set(fields, value); // put it back
} }
if (testNPE) {
if (caughtEx == null || !(caughtEx instanceof NullPointerException))
throw new RuntimeException("failed to catch NPE exception"+(caughtEx == null ? " (caughtEx=null)" : ""), caughtEx);
caughtEx = null; // nullify expected exception
}
if (caughtEx != null) {
throw new RuntimeException("unexpected exception", caughtEx);
}
} }
@Test @Test
public void testUnreflectSetter() throws Throwable { public void testUnreflectSetter() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("unreflectSetter"); startTest("unreflectSetter");
testSetter(TEST_UNREFLECT); testSetter(TEST_UNREFLECT);
} }
@Test @Test
public void testFindSetter() throws Throwable { public void testFindSetter() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("findSetter"); startTest("findSetter");
testSetter(TEST_FIND_FIELD); testSetter(TEST_FIND_FIELD);
testSetter(TEST_FIND_FIELD | TEST_BOUND);
} }
@Test @Test
public void testFindStaticSetter() throws Throwable { public void testFindStaticSetter() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("findStaticSetter"); startTest("findStaticSetter");
testSetter(TEST_FIND_STATIC); testSetter(TEST_FIND_STATIC);
} }
@ -1164,6 +1227,8 @@ public class MethodHandlesTest {
for (Object[] c : HasFields.CASES) { for (Object[] c : HasFields.CASES) {
boolean positive = (c[1] != Error.class); boolean positive = (c[1] != Error.class);
testSetter(positive, lookup, c[0], c[1], testMode); testSetter(positive, lookup, c[0], c[1], testMode);
if (positive)
testSetter(positive, lookup, c[0], c[1], testMode | TEST_NPE);
} }
for (int isStaticN = 0; isStaticN <= 1; isStaticN++) { for (int isStaticN = 0; isStaticN <= 1; isStaticN++) {
testSetter(false, lookup, testSetter(false, lookup,
@ -1178,34 +1243,84 @@ public class MethodHandlesTest {
@Test @Test
public void testArrayElementGetter() throws Throwable { public void testArrayElementGetter() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("arrayElementGetter"); startTest("arrayElementGetter");
testArrayElementGetterSetter(false); testArrayElementGetterSetter(false);
} }
@Test @Test
public void testArrayElementSetter() throws Throwable { public void testArrayElementSetter() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("arrayElementSetter"); startTest("arrayElementSetter");
testArrayElementGetterSetter(true); testArrayElementGetterSetter(true);
} }
private static final int TEST_ARRAY_NONE = 0, TEST_ARRAY_NPE = 1, TEST_ARRAY_OOB = 2, TEST_ARRAY_ASE = 3;
public void testArrayElementGetterSetter(boolean testSetter) throws Throwable { public void testArrayElementGetterSetter(boolean testSetter) throws Throwable {
testArrayElementGetterSetter(new Object[10], testSetter); testArrayElementGetterSetter(testSetter, TEST_ARRAY_NONE);
testArrayElementGetterSetter(new String[10], testSetter);
testArrayElementGetterSetter(new boolean[10], testSetter);
testArrayElementGetterSetter(new byte[10], testSetter);
testArrayElementGetterSetter(new char[10], testSetter);
testArrayElementGetterSetter(new short[10], testSetter);
testArrayElementGetterSetter(new int[10], testSetter);
testArrayElementGetterSetter(new float[10], testSetter);
testArrayElementGetterSetter(new long[10], testSetter);
testArrayElementGetterSetter(new double[10], testSetter);
} }
public void testArrayElementGetterSetter(Object array, boolean testSetter) throws Throwable { @Test
countTest(true); public void testArrayElementErrors() throws Throwable {
if (verbosity > 2) System.out.println("array type = "+array.getClass().getComponentType().getName()+"["+Array.getLength(array)+"]"); if (CAN_SKIP_WORKING) return;
startTest("arrayElementErrors");
testArrayElementGetterSetter(false, TEST_ARRAY_NPE);
testArrayElementGetterSetter(true, TEST_ARRAY_NPE);
testArrayElementGetterSetter(false, TEST_ARRAY_OOB);
testArrayElementGetterSetter(true, TEST_ARRAY_OOB);
testArrayElementGetterSetter(new Object[10], true, TEST_ARRAY_ASE);
testArrayElementGetterSetter(new Example[10], true, TEST_ARRAY_ASE);
testArrayElementGetterSetter(new IntExample[10], true, TEST_ARRAY_ASE);
}
public void testArrayElementGetterSetter(boolean testSetter, int negTest) throws Throwable {
testArrayElementGetterSetter(new String[10], testSetter, negTest);
testArrayElementGetterSetter(new Iterable<?>[10], testSetter, negTest);
testArrayElementGetterSetter(new Example[10], testSetter, negTest);
testArrayElementGetterSetter(new IntExample[10], testSetter, negTest);
testArrayElementGetterSetter(new Object[10], testSetter, negTest);
testArrayElementGetterSetter(new boolean[10], testSetter, negTest);
testArrayElementGetterSetter(new byte[10], testSetter, negTest);
testArrayElementGetterSetter(new char[10], testSetter, negTest);
testArrayElementGetterSetter(new short[10], testSetter, negTest);
testArrayElementGetterSetter(new int[10], testSetter, negTest);
testArrayElementGetterSetter(new float[10], testSetter, negTest);
testArrayElementGetterSetter(new long[10], testSetter, negTest);
testArrayElementGetterSetter(new double[10], testSetter, negTest);
}
public void testArrayElementGetterSetter(Object array, boolean testSetter, int negTest) throws Throwable {
boolean positive = (negTest == TEST_ARRAY_NONE);
int length = java.lang.reflect.Array.getLength(array);
Class<?> arrayType = array.getClass(); Class<?> arrayType = array.getClass();
Class<?> elemType = arrayType.getComponentType(); Class<?> elemType = arrayType.getComponentType();
Object arrayToMH = array;
// this stanza allows negative tests to make argument perturbations:
switch (negTest) {
case TEST_ARRAY_NPE:
arrayToMH = null;
break;
case TEST_ARRAY_OOB:
assert(length > 0);
arrayToMH = java.lang.reflect.Array.newInstance(elemType, 0);
break;
case TEST_ARRAY_ASE:
assert(testSetter && !elemType.isPrimitive());
if (elemType == Object.class)
arrayToMH = new StringBuffer[length]; // very random subclass of Object!
else if (elemType == Example.class)
arrayToMH = new SubExample[length];
else if (elemType == IntExample.class)
arrayToMH = new SubIntExample[length];
else
return; // can't make an ArrayStoreException test
assert(arrayType.isInstance(arrayToMH))
: Arrays.asList(arrayType, arrayToMH.getClass(), testSetter, negTest);
break;
}
countTest(positive);
if (verbosity > 2) System.out.println("array type = "+array.getClass().getComponentType().getName()+"["+length+"]"+(positive ? "" : " negative test #"+negTest+" using "+Arrays.deepToString(new Object[]{arrayToMH})));
MethodType expType = !testSetter MethodType expType = !testSetter
? MethodType.methodType(elemType, arrayType, int.class) ? MethodType.methodType(elemType, arrayType, int.class)
: MethodType.methodType(void.class, arrayType, int.class, elemType); : MethodType.methodType(void.class, arrayType, int.class, elemType);
@ -1214,25 +1329,29 @@ public class MethodHandlesTest {
: MethodHandles.arrayElementSetter(arrayType); : MethodHandles.arrayElementSetter(arrayType);
assertSame(mh.type(), expType); assertSame(mh.type(), expType);
if (elemType != int.class && elemType != boolean.class) { if (elemType != int.class && elemType != boolean.class) {
// FIXME: change Integer.class and (Integer) below to int.class and (int) below. MethodType gtype = mh.type().generic().changeParameterType(1, int.class);
MethodType gtype = mh.type().generic().changeParameterType(1, Integer.class);
if (testSetter) gtype = gtype.changeReturnType(void.class); if (testSetter) gtype = gtype.changeReturnType(void.class);
mh = mh.asType(gtype); mh = mh.asType(gtype);
} }
Object sawValue, expValue; Object sawValue, expValue;
List<Object> model = array2list(array); List<Object> model = array2list(array);
int length = Array.getLength(array); Throwable caughtEx = null;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
// update array element // update array element
Object random = randomArg(elemType); Object random = randomArg(elemType);
model.set(i, random); model.set(i, random);
if (testSetter) { if (testSetter) {
try {
if (elemType == int.class) if (elemType == int.class)
mh.invokeExact((int[]) array, i, (int)random); mh.invokeExact((int[]) arrayToMH, i, (int)random);
else if (elemType == boolean.class) else if (elemType == boolean.class)
mh.invokeExact((boolean[]) array, i, (boolean)random); mh.invokeExact((boolean[]) arrayToMH, i, (boolean)random);
else else
mh.invokeExact(array, (Integer)i, random); mh.invokeExact(arrayToMH, i, random);
} catch (RuntimeException ex) {
caughtEx = ex;
break;
}
assertEquals(model, array2list(array)); assertEquals(model, array2list(array));
} else { } else {
Array.set(array, i, random); Array.set(array, i, random);
@ -1247,16 +1366,39 @@ public class MethodHandlesTest {
sawValue = Array.get(array, i); sawValue = Array.get(array, i);
if (!testSetter) { if (!testSetter) {
expValue = sawValue; expValue = sawValue;
try {
if (elemType == int.class) if (elemType == int.class)
sawValue = (int) mh.invokeExact((int[]) array, i); sawValue = (int) mh.invokeExact((int[]) arrayToMH, i);
else if (elemType == boolean.class) else if (elemType == boolean.class)
sawValue = (boolean) mh.invokeExact((boolean[]) array, i); sawValue = (boolean) mh.invokeExact((boolean[]) arrayToMH, i);
else else
sawValue = mh.invokeExact(array, (Integer)i); sawValue = mh.invokeExact(arrayToMH, i);
} catch (RuntimeException ex) {
caughtEx = ex;
break;
}
assertEquals(sawValue, expValue); assertEquals(sawValue, expValue);
assertEquals(model, array2list(array)); assertEquals(model, array2list(array));
} }
} }
if (!positive) {
if (caughtEx == null)
throw new RuntimeException("failed to catch exception for negTest="+negTest);
// test the kind of exception
Class<?> reqType = null;
switch (negTest) {
case TEST_ARRAY_ASE: reqType = ArrayStoreException.class; break;
case TEST_ARRAY_OOB: reqType = ArrayIndexOutOfBoundsException.class; break;
case TEST_ARRAY_NPE: reqType = NullPointerException.class; break;
default: assert(false);
}
if (reqType.isInstance(caughtEx)) {
caughtEx = null; // nullify expected exception
}
}
if (caughtEx != null) {
throw new RuntimeException("unexpected exception", caughtEx);
}
} }
List<Object> array2list(Object array) { List<Object> array2list(Object array) {
@ -1363,6 +1505,8 @@ public class MethodHandlesTest {
@Test @Test
public void testVarargsCollector() throws Throwable { public void testVarargsCollector() throws Throwable {
if (CAN_SKIP_WORKING) return;
startTest("varargsCollector");
MethodHandle vac0 = PRIVATE.findStatic(MethodHandlesTest.class, "called", MethodHandle vac0 = PRIVATE.findStatic(MethodHandlesTest.class, "called",
MethodType.methodType(Object.class, String.class, Object[].class)); MethodType.methodType(Object.class, String.class, Object[].class));
vac0 = vac0.bindTo("vac"); vac0 = vac0.bindTo("vac");
@ -1375,7 +1519,7 @@ public class MethodHandlesTest {
} }
} }
@Test @Test // SLOW
public void testPermuteArguments() throws Throwable { public void testPermuteArguments() throws Throwable {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("permuteArguments"); startTest("permuteArguments");
@ -1514,7 +1658,7 @@ public class MethodHandlesTest {
} }
@Test @Test // SLOW
public void testSpreadArguments() throws Throwable { public void testSpreadArguments() throws Throwable {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("spreadArguments"); startTest("spreadArguments");
@ -1522,7 +1666,7 @@ public class MethodHandlesTest {
if (verbosity >= 3) if (verbosity >= 3)
System.out.println("spreadArguments "+argType); System.out.println("spreadArguments "+argType);
for (int nargs = 0; nargs < 50; nargs++) { for (int nargs = 0; nargs < 50; nargs++) {
if (CAN_TEST_LIGHTLY && nargs > 7) break; if (CAN_TEST_LIGHTLY && nargs > 11) break;
for (int pos = 0; pos <= nargs; pos++) { for (int pos = 0; pos <= nargs; pos++) {
if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2) continue; if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2) continue;
if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3) if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3)
@ -1608,7 +1752,7 @@ public class MethodHandlesTest {
} }
} }
@Test @Test // SLOW
public void testCollectArguments() throws Throwable { public void testCollectArguments() throws Throwable {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("collectArguments"); startTest("collectArguments");
@ -1616,7 +1760,7 @@ public class MethodHandlesTest {
if (verbosity >= 3) if (verbosity >= 3)
System.out.println("collectArguments "+argType); System.out.println("collectArguments "+argType);
for (int nargs = 0; nargs < 50; nargs++) { for (int nargs = 0; nargs < 50; nargs++) {
if (CAN_TEST_LIGHTLY && nargs > 7) break; if (CAN_TEST_LIGHTLY && nargs > 11) break;
for (int pos = 0; pos <= nargs; pos++) { for (int pos = 0; pos <= nargs; pos++) {
if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2) continue; if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2) continue;
if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3) if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3)
@ -1650,12 +1794,12 @@ public class MethodHandlesTest {
assertArrayEquals(collectedArgs, returnValue); assertArrayEquals(collectedArgs, returnValue);
} }
@Test @Test // SLOW
public void testInsertArguments() throws Throwable { public void testInsertArguments() throws Throwable {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("insertArguments"); startTest("insertArguments");
for (int nargs = 0; nargs < 50; nargs++) { for (int nargs = 0; nargs < 50; nargs++) {
if (CAN_TEST_LIGHTLY && nargs > 7) break; if (CAN_TEST_LIGHTLY && nargs > 11) break;
for (int ins = 0; ins <= nargs; ins++) { for (int ins = 0; ins <= nargs; ins++) {
if (nargs > 10 && ins > 4 && ins < nargs-4 && ins % 10 != 3) if (nargs > 10 && ins > 4 && ins < nargs-4 && ins % 10 != 3)
continue; continue;
@ -1677,7 +1821,7 @@ public class MethodHandlesTest {
List<Object> argsToPass = new ArrayList<>(resList); List<Object> argsToPass = new ArrayList<>(resList);
List<Object> argsToInsert = argsToPass.subList(pos, pos + ins); List<Object> argsToInsert = argsToPass.subList(pos, pos + ins);
if (verbosity >= 3) if (verbosity >= 3)
System.out.println("insert: "+argsToInsert+" into "+target); System.out.println("insert: "+argsToInsert+" @"+pos+" into "+target);
@SuppressWarnings("cast") // cast to spread Object... is helpful @SuppressWarnings("cast") // cast to spread Object... is helpful
MethodHandle target2 = MethodHandles.insertArguments(target, pos, MethodHandle target2 = MethodHandles.insertArguments(target, pos,
(Object[]/*...*/) argsToInsert.toArray()); (Object[]/*...*/) argsToInsert.toArray());
@ -1840,7 +1984,7 @@ public class MethodHandlesTest {
assertEquals(resList, res2List); assertEquals(resList, res2List);
} }
@Test @Test // SLOW
public void testInvokers() throws Throwable { public void testInvokers() throws Throwable {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("exactInvoker, genericInvoker, varargsInvoker, dynamicInvoker"); startTest("exactInvoker, genericInvoker, varargsInvoker, dynamicInvoker");
@ -2109,7 +2253,7 @@ public class MethodHandlesTest {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("catchException"); startTest("catchException");
for (int nargs = 0; nargs < 40; nargs++) { for (int nargs = 0; nargs < 40; nargs++) {
if (CAN_TEST_LIGHTLY && nargs > 7) break; if (CAN_TEST_LIGHTLY && nargs > 11) break;
for (int throwMode = 0; throwMode < THROW_MODE_LIMIT; throwMode++) { for (int throwMode = 0; throwMode < THROW_MODE_LIMIT; throwMode++) {
testCatchException(int.class, new ClassCastException("testing"), throwMode, nargs); testCatchException(int.class, new ClassCastException("testing"), throwMode, nargs);
if (CAN_TEST_LIGHTLY && nargs > 3) continue; if (CAN_TEST_LIGHTLY && nargs > 3) continue;
@ -2237,8 +2381,10 @@ public class MethodHandlesTest {
assertSame(thrown, caught); assertSame(thrown, caught);
} }
@Test //@Test
public void testInterfaceCast() throws Throwable { public void testInterfaceCast() throws Throwable {
//if (CAN_SKIP_WORKING) return;
startTest("interfaceCast");
for (Class<?> ctype : new Class<?>[]{ Object.class, String.class, CharSequence.class, Number.class, Iterable.class}) { for (Class<?> ctype : new Class<?>[]{ Object.class, String.class, CharSequence.class, Number.class, Iterable.class}) {
testInterfaceCast(ctype, false, false); testInterfaceCast(ctype, false, false);
testInterfaceCast(ctype, true, false); testInterfaceCast(ctype, true, false);
@ -2279,11 +2425,12 @@ public class MethodHandlesTest {
} }
} }
@Test @Test // SLOW
public void testCastFailure() throws Throwable { public void testCastFailure() throws Throwable {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("testCastFailure"); startTest("testCastFailure");
testCastFailure("cast/argument", 11000); testCastFailure("cast/argument", 11000);
if (CAN_TEST_LIGHTLY) return;
testCastFailure("unbox/argument", 11000); testCastFailure("unbox/argument", 11000);
testCastFailure("cast/return", 11000); testCastFailure("cast/return", 11000);
testCastFailure("unbox/return", 11000); testCastFailure("unbox/return", 11000);
@ -2380,7 +2527,7 @@ public class MethodHandlesTest {
if (verbosity > 2) if (verbosity > 2)
System.out.println("caught "+ex); System.out.println("caught "+ex);
if (verbosity > 3) if (verbosity > 3)
ex.printStackTrace(); ex.printStackTrace(System.out);
assertTrue(true); // all is well assertTrue(true); // all is well
} }
} }
@ -2444,7 +2591,7 @@ public class MethodHandlesTest {
@Test @Test
public void testAsInterfaceInstance() throws Throwable { public void testAsInterfaceInstance() throws Throwable {
if (CAN_SKIP_WORKING) return; if (CAN_SKIP_WORKING) return;
startTest("testAsInterfaceInstance"); startTest("asInterfaceInstance");
Lookup lookup = MethodHandles.lookup(); Lookup lookup = MethodHandles.lookup();
// test typical case: Runnable.run // test typical case: Runnable.run
{ {
@ -2550,7 +2697,7 @@ public class MethodHandlesTest {
} else { } else {
assertNotSame("must pass undeclared checked exception with wrapping", ex, ex1); assertNotSame("must pass undeclared checked exception with wrapping", ex, ex1);
if (!(ex1 instanceof UndeclaredThrowableException) || ex1.getCause() != ex) { if (!(ex1 instanceof UndeclaredThrowableException) || ex1.getCause() != ex) {
ex1.printStackTrace(); ex1.printStackTrace(System.out);
} }
assertSame(ex, ex1.getCause()); assertSame(ex, ex1.getCause());
UndeclaredThrowableException utex = (UndeclaredThrowableException) ex1; UndeclaredThrowableException utex = (UndeclaredThrowableException) ex1;
@ -2630,6 +2777,8 @@ public class MethodHandlesTest {
} }
} }
// Local abbreviated copy of sun.invoke.util.ValueConversions // Local abbreviated copy of sun.invoke.util.ValueConversions
// This guy tests access from outside the same package member, but inside
// the package itself.
class ValueConversions { class ValueConversions {
private static final Lookup IMPL_LOOKUP = MethodHandles.lookup(); private static final Lookup IMPL_LOOKUP = MethodHandles.lookup();
private static final Object[] NO_ARGS_ARRAY = {}; private static final Object[] NO_ARGS_ARRAY = {};

View file

@ -0,0 +1,376 @@
/*
* Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @summary white-box testing of method handle sub-primitives
* @run junit test.java.lang.invoke.PrivateInvokeTest
*/
package test.java.lang.invoke;
import java.lang.invoke.*;
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.*;
import static org.junit.Assert.*;
public class PrivateInvokeTest {
// Utility functions
private static final Lookup LOOKUP = lookup();
private static final Class<?> THIS_CLASS = PrivateInvokeTest.class;
private static final int
REF_NONE = 0, // null value
REF_getField = 1,
REF_getStatic = 2,
REF_putField = 3,
REF_putStatic = 4,
REF_invokeVirtual = 5,
REF_invokeStatic = 6,
REF_invokeSpecial = 7,
REF_newInvokeSpecial = 8,
REF_invokeInterface = 9,
REF_LIMIT = 10,
REF_MH_invokeBasic = REF_NONE;;
private static final String[] REF_KIND_NAMES = {
"MH::invokeBasic",
"REF_getField", "REF_getStatic", "REF_putField", "REF_putStatic",
"REF_invokeVirtual", "REF_invokeStatic", "REF_invokeSpecial",
"REF_newInvokeSpecial", "REF_invokeInterface"
};
private int verbose;
//{ verbose = 99; } // for debugging
{
String vstr = System.getProperty(THIS_CLASS.getSimpleName()+".verbose");
if (vstr == null)
vstr = System.getProperty(THIS_CLASS.getName()+".verbose");
if (vstr == null)
vstr = System.getProperty("test.verbose");
if (vstr != null) verbose = Integer.parseInt(vstr);
}
private static int referenceKind(Method m) {
if (Modifier.isStatic(m.getModifiers()))
return REF_invokeStatic;
else if (m.getDeclaringClass().isInterface())
return REF_invokeInterface;
else if (Modifier.isFinal(m.getModifiers()) ||
Modifier.isFinal(m.getDeclaringClass().getModifiers()))
return REF_invokeSpecial;
else
return REF_invokeVirtual;
}
private static MethodType basicType(MethodType mtype) {
MethodType btype = mtype.erase();
if (btype.hasPrimitives()) {
for (int i = -1; i < mtype.parameterCount(); i++) {
Class<?> type = (i < 0 ? mtype.returnType() : mtype.parameterType(i));
if (type == boolean.class ||
type == byte.class ||
type == char.class ||
type == short.class) {
type = int.class;
if (i < 0)
btype = btype.changeReturnType(type);
else
btype = btype.changeParameterType(i, type);
}
}
}
return btype;
}
private static Method getMethod(Class<?> defc, String name, Class<?>... ptypes) {
try {
return defc.getDeclaredMethod(name, ptypes);
} catch (NoSuchMethodException ex) {
}
try {
return defc.getMethod(name, ptypes);
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException(ex);
}
}
private static MethodHandle unreflect(Method m) {
try {
MethodHandle mh = LOOKUP.unreflect(m);
if (Modifier.isTransient(m.getModifiers()))
mh = mh.asFixedArity(); // remove varargs wrapper
return mh;
} catch (IllegalAccessException ex) {
throw new IllegalArgumentException(ex);
}
}
private static final Lookup DIRECT_INVOKER_LOOKUP;
private static final Class<?> MEMBER_NAME_CLASS;
private static final MethodHandle MH_INTERNAL_MEMBER_NAME;
private static final MethodHandle MH_DEBUG_STRING;
static {
try {
// This is white box testing. Use reflection to grab private implementation bits.
String magicName = "IMPL_LOOKUP";
Field magicLookup = MethodHandles.Lookup.class.getDeclaredField(magicName);
// This unit test will fail if a security manager is installed.
magicLookup.setAccessible(true);
// Forbidden fruit...
DIRECT_INVOKER_LOOKUP = (Lookup) magicLookup.get(null);
MEMBER_NAME_CLASS = Class.forName("java.lang.invoke.MemberName", false, MethodHandle.class.getClassLoader());
MH_INTERNAL_MEMBER_NAME = DIRECT_INVOKER_LOOKUP
.findVirtual(MethodHandle.class, "internalMemberName", methodType(MEMBER_NAME_CLASS))
.asType(methodType(Object.class, MethodHandle.class));
MH_DEBUG_STRING = DIRECT_INVOKER_LOOKUP
.findVirtual(MethodHandle.class, "debugString", methodType(String.class));
} catch (ReflectiveOperationException ex) {
throw new InternalError(ex);
}
}
private Object internalMemberName(MethodHandle mh) {
try {
return MH_INTERNAL_MEMBER_NAME.invokeExact(mh);
} catch (Throwable ex) {
throw new InternalError(ex);
}
}
private String debugString(MethodHandle mh) {
try {
return (String) MH_DEBUG_STRING.invokeExact(mh);
} catch (Throwable ex) {
throw new InternalError(ex);
}
}
private static MethodHandle directInvoker(int refKind, MethodType mtype) {
return directInvoker(REF_KIND_NAMES[refKind], mtype);
}
private static MethodHandle directInvoker(String name, MethodType mtype) {
boolean isStatic;
mtype = mtype.erase();
if (name.startsWith("MH::")) {
isStatic = false;
name = strip("MH::", name);
} else if (name.startsWith("REF_")) {
isStatic = true;
name = strip("REF_", name);
if (name.startsWith("invoke"))
name = "linkTo"+strip("invoke", name);
mtype = mtype.appendParameterTypes(MEMBER_NAME_CLASS);
} else {
throw new AssertionError("name="+name);
}
//System.out.println("directInvoker = "+name+mtype);
try {
if (isStatic)
return DIRECT_INVOKER_LOOKUP
.findStatic(MethodHandle.class, name, mtype);
else
return DIRECT_INVOKER_LOOKUP
.findVirtual(MethodHandle.class, name, mtype);
} catch (ReflectiveOperationException ex) {
throw new IllegalArgumentException(ex);
}
}
private Object invokeWithArguments(Method m, Object... args) {
Object recv = null;
if (!Modifier.isStatic(m.getModifiers())) {
recv = args[0];
args = pop(1, args);
}
try {
return m.invoke(recv, args);
} catch (IllegalAccessException|IllegalArgumentException|InvocationTargetException ex) {
throw new IllegalArgumentException(ex);
}
}
private Object invokeWithArguments(MethodHandle mh, Object... args) {
try {
return mh.invokeWithArguments(args);
} catch (Throwable ex) {
throw new IllegalArgumentException(ex);
}
}
private int counter;
private Object makeArgument(Class<?> type) {
final String cname = type.getSimpleName();
final int n = ++counter;
final int nn = (n << 10) + 13;
if (type.isAssignableFrom(String.class)) {
return "<"+cname+"#"+nn+">";
}
if (type == THIS_CLASS) return this.withCounter(nn);
if (type == Integer.class || type == int.class) return nn;
if (type == Character.class || type == char.class) return (char)(n % 100+' ');
if (type == Byte.class || type == byte.class) return (byte)-(n % 100);
if (type == Long.class || type == long.class) return (long)nn;
throw new IllegalArgumentException("don't know how to make argument of type: "+type);
}
private Object[] makeArguments(Class<?>... ptypes) {
Object[] args = new Object[ptypes.length];
for (int i = 0; i < args.length; i++)
args[i] = makeArgument(ptypes[i]);
return args;
}
private Object[] makeArguments(MethodType mtype) {
return makeArguments(mtype.parameterArray());
}
private Object[] pop(int n, Object[] args) {
if (n >= 0)
return Arrays.copyOfRange(args, n, args.length);
else
return Arrays.copyOfRange(args, 0, args.length+n);
}
private Object[] pushAtFront(Object arg1, Object[] args) {
Object[] res = new Object[1+args.length];
res[0] = arg1;
System.arraycopy(args, 0, res, 1, args.length);
return res;
}
private Object[] pushAtBack(Object[] args, Object argN) {
Object[] res = new Object[1+args.length];
System.arraycopy(args, 0, res, 0, args.length);
res[args.length] = argN;
return res;
}
private static String strip(String prefix, String s) {
assert(s.startsWith(prefix));
return s.substring(prefix.length());
}
private final int[] refKindTestCounts = new int[REF_KIND_NAMES.length];
@After
public void printCounts() {
ArrayList<String> zeroes = new ArrayList<>();
for (int i = 0; i < refKindTestCounts.length; i++) {
final int count = refKindTestCounts[i];
final String name = REF_KIND_NAMES[i];
if (count == 0) {
if (name != null) zeroes.add(name);
continue;
}
if (verbose >= 0)
System.out.println("test count for "+name+" : "+count);
else if (name != null)
zeroes.add(name);
}
if (verbose >= 0)
System.out.println("test counts zero for "+zeroes);
}
// Test subjects
public static String makeString(Object x) { return "makeString("+x+")"; }
public static String dupString(String x) { return "("+x+"+"+x+")"; }
public static String intString(int x) { return "intString("+x+")"; }
public static String byteString(byte x) { return "byteString("+x+")"; }
public static String longString(String x, long y, String z) { return "longString("+x+y+z+")"; }
public final String toString() {
return "<"+getClass().getSimpleName()+"#"+counter+">";
}
public final String hello() { return "hello from "+this; }
private PrivateInvokeTest withCounter(int counter) {
PrivateInvokeTest res = new PrivateInvokeTest();
res.counter = counter;
return res;
}
public static void main(String... av) throws Throwable {
new PrivateInvokeTest().run();
}
public void run() throws Throwable {
testFirst();
testInvokeDirect();
}
@Test
public void testFirst() throws Throwable {
if (true) return; // nothing here
try {
System.out.println("start of testFirst");
} finally {
System.out.println("end of testFirst");
}
}
@Test
public void testInvokeDirect() {
testInvokeDirect(getMethod(THIS_CLASS, "hello"));
testInvokeDirect(getMethod(Object.class, "toString"));
testInvokeDirect(getMethod(Comparable.class, "compareTo", Object.class));
testInvokeDirect(getMethod(THIS_CLASS, "makeString", Object.class));
testInvokeDirect(getMethod(THIS_CLASS, "dupString", String.class));
testInvokeDirect(getMethod(THIS_CLASS, "intString", int.class));
testInvokeDirect(getMethod(THIS_CLASS, "byteString", byte.class));
testInvokeDirect(getMethod(THIS_CLASS, "longString", String.class, long.class, String.class));
}
void testInvokeDirect(Method m) {
final int refKind = referenceKind(m);
testInvokeDirect(m, refKind);
testInvokeDirect(m, REF_MH_invokeBasic);
}
void testInvokeDirect(Method m, int refKind) {
if (verbose >= 1)
System.out.println("testInvoke m="+m+" : "+REF_KIND_NAMES[refKind]);
final MethodHandle mh = unreflect(m);
Object[] args = makeArguments(mh.type());
Object res1 = invokeWithArguments(m, args);
// res1 comes from java.lang.reflect.Method::invoke
if (verbose >= 1)
System.out.println("m"+Arrays.asList(args)+" => "+res1);
// res2 comes from java.lang.invoke.MethodHandle::invoke
Object res2 = invokeWithArguments(mh, args);
assertEquals(res1, res2);
MethodType mtype = mh.type();
testInvokeVia("DMH invoker", refKind, directInvoker(refKind, mtype), mh, res1, args);
MethodType etype = mtype.erase();
if (etype != mtype) {
// Try a detuned invoker.
testInvokeVia("erased DMH invoker", refKind, directInvoker(refKind, etype), mh, res1, args);
}
MethodType btype = basicType(mtype);
if (btype != mtype && btype != etype) {
// Try a detuned invoker.
testInvokeVia("basic DMH invoker", refKind, directInvoker(refKind, btype), mh, res1, args);
}
if (false) {
// this can crash the JVM
testInvokeVia("generic DMH invoker", refKind, directInvoker(refKind, mtype.generic()), mh, res1, args);
}
refKindTestCounts[refKind] += 1;
}
void testInvokeVia(String kind, int refKind, MethodHandle invoker, MethodHandle mh, Object res1, Object... args) {
Object[] args1;
if (refKind == REF_MH_invokeBasic)
args1 = pushAtFront(mh, args);
else
args1 = pushAtBack(args, internalMemberName(mh));
if (verbose >= 2) {
System.out.println(kind+" invoker="+invoker+" mh="+debugString(mh)+" args="+Arrays.asList(args1));
}
Object res3 = invokeWithArguments(invoker, args1);
assertEquals(res1, res3);
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2009-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package test.java.lang.invoke.remote;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import test.java.lang.invoke.MethodHandlesTest;
/**
* Out-of-package access into protected members of test.java.lang.invoke.remote.MethodHandle.PubExample.
*/
public class RemoteExample extends MethodHandlesTest.PubExample {
public RemoteExample() { super("RemoteExample"); }
public static Lookup lookup() { return MethodHandles.lookup(); }
public final void fin_v0() { MethodHandlesTest.called("Rem/fin_v0", this); }
protected void pro_v0() { MethodHandlesTest.called("Rem/pro_v0", this); }
protected static void pro_s0() { MethodHandlesTest.called("Rem/pro_s0"); }
}

View file

@ -121,36 +121,6 @@ public class ValueConversionsTest {
} }
} }
@Test
public void testUnboxRaw() throws Throwable {
//System.out.println("unboxRaw");
for (Wrapper w : Wrapper.values()) {
if (w == Wrapper.OBJECT) continue; // skip this; no raw form
//System.out.println(w);
for (int n = -5; n < 10; n++) {
Object box = w.wrap(n);
long expResult = w.unwrapRaw(box);
Object box2 = w.wrapRaw(expResult);
assertEquals(box, box2);
MethodHandle unboxer = ValueConversions.unboxRaw(w.primitiveType());
long result = -1;
switch (w) {
case INT: result = (int) unboxer.invokeExact(box); break;
case LONG: result = (long) unboxer.invokeExact(box); break;
case FLOAT: result = (int) unboxer.invokeExact(box); break;
case DOUBLE: result = (long) unboxer.invokeExact(box); break;
case CHAR: result = (int) unboxer.invokeExact(box); break;
case BYTE: result = (int) unboxer.invokeExact(box); break;
case SHORT: result = (int) unboxer.invokeExact(box); break;
case BOOLEAN: result = (int) unboxer.invokeExact(box); break;
case VOID: result = (int) unboxer.invokeExact(box); break;
}
assertEquals("(w,n,box)="+Arrays.asList(w,n,box),
expResult, result);
}
}
}
@Test @Test
public void testBox() throws Throwable { public void testBox() throws Throwable {
//System.out.println("box"); //System.out.println("box");
@ -179,65 +149,6 @@ public class ValueConversionsTest {
} }
} }
@Test
public void testBoxRaw() throws Throwable {
//System.out.println("boxRaw");
for (Wrapper w : Wrapper.values()) {
if (w == Wrapper.VOID) continue; // skip this; no unboxed form
if (w == Wrapper.OBJECT) continue; // skip this; no raw form
//System.out.println(w);
for (int n = -5; n < 10; n++) {
Object box = w.wrap(n);
long raw = w.unwrapRaw(box);
Object expResult = box;
MethodHandle boxer = ValueConversions.boxRaw(w.primitiveType());
Object result = null;
switch (w) {
case INT: result = boxer.invokeExact((int)raw); break;
case LONG: result = boxer.invokeExact(raw); break;
case FLOAT: result = boxer.invokeExact((int)raw); break;
case DOUBLE: result = boxer.invokeExact(raw); break;
case CHAR: result = boxer.invokeExact((int)raw); break;
case BYTE: result = boxer.invokeExact((int)raw); break;
case SHORT: result = boxer.invokeExact((int)raw); break;
case BOOLEAN: result = boxer.invokeExact((int)raw); break;
}
assertEquals("(dst,src,n,box)="+Arrays.asList(w,w,n,box),
expResult, result);
}
}
}
@Test
public void testReboxRaw() throws Throwable {
//System.out.println("reboxRaw");
for (Wrapper w : Wrapper.values()) {
Wrapper pw = Wrapper.forPrimitiveType(w.rawPrimitiveType());
if (w == Wrapper.VOID) continue; // skip this; no unboxed form
if (w == Wrapper.OBJECT) continue; // skip this; no raw form
//System.out.println(w);
for (int n = -5; n < 10; n++) {
Object box = w.wrap(n);
Object raw = pw.wrap(w.unwrapRaw(box));
Object expResult = box;
MethodHandle boxer = ValueConversions.rebox(w.primitiveType());
Object result = null;
switch (w) {
case INT: result = boxer.invokeExact(raw); break;
case LONG: result = boxer.invokeExact(raw); break;
case FLOAT: result = boxer.invokeExact(raw); break;
case DOUBLE: result = boxer.invokeExact(raw); break;
case CHAR: result = boxer.invokeExact(raw); break;
case BYTE: result = boxer.invokeExact(raw); break;
case SHORT: result = boxer.invokeExact(raw); break;
case BOOLEAN: result = boxer.invokeExact(raw); break;
}
assertEquals("(dst,src,n,box)="+Arrays.asList(w,w,n,box),
expResult, result);
}
}
}
@Test @Test
public void testCast() throws Throwable { public void testCast() throws Throwable {
//System.out.println("cast"); //System.out.println("cast");
@ -280,6 +191,91 @@ public class ValueConversionsTest {
assertEquals(expResult, result); assertEquals(expResult, result);
} }
@Test
public void testConvert() throws Throwable {
//System.out.println("convert");
for (long tval = 0, ctr = 0;;) {
if (++ctr > 99999) throw new AssertionError("too many test values");
// next test value:
//System.out.println(Long.toHexString(tval)); // prints 3776 test patterns
tval = nextTestValue(tval);
if (tval == 0) {
//System.out.println("test value count = "+ctr); // 3776 = 8*59*8
break; // repeat
}
}
for (Wrapper src : Wrapper.values()) {
for (Wrapper dst : Wrapper.values()) {
testConvert(src, dst, 0);
}
}
}
static void testConvert(Wrapper src, Wrapper dst, long tval) throws Throwable {
//System.out.println(src+" => "+dst);
boolean testSingleCase = (tval != 0);
final long tvalInit = tval;
MethodHandle conv = ValueConversions.convertPrimitive(src, dst);
MethodType convType;
if (src == Wrapper.VOID)
convType = MethodType.methodType(dst.primitiveType() /* , void */);
else
convType = MethodType.methodType(dst.primitiveType(), src.primitiveType());
assertEquals(convType, conv.type());
MethodHandle converter = conv.asType(conv.type().changeReturnType(Object.class));
for (;;) {
long n = tval;
Object testValue = src.wrap(n);
Object expResult = dst.cast(testValue, dst.primitiveType());
Object result;
switch (src) {
case INT: result = converter.invokeExact((int)n); break;
case LONG: result = converter.invokeExact(/*long*/n); break;
case FLOAT: result = converter.invokeExact((float)n); break;
case DOUBLE: result = converter.invokeExact((double)n); break;
case CHAR: result = converter.invokeExact((char)n); break;
case BYTE: result = converter.invokeExact((byte)n); break;
case SHORT: result = converter.invokeExact((short)n); break;
case OBJECT: result = converter.invokeExact((Object)n); break;
case BOOLEAN: result = converter.invokeExact((n & 1) != 0); break;
case VOID: result = converter.invokeExact(); break;
default: throw new AssertionError();
}
assertEquals("(src,dst,n,testValue)="+Arrays.asList(src,dst,"0x"+Long.toHexString(n),testValue),
expResult, result);
if (testSingleCase) break;
// next test value:
tval = nextTestValue(tval);
if (tval == tvalInit) break; // repeat
}
}
static long tweakSign(long x) {
// Assuming that x is mostly zeroes, make those zeroes follow bit #62 (just below the sign).
// This function is self-inverse.
final long MID_SIGN_BIT = 62;
long sign = -((x >>> MID_SIGN_BIT) & 1); // all ones or all zeroes
long flip = (sign >>> -MID_SIGN_BIT); // apply the sign below the mid-bit
return x ^ flip;
}
static long nextTestValue(long x) {
// Produce 64 bits with three component bitfields: [ high:3 | mid:58 | low:3 ].
// The high and low fields vary through all possible bit patterns.
// The middle field is either all zero or has a single bit set.
// For better coverage of the neighborhood of zero, an internal sign bit is xored downward also.
long ux = tweakSign(x); // unsign the middle field
final long LOW_BITS = 3, LOW_BITS_MASK = (1L << LOW_BITS)-1;
final long HIGH_BITS = 3, HIGH_BITS_MASK = ~(-1L >>> HIGH_BITS);
if ((ux & LOW_BITS_MASK) != LOW_BITS_MASK) {
++ux;
} else {
ux &= ~LOW_BITS_MASK;
long midBit = (ux & ~HIGH_BITS_MASK);
if (midBit == 0)
midBit = (1L<<LOW_BITS); // introduce a low bit
ux += midBit;
}
return tweakSign(ux);
}
@Test @Test
public void testVarargsArray() throws Throwable { public void testVarargsArray() throws Throwable {
//System.out.println("varargsArray"); //System.out.println("varargsArray");
@ -332,7 +328,7 @@ public class ValueConversionsTest {
} }
private void testTypedVarargsArray(Class<?> arrayType) throws Throwable { private void testTypedVarargsArray(Class<?> arrayType) throws Throwable {
System.out.println(arrayType.getSimpleName()); //System.out.println(arrayType.getSimpleName());
Class<?> elemType = arrayType.getComponentType(); Class<?> elemType = arrayType.getComponentType();
int MIN = START_ARITY; int MIN = START_ARITY;
int MAX = MAX_ARITY-2; // 253+1 would cause parameter overflow with 'this' added int MAX = MAX_ARITY-2; // 253+1 would cause parameter overflow with 'this' added