6839839: access checking logic is wrong at three points in MethodHandles
Summary: point fixes to access checking logic
Reviewed-by: mr
/*
* Copyright 2008-2009 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 sun.dyn;
import java.dyn.MethodHandle;
import java.dyn.MethodHandles;
import java.dyn.MethodHandles.Lookup;
import java.dyn.MethodType;
import sun.dyn.util.VerifyType;
import java.dyn.NoAccessException;
import static sun.dyn.MemberName.newIllegalArgumentException;
import static sun.dyn.MemberName.newNoAccessException;
/**
* Base class for method handles, containing JVM-specific fields and logic.
* TO DO: It should not be a base class.
* @author jrose
*/
public abstract class MethodHandleImpl {
// Fields which really belong in MethodHandle:
private byte vmentry; // adapter stub or method entry point
//private int vmslots; // optionally, hoist type.form.vmslots
protected Object vmtarget; // VM-specific, class-specific target value
//MethodType type; // defined in MethodHandle
// 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;
// type is defined in java.dyn.MethodHandle, which is platform-independent
// vmentry (a void* field) is used *only* by 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.
/**
* VM-based method handles must have a security token.
* This security token can only be obtained by trusted code.
* Do not create method handles directly; use factory methods.
*/
public MethodHandleImpl(Access token) {
Access.check(token);
}
/** Initialize the method type form to participate in JVM calls.
* This is done once for each erased type.
*/
public static void init(Access token, MethodType self) {
Access.check(token);
if (MethodHandleNatives.JVM_SUPPORT)
MethodHandleNatives.init(self);
}
/// Factory methods to create method handles:
private static final MemberName.Factory LOOKUP = MemberName.Factory.INSTANCE;
static private Lookup IMPL_LOOKUP_INIT;
public static void initLookup(Access token, Lookup lookup) {
Access.check(token);
if (IMPL_LOOKUP_INIT != null || lookup.lookupClass() != null)
throw new InternalError();
IMPL_LOOKUP_INIT = lookup;
}
public static Lookup getLookup(Access token) {
Access.check(token);
return IMPL_LOOKUP;
}
static {
// Force initialization:
Lookup.PUBLIC_LOOKUP.lookupClass();
if (IMPL_LOOKUP_INIT == null)
throw new InternalError();
}
public static void initStatics() {
// Trigger preceding sequence.
}
/** Shared secret with MethodHandles.Lookup, a copy of Lookup.IMPL_LOOKUP. */
static final Lookup IMPL_LOOKUP = IMPL_LOOKUP_INIT;
/** Look up a given method.
* Callable only from java.dyn and related packages.
* <p>
* The resulting method handle type will be of the given type,
* with a receiver type {@code rcvc} prepended if the member is not static.
* <p>
* Access checks are made as of the given lookup class.
* In particular, if the method is protected and {@code defc} is in a
* different package from the lookup class, then {@code rcvc} must be
* the lookup class or a subclass.
* @param token Proof that the lookup class has access to this package.
* @param member Resolved method or constructor to call.
* @param name Name of the desired method.
* @param rcvc Receiver type of desired non-static method (else null)
* @param doDispatch whether the method handle will test the receiver type
* @param lookupClass access-check relative to this class
* @return a direct handle to the matching method
* @throws NoAccessException if the given method cannot be accessed by the lookup class
*/
public static
MethodHandle findMethod(Access token, MemberName method,
boolean doDispatch, Class<?> lookupClass) {
Access.check(token); // only trusted calls
MethodType mtype = method.getMethodType();
MethodType rtype = mtype;
if (method.isStatic()) {
doDispatch = false;
} else {
// adjust the advertised receiver type to be exactly the one requested
// (in the case of invokespecial, this will be the calling class)
Class<?> recvType = method.getDeclaringClass();
mtype = mtype.insertParameterType(0, recvType);
if (method.isConstructor())
doDispatch = true;
// FIXME: JVM has trouble building MH.invoke sites for
// classes off the boot class path
rtype = mtype;
if (recvType.getClassLoader() != null)
rtype = rtype.changeParameterType(0, Object.class);
}
DirectMethodHandle mh = new DirectMethodHandle(mtype, method, doDispatch, lookupClass);
if (!mh.isValid())
throw newNoAccessException(method, lookupClass);
MethodHandle rmh = AdapterMethodHandle.makePairwiseConvert(token, rtype, mh);
if (rmh == null) throw new InternalError();
return rmh;
}
public static
MethodHandle accessField(Access token,
MemberName member, boolean isSetter,
Class<?> lookupClass) {
Access.check(token);
// FIXME: Use sun.misc.Unsafe to dig up the dirt on the field.
throw new UnsupportedOperationException("Not yet implemented");
}
public static
MethodHandle accessArrayElement(Access token,
Class<?> arrayClass, boolean isSetter) {
Access.check(token);
if (!arrayClass.isArray())
throw newIllegalArgumentException("not an array: "+arrayClass);
// FIXME: Use sun.misc.Unsafe to dig up the dirt on the array.
throw new UnsupportedOperationException("Not yet implemented");
}
/** Bind a predetermined first argument to the given direct method handle.
* Callable only from MethodHandles.
* @param token Proof that the caller has access to this package.
* @param target Any direct method handle.
* @param receiver Receiver (or first static method argument) to pre-bind.
* @return a BoundMethodHandle for the given DirectMethodHandle, or null if it does not exist
*/
public static
MethodHandle bindReceiver(Access token,
MethodHandle target, Object receiver) {
Access.check(token);
if (target instanceof AdapterMethodHandle) {
Object info = MethodHandleNatives.getTargetInfo(target);
if (info instanceof DirectMethodHandle) {
DirectMethodHandle dmh = (DirectMethodHandle) info;
if (receiver == null ||
dmh.type().parameterType(0).isAssignableFrom(receiver.getClass()))
target = dmh;
}
}
if (target instanceof DirectMethodHandle)
return new BoundMethodHandle((DirectMethodHandle)target, receiver, 0);
return null; // let caller try something else
}
/** Bind a predetermined argument to the given arbitrary method handle.
* Callable only from MethodHandles.
* @param token Proof that the caller has access to this package.
* @param target Any method handle.
* @param receiver Argument (which can be a boxed primitive) to pre-bind.
* @return a suitable BoundMethodHandle
*/
public static
MethodHandle bindArgument(Access token,
MethodHandle target, int argnum, Object receiver) {
Access.check(token);
throw new UnsupportedOperationException("NYI");
}
public static MethodHandle convertArguments(Access token,
MethodHandle target,
MethodType newType,
MethodType oldType,
int[] permutationOrNull) {
Access.check(token);
MethodHandle res = AdapterMethodHandle.makePairwiseConvert(token, newType, target);
if (res != null)
return res;
int argc = oldType.parameterCount();
// The JVM can't do it directly, so fill in the gap with a Java adapter.
// TO DO: figure out what to put here from case-by-case experience
// Use a heavier method: Convert all the arguments to Object,
// then back to the desired types. We might have to use Java-based
// method handles to do this.
MethodType objType = MethodType.makeGeneric(argc);
MethodHandle objTarget = AdapterMethodHandle.makePairwiseConvert(token, objType, target);
if (objTarget == null)
objTarget = FromGeneric.make(target);
res = AdapterMethodHandle.makePairwiseConvert(token, newType, objTarget);
if (res != null)
return res;
return ToGeneric.make(newType, objTarget);
}
public static MethodHandle spreadArguments(Access token,
MethodHandle target,
MethodType newType,
int spreadArg) {
Access.check(token);
// TO DO: maybe allow the restarg to be Object and implicitly cast to Object[]
MethodType oldType = target.type();
// spread the last argument of newType to oldType
int spreadCount = oldType.parameterCount() - spreadArg;
Class<Object[]> spreadArgType = Object[].class;
MethodHandle res = AdapterMethodHandle.makeSpreadArguments(token, newType, target, spreadArgType, spreadArg, spreadCount);
if (res != null)
return res;
// try an intermediate adapter
Class<?> spreadType = null;
if (spreadArg < 0 || spreadArg >= newType.parameterCount()
|| !VerifyType.isSpreadArgType(spreadType = newType.parameterType(spreadArg)))
throw newIllegalArgumentException("no restarg in "+newType);
Class<?>[] ptypes = oldType.parameterArray();
for (int i = 0; i < spreadCount; i++)
ptypes[spreadArg + i] = VerifyType.spreadArgElementType(spreadType, i);
MethodType midType = MethodType.make(newType.returnType(), ptypes);
// after spreading, some arguments may need further conversion
target = convertArguments(token, target, midType, oldType, null);
if (target == null)
throw new UnsupportedOperationException("NYI: convert "+midType+" =calls=> "+oldType);
res = AdapterMethodHandle.makeSpreadArguments(token, newType, target, spreadArgType, spreadArg, spreadCount);
return res;
}
public static MethodHandle collectArguments(Access token,
MethodHandle target,
MethodType newType,
int collectArg) {
if (collectArg > 0)
throw new UnsupportedOperationException("NYI");
throw new UnsupportedOperationException("NYI");
}
public static
MethodHandle dropArguments(Access token, MethodHandle target,
MethodType newType, int argnum) {
Access.check(token);
throw new UnsupportedOperationException("NYI");
}
public static
MethodHandle makeGuardWithTest(Access token,
final MethodHandle test,
final MethodHandle target,
final MethodHandle fallback) {
Access.check(token);
// %%% This is just a sketch. It needs to be de-boxed.
// Adjust the handles to accept varargs lists.
MethodType type = target.type();
Class<?> rtype = type.returnType();
if (type.parameterCount() != 1 || type.parameterType(0).isPrimitive()) {
MethodType vatestType = MethodType.make(boolean.class, Object[].class);
MethodType vatargetType = MethodType.make(rtype, Object[].class);
MethodHandle vaguard = makeGuardWithTest(token,
MethodHandles.spreadArguments(test, vatestType),
MethodHandles.spreadArguments(target, vatargetType),
MethodHandles.spreadArguments(fallback, vatargetType));
return MethodHandles.collectArguments(vaguard, type);
}
if (rtype.isPrimitive()) {
MethodType boxtype = type.changeReturnType(Object.class);
MethodHandle boxguard = makeGuardWithTest(token,
test,
MethodHandles.convertArguments(target, boxtype),
MethodHandles.convertArguments(fallback, boxtype));
return MethodHandles.convertArguments(boxguard, type);
}
// Got here? Reduced calling sequence to Object(Object).
class Guarder {
Object invoke(Object x) {
// If javac supports MethodHandle.invoke directly:
//z = vatest.invoke<boolean>(arguments);
// If javac does not support direct MH.invoke calls:
boolean z = (Boolean) MethodHandles.invoke_1(test, x);
MethodHandle mh = (z ? target : fallback);
return MethodHandles.invoke_1(mh, x);
}
MethodHandle handle() {
MethodType invokeType = MethodType.makeGeneric(0, true);
MethodHandle vh = IMPL_LOOKUP.bind(this, "invoke", invokeType);
return MethodHandles.collectArguments(vh, target.type());
}
}
return new Guarder().handle();
}
public static
MethodHandle combineArguments(Access token, MethodHandle target, MethodHandle checker, int pos) {
Access.check(token);
throw new UnsupportedOperationException("Not yet implemented");
}
protected static String basicToString(MethodHandle target) {
MemberName name = null;
if (target != null)
name = MethodHandleNatives.getMethodName(target);
if (name == null)
return "<unknown>";
return name.getName();
}
protected static String addTypeString(MethodHandle target, String name) {
if (target == null) return name;
return name+target.type();
}
static RuntimeException newIllegalArgumentException(String string) {
return new IllegalArgumentException(string);
}
@Override
public String toString() {
MethodHandle self = (MethodHandle) this;
return addTypeString(self, basicToString(self));
}
}