8032681: Issues with Nashorn

Reviewed-by: ahgross, jlaskey, sundar
This commit is contained in:
Attila Szegedi 2014-01-30 20:13:27 +01:00
parent ea24aa581b
commit 281b87b796
30 changed files with 727 additions and 244 deletions

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) 2010, 2013, 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.
*/
/*
* This file is available under and governed by the GNU General Public
* License version 2 only, as published by the Free Software Foundation.
* However, the following notice accompanied the original version of this
* file, and Oracle licenses the original version of this file under the BSD
* license:
*/
/*
Copyright 2009-2013 Attila Szegedi
Licensed under both the Apache License, Version 2.0 (the "Apache License")
and the BSD License (the "BSD License"), with licensee being free to
choose either of the two at their discretion.
You may not use this file except in compliance with either the Apache
License or the BSD License.
If you choose to use this file in compliance with the Apache License, the
following notice applies to you:
You may obtain a copy of the Apache License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing
permissions and limitations under the License.
If you choose to use this file in compliance with the BSD License, the
following notice applies to you:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jdk.internal.dynalink.linker;
public class GuardedTypeConversion {
private final GuardedInvocation conversionInvocation;
private final boolean cacheable;
public GuardedTypeConversion(final GuardedInvocation conversionInvocation, final boolean cacheable) {
this.conversionInvocation = conversionInvocation;
this.cacheable = cacheable;
}
public GuardedInvocation getConversionInvocation() {
return conversionInvocation;
}
public boolean isCacheable() {
return cacheable;
}
}

View File

@ -96,19 +96,19 @@ import jdk.internal.dynalink.support.TypeUtilities;
*/
public interface GuardingTypeConverterFactory {
/**
* Returns a guarded invocation that receives an Object of the specified source type and returns an Object converted
* to the specified target type. The type of the invocation is targetType(sourceType), while the type of the guard
* is boolean(sourceType). Note that this will never be invoked for type conversions allowed by the JLS 5.3 "Method
* Invocation Conversion", see {@link TypeUtilities#isMethodInvocationConvertible(Class, Class)} for details. An
* implementation can assume it is never requested to produce a converter for these conversions.
* Returns a guarded type conversion that receives an Object of the specified source type and returns an Object
* converted to the specified target type. The type of the invocation is targetType(sourceType), while the type of
* the guard is boolean(sourceType). Note that this will never be invoked for type conversions allowed by the JLS
* 5.3 "Method Invocation Conversion", see {@link TypeUtilities#isMethodInvocationConvertible(Class, Class)} for
* details. An implementation can assume it is never requested to produce a converter for these conversions.
*
* @param sourceType source type
* @param targetType the target type.
* @return a guarded invocation that can take an object (if it passes guard) and returns another object that is its
* representation coerced into the target type. In case the factory is certain it is unable to handle a conversion,
* it can return null. In case the factory is certain that it can always handle the conversion, it can return an
* unconditional invocation (one whose guard is null).
* @return a guarded type conversion that contains a guarded invocation that can take an object (if it passes guard)
* and return another object that is its representation coerced into the target type. In case the factory is certain
* it is unable to handle a conversion, it can return null. In case the factory is certain that it can always handle
* the conversion, it can return an unconditional invocation (one whose guard is null).
* @throws Exception if there was an error during creation of the converter
*/
public GuardedInvocation convertToType(Class<?> sourceType, Class<?> targetType) throws Exception;
public GuardedTypeConversion convertToType(Class<?> sourceType, Class<?> targetType) throws Exception;
}

View File

@ -98,6 +98,9 @@ import jdk.internal.dynalink.linker.LinkerServices;
*/
public class LinkerServicesImpl implements LinkerServices {
private static final RuntimePermission GET_CURRENT_LINK_REQUEST = new RuntimePermission("dynalink.getCurrentLinkRequest");
private static final ThreadLocal<LinkRequest> threadLinkRequest = new ThreadLocal<>();
private final TypeConverterFactory typeConverterFactory;
private final GuardingDynamicLinker topLevelLinker;
@ -135,6 +138,26 @@ public class LinkerServicesImpl implements LinkerServices {
@Override
public GuardedInvocation getGuardedInvocation(LinkRequest linkRequest) throws Exception {
return topLevelLinker.getGuardedInvocation(linkRequest, this);
final LinkRequest prevLinkRequest = threadLinkRequest.get();
threadLinkRequest.set(linkRequest);
try {
return topLevelLinker.getGuardedInvocation(linkRequest, this);
} finally {
threadLinkRequest.set(prevLinkRequest);
}
}
/**
* Returns the currently processed link request, or null if the method is invoked outside of the linking process.
* @return the currently processed link request, or null.
* @throws SecurityException if the calling code doesn't have the {@code "dynalink.getCurrentLinkRequest"} runtime
* permission.
*/
public static LinkRequest getCurrentLinkRequest() {
SecurityManager sm = System.getSecurityManager();
if(sm != null) {
sm.checkPermission(GET_CURRENT_LINK_REQUEST);
}
return threadLinkRequest.get();
}
}

View File

@ -94,6 +94,7 @@ import java.util.List;
import jdk.internal.dynalink.linker.ConversionComparator;
import jdk.internal.dynalink.linker.ConversionComparator.Comparison;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.GuardedTypeConversion;
import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
import jdk.internal.dynalink.linker.LinkerServices;
@ -134,8 +135,8 @@ public class TypeConverterFactory {
@Override
protected MethodHandle computeValue(Class<?> targetType) {
if(!canAutoConvert(sourceType, targetType)) {
final MethodHandle converter = getTypeConverterNull(sourceType, targetType);
if(converter != null) {
final MethodHandle converter = getCacheableTypeConverter(sourceType, targetType);
if(converter != IDENTITY_CONVERSION) {
return converter;
}
}
@ -145,6 +146,24 @@ public class TypeConverterFactory {
}
};
private final ClassValue<ClassMap<Boolean>> canConvert = new ClassValue<ClassMap<Boolean>>() {
@Override
protected ClassMap<Boolean> computeValue(final Class<?> sourceType) {
return new ClassMap<Boolean>(getClassLoader(sourceType)) {
@Override
protected Boolean computeValue(Class<?> targetType) {
try {
return getTypeConverterNull(sourceType, targetType) != null;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
}
};
private static final ClassLoader getClassLoader(final Class<?> clazz) {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
@Override
@ -253,7 +272,7 @@ public class TypeConverterFactory {
* @return true if there can be a conversion, false if there can not.
*/
public boolean canConvert(final Class<?> from, final Class<?> to) {
return canAutoConvert(from, to) || getTypeConverterNull(from, to) != null;
return canAutoConvert(from, to) || canConvert.get(from).get(to).booleanValue();
}
/**
@ -294,11 +313,23 @@ public class TypeConverterFactory {
return TypeUtilities.isMethodInvocationConvertible(fromType, toType);
}
/*private*/ MethodHandle getTypeConverterNull(Class<?> sourceType, Class<?> targetType) {
final MethodHandle converter = converterMap.get(sourceType).get(targetType);
/*private*/ MethodHandle getCacheableTypeConverterNull(Class<?> sourceType, Class<?> targetType) {
final MethodHandle converter = getCacheableTypeConverter(sourceType, targetType);
return converter == IDENTITY_CONVERSION ? null : converter;
}
/*private*/ MethodHandle getTypeConverterNull(Class<?> sourceType, Class<?> targetType) {
try {
return getCacheableTypeConverterNull(sourceType, targetType);
} catch(NotCacheableConverter e) {
return e.converter;
}
}
/*private*/ MethodHandle getCacheableTypeConverter(Class<?> sourceType, Class<?> targetType) {
return converterMap.get(sourceType).get(targetType);
}
/**
* Given a source and target type, returns a method handle that converts between them. Never returns null; in worst
* case it will return an identity conversion (that might fail for some values at runtime). You can use this method
@ -309,22 +340,44 @@ public class TypeConverterFactory {
* @return a method handle performing the conversion.
*/
public MethodHandle getTypeConverter(Class<?> sourceType, Class<?> targetType) {
return converterIdentityMap.get(sourceType).get(targetType);
try {
return converterIdentityMap.get(sourceType).get(targetType);
} catch(NotCacheableConverter e) {
return e.converter;
}
}
/*private*/ MethodHandle createConverter(Class<?> sourceType, Class<?> targetType) throws Exception {
final MethodType type = MethodType.methodType(targetType, sourceType);
final MethodHandle identity = IDENTITY_CONVERSION.asType(type);
MethodHandle last = identity;
boolean cacheable = true;
for(int i = factories.length; i-- > 0;) {
final GuardedInvocation next = factories[i].convertToType(sourceType, targetType);
final GuardedTypeConversion next = factories[i].convertToType(sourceType, targetType);
if(next != null) {
next.assertType(type);
last = next.compose(last);
cacheable = cacheable && next.isCacheable();
final GuardedInvocation conversionInvocation = next.getConversionInvocation();
conversionInvocation.assertType(type);
last = conversionInvocation.compose(last);
}
}
return last == identity ? IDENTITY_CONVERSION : last;
if(last == identity) {
return IDENTITY_CONVERSION;
}
if(cacheable) {
return last;
}
throw new NotCacheableConverter(last);
}
/*private*/ static final MethodHandle IDENTITY_CONVERSION = MethodHandles.identity(Object.class);
private static class NotCacheableConverter extends RuntimeException {
final MethodHandle converter;
NotCacheableConverter(final MethodHandle converter) {
super("", null, false, false);
this.converter = converter;
}
}
}

View File

@ -32,6 +32,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
@ -104,7 +105,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
private volatile Property contextProperty;
// default options passed to Nashorn Options object
private static final String[] DEFAULT_OPTIONS = new String[] { "-scripting", "-doe" };
private static final String[] DEFAULT_OPTIONS = new String[] { "-doe" };
// Nashorn script engine error message management
private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages";
@ -355,7 +356,8 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
if (! isInterfaceImplemented(clazz, realSelf)) {
return null;
}
return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz).invoke(realSelf));
return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz,
MethodHandles.publicLookup()).invoke(realSelf));
} finally {
if (globalChanged) {
Context.setGlobal(oldGlobal);

View File

@ -28,6 +28,7 @@ package jdk.nashorn.internal.objects;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Deque;
@ -463,12 +464,14 @@ public final class NativeJava {
* </pre>
* We can see several important concepts in the above example:
* <ul>
* <li>Every specified list of Java types will have exactly one extender subclass in Nashorn - repeated invocations
* of {@code extend} for the same list of types will yield the same extender type. It's a generic adapter that
* delegates to whatever JavaScript functions its implementation object has on a per-instance basis.</li>
* <li>Every specified list of Java types will have one extender subclass in Nashorn per caller protection domain -
* repeated invocations of {@code extend} for the same list of types for scripts same protection domain will yield
* the same extender type. It's a generic adapter that delegates to whatever JavaScript functions its implementation
* object has on a per-instance basis.</li>
* <li>If the Java method is overloaded (as in the above example {@code List.add()}), then your JavaScript adapter
* must be prepared to deal with all overloads.</li>
* <li>You can't invoke {@code super.*()} from adapters for now.</li>
* <li>To invoke super methods from adapters, call them on the adapter instance prefixing them with {@code super$},
* or use the special {@link #_super(Object, Object) super-adapter}.</li>
* <li>It is also possible to specify an ordinary JavaScript object as the last argument to {@code extend}. In that
* case, it is treated as a class-level override. {@code extend} will return an extender class where all instances
* will have the methods implemented by functions on that object, just as if that object were passed as the last
@ -486,15 +489,18 @@ public final class NativeJava {
* t.join()
* </pre>
* As you can see, you don't have to pass any object when you create a new instance of {@code R1} as its
* {@code run()} function was defined already when extending the class. Of course, you can still provide
* instance-level overrides on these objects. The order of precedence is instance-level method, class-level method,
* superclass method, or {@code UnsupportedOperationException} if the superclass method is abstract. If we continue
* our previous example:
* {@code run()} function was defined already when extending the class. If you also want to add instance-level
* overrides on these objects, you will have to repeatedly use {@code extend()} to subclass the class-level adapter.
* For such adapters, the order of precedence is instance-level method, class-level method, superclass method, or
* {@code UnsupportedOperationException} if the superclass method is abstract. If we continue our previous example:
* <pre>
* var r2 = new R1(function() { print("r2.run() invoked!") })
* var R2 = Java.extend(R1);
* var r2 = new R2(function() { print("r2.run() invoked!") })
* r2.run()
* </pre>
* We'll see it'll print {@code "r2.run() invoked!"}, thus overriding on instance-level the class-level behavior.
* Note that you must use {@code Java.extend} to explicitly create an instance-override adapter class from a
* class-override adapter class, as the class-override adapter class is no longer abstract.
* </li>
* </ul>
* @param self not used
@ -541,7 +547,18 @@ public final class NativeJava {
} catch(final ClassCastException e) {
throw typeError("extend.expects.java.types");
}
return JavaAdapterFactory.getAdapterClassFor(stypes, classOverrides);
// Note that while the public API documentation claims self is not used, we actually use it.
// ScriptFunction.findCallMethod will bind the lookup object into it, and we can then use that lookup when
// requesting the adapter class. Note that if Java.extend is invoked with no lookup object, it'll pass the
// public lookup which'll result in generation of a no-permissions adapter. A typical situation this can happen
// is when the extend function is bound.
final MethodHandles.Lookup lookup;
if(self instanceof MethodHandles.Lookup) {
lookup = (MethodHandles.Lookup)self;
} else {
lookup = MethodHandles.publicLookup();
}
return JavaAdapterFactory.getAdapterClassFor(stypes, classOverrides, lookup);
}
/**

View File

@ -33,6 +33,7 @@ import jdk.nashorn.internal.objects.annotations.Attribute;
import jdk.nashorn.internal.objects.annotations.Constructor;
import jdk.nashorn.internal.objects.annotations.Function;
import jdk.nashorn.internal.objects.annotations.ScriptClass;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.NativeJavaPackage;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.ScriptObject;
@ -161,8 +162,9 @@ public final class NativeJavaImporter extends ScriptObject {
} else if (obj instanceof NativeJavaPackage) {
final String pkgName = ((NativeJavaPackage)obj).getName();
final String fullName = pkgName.isEmpty() ? name : (pkgName + "." + name);
final Context context = Global.instance().getContext();
try {
return StaticClass.forClass(Class.forName(fullName));
return StaticClass.forClass(context.findClass(fullName));
} catch (final ClassNotFoundException e) {
// IGNORE
}

View File

@ -37,7 +37,6 @@ import java.io.PrintWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Modifier;
import java.util.concurrent.atomic.AtomicLong;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessControlContext;
@ -48,7 +47,7 @@ import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
@ -651,6 +650,19 @@ public final class Context {
}
}
/**
* Checks that the given package name can be accessed from no permissions context.
*
* @param pkgName package name
* @throw SecurityException if not accessible
*/
public static void checkPackageAccess(final String pkgName) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkPackageAccess(sm, pkgName.endsWith(".")? pkgName : pkgName + ".");
}
}
/**
* Checks that the given package can be accessed from no permissions context.
*

View File

@ -85,6 +85,8 @@ public final class NativeJavaPackage extends ScriptObject {
*/
public NativeJavaPackage(final String name, final ScriptObject proto) {
super(proto, null);
// defense-in-path, check here for sensitive packages
Context.checkPackageAccess(name);
this.name = name;
}

View File

@ -26,14 +26,13 @@
package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.LinkRequest;
@ -524,7 +523,11 @@ public abstract class ScriptFunction extends ScriptObject {
}
} else {
final MethodHandle callHandle = getBestInvoker(type.dropParameterTypes(0, 1), request.getArguments());
if (scopeCall) {
if (data.isBuiltin() && "extend".equals(data.getName())) {
// NOTE: the only built-in named "extend" is NativeJava.extend. As a special-case we're binding the
// current lookup as its "this" so it can do security-sensitive creation of adapter classes.
boundHandle = MH.dropArguments(MH.bindTo(callHandle, desc.getLookup()), 0, Object.class, Object.class);
} else if (scopeCall) {
// Make a handle that drops the passed "this" argument and substitutes either Global or Undefined
// (this, args...) => (args...)
boundHandle = MH.bindTo(callHandle, needsWrappedThis() ? Context.getGlobalTrusted() : ScriptRuntime.UNDEFINED);

View File

@ -47,7 +47,8 @@ final class AdaptationResult {
ERROR_NON_PUBLIC_CLASS,
ERROR_NO_ACCESSIBLE_CONSTRUCTOR,
ERROR_MULTIPLE_SUPERCLASSES,
ERROR_NO_COMMON_LOADER
ERROR_NO_COMMON_LOADER,
ERROR_FINAL_FINALIZER
}
static final AdaptationResult SUCCESSFUL_RESULT = new AdaptationResult(Outcome.SUCCESS, "");

View File

@ -32,6 +32,7 @@ import java.util.HashMap;
import java.util.Map;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.GuardedTypeConversion;
import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.linker.LinkerServices;
@ -79,7 +80,7 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTy
}
@Override
public GuardedInvocation convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
public GuardedTypeConversion convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
final boolean sourceIsAlwaysJSObject = JSObject.class.isAssignableFrom(sourceType);
if(!sourceIsAlwaysJSObject && !sourceType.isAssignableFrom(JSObject.class)) {
return null;
@ -90,7 +91,7 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTy
return null;
}
return new GuardedInvocation(converter, sourceIsAlwaysJSObject ? null : IS_JSOBJECT_GUARD).asType(MethodType.methodType(targetType, sourceType));
return new GuardedTypeConversion(new GuardedInvocation(converter, sourceIsAlwaysJSObject ? null : IS_JSOBJECT_GUARD).asType(MethodType.methodType(targetType, sourceType)), true);
}

View File

@ -59,6 +59,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
@ -66,21 +67,23 @@ import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome;
import sun.reflect.CallerSensitive;
/**
* Generates bytecode for a Java adapter class. Used by the {@link JavaAdapterFactory}.
* </p><p>
* For every protected or public constructor in the extended class, the adapter class will have between one to three
* For every protected or public constructor in the extended class, the adapter class will have either one or two
* public constructors (visibility of protected constructors in the extended class is promoted to public).
* <ul>
* <li>In every case, a constructor taking a trailing ScriptObject argument preceded by original constructor arguments
* is always created on the adapter class. When such a constructor is invoked, the passed ScriptObject's member
* functions are used to implement and/or override methods on the original class, dispatched by name. A single
* JavaScript function will act as the implementation for all overloaded methods of the same name. When methods on an
* adapter instance are invoked, the functions are invoked having the ScriptObject passed in the instance constructor as
* their "this". Subsequent changes to the ScriptObject (reassignment or removal of its functions) are not reflected in
* the adapter instance; the method implementations are bound to functions at constructor invocation time.
* <li>
* <li>For adapter classes with instance-level overrides, a constructor taking a trailing ScriptObject argument preceded
* by original constructor arguments is always created on the adapter class. When such a constructor is invoked, the
* passed ScriptObject's member functions are used to implement and/or override methods on the original class,
* dispatched by name. A single JavaScript function will act as the implementation for all overloaded methods of the
* same name. When methods on an adapter instance are invoked, the functions are invoked having the ScriptObject passed
* in the instance constructor as their "this". Subsequent changes to the ScriptObject (reassignment or removal of its
* functions) are not reflected in the adapter instance; the method implementations are bound to functions at
* constructor invocation time.
* {@code java.lang.Object} methods {@code equals}, {@code hashCode}, and {@code toString} can also be overridden. The
* only restriction is that since every JavaScript object already has a {@code toString} function through the
* {@code Object.prototype}, the {@code toString} in the adapter is only overridden if the passed ScriptObject has a
@ -89,16 +92,17 @@ import sun.reflect.CallerSensitive;
* </li>
* <li>
* If the original types collectively have only one abstract method, or have several of them, but all share the
* same name, an additional constructor is provided for every original constructor; this one takes a ScriptFunction as
* its last argument preceded by original constructor arguments. This constructor will use the passed function as the
* implementation for all abstract methods. For consistency, any concrete methods sharing the single abstract method
* name will also be overridden by the function. When methods on the adapter instance are invoked, the ScriptFunction is
* invoked with global or UNDEFINED as its "this" depending whether the function is non-strict or not.
* same name, an additional constructor for instance-level override adapter is provided for every original constructor;
* this one takes a ScriptFunction as its last argument preceded by original constructor arguments. This constructor
* will use the passed function as the implementation for all abstract methods. For consistency, any concrete methods
* sharing the single abstract method name will also be overridden by the function. When methods on the adapter instance
* are invoked, the ScriptFunction is invoked with UNDEFINED or Global as its "this" depending whether the function is
* strict or not.
* </li>
* <li>
* If the adapter being generated can have class-level overrides, constructors taking same arguments as the superclass
* constructors are also created. These constructors simply delegate to the superclass constructor. They are used to
* create instances of the adapter class with no instance-level overrides.
* constructors are created. These constructors simply delegate to the superclass constructor. They are simply used to
* create instances of the adapter class, with no instance-level overrides, as they don't have them.
* </li>
* </ul>
* </p><p>
@ -111,16 +115,20 @@ import sun.reflect.CallerSensitive;
* source-level script expression <code>new X(a, b) { ... }</code> (which is a proprietary syntax extension Nashorn uses
* to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>.
* </p><p>
* It is possible to create two different classes: those that can have both class-level and instance-level overrides,
* and those that can only have instance-level overrides. When
* {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject)} is invoked with non-null {@code classOverrides}
* parameter, an adapter class is created that can have class-level overrides, and the passed script object will be used
* as the implementations for its methods, just as in the above case of the constructor taking a script object. Note
* that in the case of class-level overrides, a new adapter class is created on every invocation, and the implementation
* object is bound to the class, not to any instance. All created instances will share these functions. Of course, when
* instances of such a class are being created, they can still take another object (or possibly a function) in their
* constructor's trailing position and thus provide further instance-specific overrides. The order of invocation is
* always instance-specified method, then a class-specified method, and finally the superclass method.
* It is possible to create two different adapter classes: those that can have class-level overrides, and those that can
* have instance-level overrides. When {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject)} is invoked
* with non-null {@code classOverrides} parameter, an adapter class is created that can have class-level overrides, and
* the passed script object will be used as the implementations for its methods, just as in the above case of the
* constructor taking a script object. Note that in the case of class-level overrides, a new adapter class is created on
* every invocation, and the implementation object is bound to the class, not to any instance. All created instances
* will share these functions. If it is required to have both class-level overrides and instance-level overrides, the
* class-level override adapter class should be subclassed with an instance-override adapter. Since adapters delegate to
* super class when an overriding method handle is not specified, this will behave as expected. It is not possible to
* have both class-level and instance-level overrides in the same class for security reasons: adapter classes are
* defined with a protection domain of their creator code, and an adapter class that has both class and instance level
* overrides would need to have two potentially different protection domains: one for class-based behavior and one for
* instance-based behavior; since Java classes can only belong to a single protection domain, this could not be
* implemented securely.
*/
final class JavaAdapterBytecodeGenerator {
static final Type CONTEXT_TYPE = Type.getType(Context.class);
@ -171,7 +179,6 @@ final class JavaAdapterBytecodeGenerator {
private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 255;
private static final String CLASS_INIT = "<clinit>";
private static final String STATIC_GLOBAL_FIELD_NAME = "staticGlobal";
// Method name prefix for invoking super-methods
static final String SUPER_PREFIX = "super$";
@ -199,6 +206,7 @@ final class JavaAdapterBytecodeGenerator {
private final Set<MethodInfo> finalMethods = new HashSet<>(EXCLUDED);
private final Set<MethodInfo> methodInfos = new HashSet<>();
private boolean autoConvertibleFromFunction = false;
private boolean hasExplicitFinalizer = false;
private final ClassWriter cw;
@ -207,8 +215,8 @@ final class JavaAdapterBytecodeGenerator {
* @param superClass the superclass the adapter will extend.
* @param interfaces the interfaces the adapter will implement.
* @param commonLoader the class loader that can see all of superClass, interfaces, and Nashorn classes.
* @param classOverride true to generate the bytecode for the adapter that has both class-level and instance-level
* overrides, false to generate the bytecode for the adapter that only has instance-level overrides.
* @param classOverride true to generate the bytecode for the adapter that has class-level overrides, false to
* generate the bytecode for the adapter that has instance-level overrides.
* @throws AdaptationException if the adapter can not be generated for some reason.
*/
JavaAdapterBytecodeGenerator(final Class<?> superClass, final List<Class<?>> interfaces,
@ -230,8 +238,7 @@ final class JavaAdapterBytecodeGenerator {
superClassName = Type.getInternalName(superClass);
generatedClassName = getGeneratedClassName(superClass, interfaces);
cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, generatedClassName, null, superClassName, getInternalTypeNames(interfaces));
cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER, generatedClassName, null, superClassName, getInternalTypeNames(interfaces));
generateGlobalFields();
gatherMethods(superClass);
@ -244,17 +251,16 @@ final class JavaAdapterBytecodeGenerator {
generateConstructors();
generateMethods();
generateSuperMethods();
if (hasExplicitFinalizer) {
generateFinalizerMethods();
}
// }
cw.visitEnd();
}
private void generateGlobalFields() {
cw.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd();
cw.visitField(ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0), GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd();
usedFieldNames.add(GLOBAL_FIELD_NAME);
if(classOverride) {
cw.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd();
usedFieldNames.add(STATIC_GLOBAL_FIELD_NAME);
}
}
JavaAdapterClassLoader createAdapterClassLoader() {
@ -305,11 +311,9 @@ final class JavaAdapterBytecodeGenerator {
}
private void generateHandleFields() {
final int flags = ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0);
for (final MethodInfo mi: methodInfos) {
cw.visitField(ACC_PRIVATE | ACC_FINAL, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd();
if(classOverride) {
cw.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd();
}
cw.visitField(flags, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd();
}
}
@ -337,7 +341,7 @@ final class JavaAdapterBytecodeGenerator {
} else {
mv.visitInsn(ACONST_NULL);
}
mv.putstatic(generatedClassName, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
mv.putstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
}
initGlobal = new Label();
mv.goTo(initGlobal);
@ -351,15 +355,15 @@ final class JavaAdapterBytecodeGenerator {
mv.aconst(mi.getName());
mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_OBJECT_DESCRIPTOR, false);
mv.putstatic(generatedClassName, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
mv.putstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
}
if(initGlobal != null) {
mv.visitLabel(initGlobal);
}
// Assign "staticGlobal = Context.getGlobal()"
// Assign "global = Context.getGlobal()"
invokeGetGlobalWithNullCheck(mv);
mv.putstatic(generatedClassName, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
mv.putstatic(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
endInitMethod(mv);
}
@ -390,21 +394,21 @@ final class JavaAdapterBytecodeGenerator {
// Generate a constructor that just delegates to ctor. This is used with class-level overrides, when we want
// to create instances without further per-instance overrides.
generateDelegatingConstructor(ctor);
}
} else {
// Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the
// beginning of its parameter list.
generateOverridingConstructor(ctor, false);
// Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the
// beginning of its parameter list.
generateOverridingConstructor(ctor, false);
if (samName != null) {
if (!autoConvertibleFromFunction && ctor.getParameterTypes().length == 0) {
// If the original type only has a single abstract method name, as well as a default ctor, then it can
// be automatically converted from JS function.
autoConvertibleFromFunction = true;
if (samName != null) {
if (!autoConvertibleFromFunction && ctor.getParameterTypes().length == 0) {
// If the original type only has a single abstract method name, as well as a default ctor, then it can
// be automatically converted from JS function.
autoConvertibleFromFunction = true;
}
// If all our abstract methods have a single name, generate an additional constructor, one that takes a
// ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
generateOverridingConstructor(ctor, true);
}
// If all our abstract methods have a single name, generate an additional constructor, one that takes a
// ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
generateOverridingConstructor(ctor, true);
}
}
@ -430,7 +434,7 @@ final class JavaAdapterBytecodeGenerator {
}
/**
* Generates a constructor for the adapter class. This constructor will take the same arguments as the supertype
* Generates a constructor for the instance adapter class. This constructor will take the same arguments as the supertype
* constructor passed as the argument here, and delegate to it. However, it will take an additional argument of
* either ScriptObject or ScriptFunction type (based on the value of the "fromFunction" parameter), and initialize
* all the method handle fields of the adapter instance with functions from the script object (or the script
@ -498,7 +502,7 @@ final class JavaAdapterBytecodeGenerator {
mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor, false);
}
mv.putfield(generatedClassName, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
}
// Assign "this.global = Context.getGlobal()"
@ -536,8 +540,7 @@ final class JavaAdapterBytecodeGenerator {
private static class MethodInfo {
private final Method method;
private final MethodType type;
private String methodHandleInstanceFieldName;
private String methodHandleClassFieldName;
private String methodHandleFieldName;
private MethodInfo(final Class<?> clazz, final String name, final Class<?>... argTypes) throws NoSuchMethodException {
this(clazz.getDeclaredMethod(name, argTypes));
@ -567,25 +570,20 @@ final class JavaAdapterBytecodeGenerator {
return getName().hashCode() ^ type.hashCode();
}
void setIsCanonical(final Set<String> usedFieldNames, boolean classOverride) {
methodHandleInstanceFieldName = nextName(usedFieldNames);
if(classOverride) {
methodHandleClassFieldName = nextName(usedFieldNames);
}
void setIsCanonical(final JavaAdapterBytecodeGenerator self) {
methodHandleFieldName = self.nextName(getName());
}
}
String nextName(final Set<String> usedFieldNames) {
int i = 0;
final String name = getName();
String nextName = name;
while (!usedFieldNames.add(nextName)) {
final String ordinal = String.valueOf(i++);
final int maxNameLen = 255 - ordinal.length();
nextName = (name.length() <= maxNameLen ? name : name.substring(0, maxNameLen)).concat(ordinal);
}
return nextName;
private String nextName(final String name) {
int i = 0;
String nextName = name;
while (!usedFieldNames.add(nextName)) {
final String ordinal = String.valueOf(i++);
final int maxNameLen = 255 - ordinal.length();
nextName = (name.length() <= maxNameLen ? name : name.substring(0, maxNameLen)).concat(ordinal);
}
return nextName;
}
private void generateMethods() {
@ -624,23 +622,19 @@ final class JavaAdapterBytecodeGenerator {
methodDesc, null, exceptionNames));
mv.visitCode();
final Label instanceHandleDefined = new Label();
final Label classHandleDefined = new Label();
final Label handleDefined = new Label();
final Type asmReturnType = Type.getType(type.returnType());
// See if we have instance handle defined
mv.visitVarInsn(ALOAD, 0);
mv.getfield(generatedClassName, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
// stack: [instanceHandle]
jumpIfNonNullKeepOperand(mv, instanceHandleDefined);
// See if we have overriding method handle defined
if(classOverride) {
// See if we have the static handle
mv.getstatic(generatedClassName, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
// stack: [classHandle]
jumpIfNonNullKeepOperand(mv, classHandleDefined);
mv.getstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
} else {
mv.visitVarInsn(ALOAD, 0);
mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
}
// stack: [handle]
jumpIfNonNullKeepOperand(mv, handleDefined);
// No handle is available, fall back to default behavior
if(Modifier.isAbstract(method.getModifiers())) {
@ -654,25 +648,17 @@ final class JavaAdapterBytecodeGenerator {
emitSuperCall(mv, method.getDeclaringClass(), name, methodDesc);
}
final Label setupGlobal = new Label();
mv.visitLabel(handleDefined);
// Load the creatingGlobal object
if(classOverride) {
mv.visitLabel(classHandleDefined);
// If class handle is defined, load the static defining global
mv.getstatic(generatedClassName, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
// stack: [creatingGlobal := classGlobal, classHandle]
mv.goTo(setupGlobal);
mv.getstatic(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
} else {
mv.visitVarInsn(ALOAD, 0);
mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
}
mv.visitLabel(instanceHandleDefined);
// If instance handle is defined, load the instance defining global
mv.visitVarInsn(ALOAD, 0);
mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
// stack: [creatingGlobal := instanceGlobal, instanceHandle]
// fallthrough to setupGlobal
// stack: [creatingGlobal, someHandle]
// stack: [creatingGlobal, handle]
final Label setupGlobal = new Label();
mv.visitLabel(setupGlobal);
// Determine the first index for a local variable
@ -685,38 +671,39 @@ final class JavaAdapterBytecodeGenerator {
final int globalsDifferVar = nextLocalVar++;
mv.dup();
// stack: [creatingGlobal, creatingGlobal, someHandle]
// stack: [creatingGlobal, creatingGlobal, handle]
// Emit code for switching to the creating global
// ScriptObject currentGlobal = Context.getGlobal();
invokeGetGlobal(mv);
mv.dup();
mv.visitVarInsn(ASTORE, currentGlobalVar);
// stack: [currentGlobal, creatingGlobal, creatingGlobal, someHandle]
// stack: [currentGlobal, creatingGlobal, creatingGlobal, handle]
// if(definingGlobal == currentGlobal) {
final Label globalsDiffer = new Label();
mv.ifacmpne(globalsDiffer);
// stack: [someGlobal, someHandle]
// stack: [creatingGlobal, handle]
// globalsDiffer = false
mv.pop();
// stack: [someHandle]
// stack: [handle]
mv.iconst(0); // false
// stack: [false, someHandle]
// stack: [false, handle]
final Label invokeHandle = new Label();
mv.goTo(invokeHandle);
mv.visitLabel(globalsDiffer);
// } else {
// Context.setGlobal(definingGlobal);
// stack: [someGlobal, someHandle]
// stack: [creatingGlobal, handle]
invokeSetGlobal(mv);
// stack: [someHandle]
// stack: [handle]
// globalsDiffer = true
mv.iconst(1);
// stack: [true, someHandle]
// stack: [true, handle]
mv.visitLabel(invokeHandle);
mv.visitVarInsn(ISTORE, globalsDifferVar);
// stack: [someHandle]
// stack: [handle]
// Load all parameters back on stack for dynamic invocation.
int varOffset = 1;
@ -835,7 +822,7 @@ final class JavaAdapterBytecodeGenerator {
endMethod(mv);
}
private void emitSuperCall(final InstructionAdapter mv, final Class owner, final String name, final String methodDesc) {
private void emitSuperCall(final InstructionAdapter mv, final Class<?> owner, final String name, final String methodDesc) {
mv.visitVarInsn(ALOAD, 0);
int nextParam = 1;
final Type methodType = Type.getMethodType(methodDesc);
@ -853,6 +840,42 @@ final class JavaAdapterBytecodeGenerator {
mv.areturn(methodType.getReturnType());
}
private void generateFinalizerMethods() {
final String finalizerDelegateName = nextName("access$");
generateFinalizerDelegate(finalizerDelegateName);
generateFinalizerOverride(finalizerDelegateName);
}
private void generateFinalizerDelegate(final String finalizerDelegateName) {
// Generate a delegate that will be invoked from the no-permission trampoline. Note it can be private, as we'll
// refer to it with a MethodHandle constant pool entry in the overridden finalize() method (see
// generateFinalizerOverride()).
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PRIVATE | ACC_STATIC,
finalizerDelegateName, Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE), null, null));
// Simply invoke super.finalize()
mv.visitVarInsn(ALOAD, 0);
mv.checkcast(Type.getType(generatedClassName));
mv.invokespecial(superClassName, "finalize", Type.getMethodDescriptor(Type.VOID_TYPE), false);
mv.visitInsn(RETURN);
endMethod(mv);
}
private void generateFinalizerOverride(final String finalizerDelegateName) {
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, "finalize",
VOID_NOARG_METHOD_DESCRIPTOR, null, null));
// Overridden finalizer will take a MethodHandle to the finalizer delegating method, ...
mv.aconst(new Handle(Opcodes.H_INVOKESTATIC, generatedClassName, finalizerDelegateName,
Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE)));
mv.visitVarInsn(ALOAD, 0);
// ...and invoke it through JavaAdapterServices.invokeNoPermissions
mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "invokeNoPermissions",
Type.getMethodDescriptor(METHOD_HANDLE_TYPE, OBJECT_TYPE), false);
mv.visitInsn(RETURN);
endMethod(mv);
}
private static String[] getExceptionNames(final Class<?>[] exceptions) {
final String[] exceptionNames = new String[exceptions.length];
for (int i = 0; i < exceptions.length; ++i) {
@ -873,16 +896,32 @@ final class JavaAdapterBytecodeGenerator {
* class.
* @param type the type defining the methods.
*/
private void gatherMethods(final Class<?> type) {
private void gatherMethods(final Class<?> type) throws AdaptationException {
if (Modifier.isPublic(type.getModifiers())) {
final Method[] typeMethods = type.isInterface() ? type.getMethods() : type.getDeclaredMethods();
for (final Method typeMethod: typeMethods) {
final String name = typeMethod.getName();
if(name.startsWith(SUPER_PREFIX)) {
continue;
}
final int m = typeMethod.getModifiers();
if (Modifier.isStatic(m)) {
continue;
}
if (Modifier.isPublic(m) || Modifier.isProtected(m)) {
// Is it a "finalize()"?
if(name.equals("finalize") && typeMethod.getParameterCount() == 0) {
if(type != Object.class) {
hasExplicitFinalizer = true;
if(Modifier.isFinal(m)) {
// Must be able to override an explicit finalizer
throw new AdaptationException(Outcome.ERROR_FINAL_FINALIZER, type.getCanonicalName());
}
}
continue;
}
final MethodInfo mi = new MethodInfo(typeMethod);
if (Modifier.isFinal(m) || isCallerSensitive(typeMethod)) {
finalMethods.add(mi);
@ -890,7 +929,7 @@ final class JavaAdapterBytecodeGenerator {
if (Modifier.isAbstract(m)) {
abstractMethodNames.add(mi.getName());
}
mi.setIsCanonical(usedFieldNames, classOverride);
mi.setIsCanonical(this);
}
}
}
@ -911,7 +950,7 @@ final class JavaAdapterBytecodeGenerator {
}
}
private void gatherMethods(final List<Class<?>> classes) {
private void gatherMethods(final List<Class<?>> classes) throws AdaptationException {
for(final Class<?> c: classes) {
gatherMethods(c);
}

View File

@ -27,10 +27,6 @@ package jdk.nashorn.internal.runtime.linker;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
@ -45,35 +41,29 @@ import jdk.internal.dynalink.beans.StaticClass;
*/
@SuppressWarnings("javadoc")
final class JavaAdapterClassLoader {
private static final ProtectionDomain GENERATED_PROTECTION_DOMAIN = createGeneratedProtectionDomain();
private static final AccessControlContext CREATE_LOADER_ACC_CTXT = ClassAndLoader.createPermAccCtxt("createClassLoader");
private final String className;
private volatile byte[] classBytes;
private final byte[] classBytes;
JavaAdapterClassLoader(String className, byte[] classBytes) {
this.className = className.replace('/', '.');
this.classBytes = classBytes;
}
/**
* clear classBytes after loading class.
*/
void clearClassBytes() {
this.classBytes = null;
}
/**
* Loads the generated adapter class into the JVM.
* @param parentLoader the parent class loader for the generated class loader
* @param protectionDomain the protection domain for the generated class
* @return the generated adapter class
*/
StaticClass generateClass(final ClassLoader parentLoader) {
StaticClass generateClass(final ClassLoader parentLoader, final ProtectionDomain protectionDomain) {
assert protectionDomain != null;
return AccessController.doPrivileged(new PrivilegedAction<StaticClass>() {
@Override
public StaticClass run() {
try {
return StaticClass.forClass(Class.forName(className, true, createClassLoader(parentLoader)));
return StaticClass.forClass(Class.forName(className, true, createClassLoader(parentLoader, protectionDomain)));
} catch (final ClassNotFoundException e) {
throw new AssertionError(e); // cannot happen
}
@ -88,7 +78,7 @@ final class JavaAdapterClassLoader {
// it even more by separating its invocation into a separate static method on the adapter class, but then someone
// with ability to introspect on the class and use setAccessible(true) on it could invoke the method. It's a
// security tradeoff...
private ClassLoader createClassLoader(final ClassLoader parentLoader) {
private ClassLoader createClassLoader(final ClassLoader parentLoader, final ProtectionDomain protectionDomain) {
return new SecureClassLoader(parentLoader) {
private final ClassLoader myLoader = getClass().getClassLoader();
@ -112,21 +102,10 @@ final class JavaAdapterClassLoader {
protected Class<?> findClass(final String name) throws ClassNotFoundException {
if(name.equals(className)) {
assert classBytes != null : "what? already cleared .class bytes!!";
return defineClass(name, classBytes, 0, classBytes.length, GENERATED_PROTECTION_DOMAIN);
return defineClass(name, classBytes, 0, classBytes.length, protectionDomain);
}
throw new ClassNotFoundException(name);
}
};
}
private static ProtectionDomain createGeneratedProtectionDomain() {
// Generated classes need to have AllPermission. Since we require the "createClassLoader" RuntimePermission, we
// can create a class loader that'll load new classes with any permissions. Our generated classes are just
// delegating adapters, so having AllPermission can't cause anything wrong; the effective set of permissions for
// the executing script functions will still be limited by the permissions of the caller and the permissions of
// the script.
final Permissions permissions = new Permissions();
permissions.add(new AllPermission());
return new ProtectionDomain(new CodeSource(null, (CodeSigner[])null), permissions);
}
}

View File

@ -29,17 +29,23 @@ import static jdk.nashorn.internal.lookup.Lookup.MH;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import jdk.internal.dynalink.beans.StaticClass;
import jdk.internal.dynalink.support.LinkRequestImpl;
import jdk.nashorn.internal.objects.NativeJava;
@ -70,6 +76,8 @@ import jdk.nashorn.internal.runtime.ScriptObject;
@SuppressWarnings("javadoc")
public final class JavaAdapterFactory {
private static final ProtectionDomain MINIMAL_PERMISSION_DOMAIN = createMinimalPermissionDomain();
// context with permissions needs for AdapterInfo creation
private static final AccessControlContext CREATE_ADAPTER_INFO_ACC_CTXT =
ClassAndLoader.createPermAccCtxt("createClassLoader", "getClassLoader",
@ -99,11 +107,18 @@ public final class JavaAdapterFactory {
* @param classOverrides a JavaScript object with functions serving as the class-level overrides and
* implementations. These overrides are defined for all instances of the class, and can be further overridden on a
* per-instance basis by passing additional objects in the constructor.
* @param lookup the lookup object identifying the caller class. The generated adapter class will have the
* protection domain of the caller class iff the lookup object is full-strength, otherwise it will be completely
* unprivileged.
* @return an adapter class. See this class' documentation for details on the generated adapter class.
* @throws ECMAException with a TypeError if the adapter class can not be generated because the original class is
* final, non-public, or has no public or protected constructors.
*/
public static StaticClass getAdapterClassFor(final Class<?>[] types, ScriptObject classOverrides) {
public static StaticClass getAdapterClassFor(final Class<?>[] types, ScriptObject classOverrides, final MethodHandles.Lookup lookup) {
return getAdapterClassFor(types, classOverrides, getProtectionDomain(lookup));
}
private static StaticClass getAdapterClassFor(final Class<?>[] types, ScriptObject classOverrides, final ProtectionDomain protectionDomain) {
assert types != null && types.length > 0;
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
@ -114,7 +129,23 @@ public final class JavaAdapterFactory {
ReflectionCheckLinker.checkReflectionAccess(type, true);
}
}
return getAdapterInfo(types).getAdapterClassFor(classOverrides);
return getAdapterInfo(types).getAdapterClass(classOverrides, protectionDomain);
}
private static ProtectionDomain getProtectionDomain(final MethodHandles.Lookup lookup) {
if((lookup.lookupModes() & Lookup.PRIVATE) == 0) {
return MINIMAL_PERMISSION_DOMAIN;
}
return getProtectionDomain(lookup.lookupClass());
}
private static ProtectionDomain getProtectionDomain(final Class<?> clazz) {
return AccessController.doPrivileged(new PrivilegedAction<ProtectionDomain>() {
@Override
public ProtectionDomain run() {
return clazz.getProtectionDomain();
}
});
}
/**
@ -129,10 +160,10 @@ public final class JavaAdapterFactory {
* @return the constructor method handle.
* @throws Exception if anything goes wrong
*/
public static MethodHandle getConstructor(final Class<?> sourceType, final Class<?> targetType) throws Exception {
final StaticClass adapterClass = getAdapterClassFor(new Class<?>[] { targetType }, null);
public static MethodHandle getConstructor(final Class<?> sourceType, final Class<?> targetType, final MethodHandles.Lookup lookup) throws Exception {
final StaticClass adapterClass = getAdapterClassFor(new Class<?>[] { targetType }, null, lookup);
return MH.bindTo(Bootstrap.getLinkerServices().getGuardedInvocation(new LinkRequestImpl(
NashornCallSiteDescriptor.get(MethodHandles.publicLookup(), "dyn:new",
NashornCallSiteDescriptor.get(lookup, "dyn:new",
MethodType.methodType(targetType, StaticClass.class, sourceType), 0), false,
adapterClass, null)).getInvocation(), adapterClass);
}
@ -220,10 +251,10 @@ public final class JavaAdapterFactory {
private static final ClassAndLoader SCRIPT_OBJECT_LOADER = new ClassAndLoader(ScriptObject.class, true);
private final ClassLoader commonLoader;
private final JavaAdapterClassLoader adapterGenerator;
// Cacheable adapter class that is shared by all adapter instances that don't have class overrides, only
// instance overrides.
final StaticClass instanceAdapterClass;
// TODO: soft reference the JavaAdapterClassLoader objects. They can be recreated when needed.
private final JavaAdapterClassLoader classAdapterGenerator;
private final JavaAdapterClassLoader instanceAdapterGenerator;
private final Map<CodeSource, StaticClass> instanceAdapters = new ConcurrentHashMap<>();
final boolean autoConvertibleFromFunction;
final AdaptationResult adaptationResult;
@ -231,11 +262,8 @@ public final class JavaAdapterFactory {
this.commonLoader = findCommonLoader(definingLoader);
final JavaAdapterBytecodeGenerator gen = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, false);
this.autoConvertibleFromFunction = gen.isAutoConvertibleFromFunction();
final JavaAdapterClassLoader jacl = gen.createAdapterClassLoader();
this.instanceAdapterClass = jacl.generateClass(commonLoader);
// loaded Class - no need to keep class bytes around
jacl.clearClassBytes();
this.adapterGenerator = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, true).createAdapterClassLoader();
instanceAdapterGenerator = gen.createAdapterClassLoader();
this.classAdapterGenerator = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, true).createAdapterClassLoader();
this.adaptationResult = AdaptationResult.SUCCESSFUL_RESULT;
}
@ -245,22 +273,42 @@ public final class JavaAdapterFactory {
AdapterInfo(final AdaptationResult adaptationResult) {
this.commonLoader = null;
this.adapterGenerator = null;
this.instanceAdapterClass = null;
this.classAdapterGenerator = null;
this.instanceAdapterGenerator = null;
this.autoConvertibleFromFunction = false;
this.adaptationResult = adaptationResult;
}
StaticClass getAdapterClassFor(ScriptObject classOverrides) {
StaticClass getAdapterClass(final ScriptObject classOverrides, final ProtectionDomain protectionDomain) {
if(adaptationResult.getOutcome() != AdaptationResult.Outcome.SUCCESS) {
throw adaptationResult.typeError();
}
if(classOverrides == null) {
return classOverrides == null ? getInstanceAdapterClass(protectionDomain) :
getClassAdapterClass(classOverrides, protectionDomain);
}
private StaticClass getInstanceAdapterClass(final ProtectionDomain protectionDomain) {
CodeSource codeSource = protectionDomain.getCodeSource();
if(codeSource == null) {
codeSource = MINIMAL_PERMISSION_DOMAIN.getCodeSource();
}
StaticClass instanceAdapterClass = instanceAdapters.get(codeSource);
if(instanceAdapterClass != null) {
return instanceAdapterClass;
}
// Any "unknown source" code source will default to no permission domain.
final ProtectionDomain effectiveDomain = codeSource.equals(MINIMAL_PERMISSION_DOMAIN.getCodeSource()) ?
MINIMAL_PERMISSION_DOMAIN : protectionDomain;
instanceAdapterClass = instanceAdapterGenerator.generateClass(commonLoader, effectiveDomain);
final StaticClass existing = instanceAdapters.putIfAbsent(codeSource, instanceAdapterClass);
return existing == null ? instanceAdapterClass : existing;
}
private StaticClass getClassAdapterClass(final ScriptObject classOverrides, final ProtectionDomain protectionDomain) {
JavaAdapterServices.setClassOverrides(classOverrides);
try {
return adapterGenerator.generateClass(commonLoader);
return classAdapterGenerator.generateClass(commonLoader, protectionDomain);
} finally {
JavaAdapterServices.setClassOverrides(null);
}
@ -285,4 +333,12 @@ public final class JavaAdapterFactory {
throw new AdaptationException(AdaptationResult.Outcome.ERROR_NO_COMMON_LOADER, classAndLoader.getRepresentativeClass().getCanonicalName());
}
}
private static ProtectionDomain createMinimalPermissionDomain() {
// Generated classes need to have at least the permission to access Nashorn runtime and runtime.linker packages.
final Permissions permissions = new Permissions();
permissions.add(new RuntimePermission("accessClassInPackage.jdk.nashorn.internal.runtime"));
permissions.add(new RuntimePermission("accessClassInPackage.jdk.nashorn.internal.runtime.linker"));
return new ProtectionDomain(new CodeSource(null, (CodeSigner[])null), permissions);
}
}

View File

@ -25,10 +25,28 @@
package jdk.nashorn.internal.runtime.linker;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.security.AccessController;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
@ -40,6 +58,7 @@ import jdk.nashorn.internal.runtime.Undefined;
*/
public final class JavaAdapterServices {
private static final ThreadLocal<ScriptObject> classOverrides = new ThreadLocal<>();
private static final MethodHandle NO_PERMISSIONS_INVOKER = createNoPermissionsInvoker();
private JavaAdapterServices() {
}
@ -55,7 +74,7 @@ public final class JavaAdapterServices {
*/
public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type) {
// JS "this" will be global object or undefined depending on if 'fn' is strict or not
return adaptHandle(fn.getBoundInvokeHandle(fn.isStrict()? ScriptRuntime.UNDEFINED : Context.getGlobal()), type);
return bindAndAdaptHandle(fn, fn.isStrict()? ScriptRuntime.UNDEFINED : Context.getGlobal(), type);
}
/**
@ -83,7 +102,7 @@ public final class JavaAdapterServices {
final Object fnObj = sobj.get(name);
if (fnObj instanceof ScriptFunction) {
return adaptHandle(((ScriptFunction)fnObj).getBoundInvokeHandle(sobj), type);
return bindAndAdaptHandle((ScriptFunction)fnObj, sobj, type);
} else if(fnObj == null || fnObj instanceof Undefined) {
return null;
} else {
@ -103,11 +122,67 @@ public final class JavaAdapterServices {
return overrides;
}
/**
* Takes a method handle and an argument to it, and invokes the method handle passing it the argument. Basically
* equivalent to {@code method.invokeExact(arg)}, except that the method handle will be invoked in a protection
* domain with absolutely no permissions.
* @param method the method handle to invoke. The handle must have the exact type of {@code void(Object)}.
* @param arg the argument to pass to the handle.
* @throws Throwable if anything goes wrong.
*/
public static void invokeNoPermissions(final MethodHandle method, final Object arg) throws Throwable {
NO_PERMISSIONS_INVOKER.invokeExact(method, arg);
}
static void setClassOverrides(ScriptObject overrides) {
classOverrides.set(overrides);
}
private static MethodHandle adaptHandle(final MethodHandle handle, final MethodType type) {
return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(handle, type, false), type);
private static MethodHandle bindAndAdaptHandle(final ScriptFunction fn, final Object self, final MethodType type) {
return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(fn.getBoundInvokeHandle(self), type, false), type);
}
private static MethodHandle createNoPermissionsInvoker() {
final String className = "NoPermissionsInvoker";
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, className, null, "java/lang/Object", null);
final Type objectType = Type.getType(Object.class);
final Type methodHandleType = Type.getType(MethodHandle.class);
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "invoke",
Type.getMethodDescriptor(Type.VOID_TYPE, methodHandleType, objectType), null, null));
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.invokevirtual(methodHandleType.getInternalName(), "invokeExact", Type.getMethodDescriptor(
Type.VOID_TYPE, objectType), false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
final byte[] bytes = cw.toByteArray();
final ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return new SecureClassLoader(null) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if(name.equals(className)) {
return defineClass(name, bytes, 0, bytes.length, new ProtectionDomain(
new CodeSource(null, (CodeSigner[])null), new Permissions()));
}
throw new ClassNotFoundException(name);
}
};
}
});
try {
return MethodHandles.lookup().findStatic(Class.forName(className, true, loader), "invoke",
MethodType.methodType(void.class, MethodHandle.class, Object.class));
} catch(ReflectiveOperationException e) {
throw new AssertionError(e.getMessage(), e);
}
}
}

View File

@ -25,19 +25,20 @@
package jdk.nashorn.internal.runtime.linker;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.HashMap;
import java.util.Map;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.beans.BeansLinker;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.GuardedTypeConversion;
import jdk.internal.dynalink.linker.GuardingDynamicLinker;
import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
import jdk.internal.dynalink.linker.LinkRequest;
@ -134,9 +135,9 @@ final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeCo
}
@Override
public GuardedInvocation convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
public GuardedTypeConversion convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
final GuardedInvocation gi = convertToTypeNoCast(sourceType, targetType);
return gi == null ? null : gi.asType(MH.type(targetType, sourceType));
return gi == null ? null : new GuardedTypeConversion(gi.asType(MH.type(targetType, sourceType)), true);
}
/**

View File

@ -29,7 +29,10 @@ import static jdk.nashorn.internal.lookup.Lookup.MH;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Deque;
import java.util.List;
import java.util.Map;
@ -37,16 +40,17 @@ import javax.script.Bindings;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.linker.ConversionComparator;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.GuardedTypeConversion;
import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.linker.LinkerServices;
import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
import jdk.internal.dynalink.support.Guards;
import jdk.internal.dynalink.support.LinkerServicesImpl;
import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.api.scripting.ScriptUtils;
import jdk.nashorn.internal.objects.NativeArray;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
@ -100,9 +104,16 @@ final class NashornLinker implements TypeBasedGuardingDynamicLinker, GuardingTyp
}
@Override
public GuardedInvocation convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
final GuardedInvocation gi = convertToTypeNoCast(sourceType, targetType);
return gi == null ? null : gi.asType(MH.type(targetType, sourceType));
public GuardedTypeConversion convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
GuardedInvocation gi = convertToTypeNoCast(sourceType, targetType);
if(gi != null) {
return new GuardedTypeConversion(gi.asType(MH.type(targetType, sourceType)), true);
}
gi = getSamTypeConverter(sourceType, targetType);
if(gi != null) {
return new GuardedTypeConversion(gi.asType(MH.type(targetType, sourceType)), false);
}
return null;
}
/**
@ -126,12 +137,7 @@ final class NashornLinker implements TypeBasedGuardingDynamicLinker, GuardingTyp
return arrayConverter;
}
final GuardedInvocation mirrorConverter = getMirrorConverter(sourceType, targetType);
if(mirrorConverter != null) {
return mirrorConverter;
}
return getSamTypeConverter(sourceType, targetType);
return getMirrorConverter(sourceType, targetType);
}
/**
@ -150,13 +156,23 @@ final class NashornLinker implements TypeBasedGuardingDynamicLinker, GuardingTyp
final boolean isSourceTypeGeneric = sourceType.isAssignableFrom(ScriptFunction.class);
if ((isSourceTypeGeneric || ScriptFunction.class.isAssignableFrom(sourceType)) && isAutoConvertibleFromFunction(targetType)) {
final MethodHandle ctor = JavaAdapterFactory.getConstructor(ScriptFunction.class, targetType);
final MethodHandle ctor = JavaAdapterFactory.getConstructor(ScriptFunction.class, targetType, getCurrentLookup());
assert ctor != null; // if isAutoConvertibleFromFunction() returned true, then ctor must exist.
return new GuardedInvocation(ctor, isSourceTypeGeneric ? IS_SCRIPT_FUNCTION : null);
}
return null;
}
private static Lookup getCurrentLookup() {
final LinkRequest currentRequest = AccessController.doPrivileged(new PrivilegedAction<LinkRequest>() {
@Override
public LinkRequest run() {
return LinkerServicesImpl.getCurrentLinkRequest();
}
});
return currentRequest == null ? MethodHandles.publicLookup() : currentRequest.getCallSiteDescriptor().getLookup();
}
/**
* Returns a guarded invocation that converts from a source type that is NativeArray to a Java array or List or
* Deque type.

View File

@ -31,6 +31,7 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import jdk.internal.dynalink.linker.ConversionComparator;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.GuardedTypeConversion;
import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.linker.LinkerServices;
@ -75,13 +76,13 @@ final class NashornPrimitiveLinker implements TypeBasedGuardingDynamicLinker, Gu
* @return a conditional converter from source to target type
*/
@Override
public GuardedInvocation convertToType(final Class<?> sourceType, final Class<?> targetType) {
public GuardedTypeConversion convertToType(final Class<?> sourceType, final Class<?> targetType) {
final MethodHandle mh = JavaArgumentConverters.getConverter(targetType);
if (mh == null) {
return null;
}
return new GuardedInvocation(mh, canLinkTypeStatic(sourceType) ? null : GUARD_PRIMITIVE).asType(mh.type().changeParameterType(0, sourceType));
return new GuardedTypeConversion(new GuardedInvocation(mh, canLinkTypeStatic(sourceType) ? null : GUARD_PRIMITIVE).asType(mh.type().changeParameterType(0, sourceType)), true);
}
/**

View File

@ -76,7 +76,8 @@ final class NashornStaticClassLinker implements TypeBasedGuardingDynamicLinker {
if (NashornLinker.isAbstractClass(receiverClass)) {
// Change this link request into a link request on the adapter class.
final Object[] args = request.getArguments();
args[0] = JavaAdapterFactory.getAdapterClassFor(new Class<?>[] { receiverClass }, null);
args[0] = JavaAdapterFactory.getAdapterClassFor(new Class<?>[] { receiverClass }, null,
linkRequest.getCallSiteDescriptor().getLookup());
final LinkRequest adapterRequest = request.replaceArguments(request.getCallSiteDescriptor(), args);
final GuardedInvocation gi = checkNullConstructor(delegate(linkerServices, adapterRequest), receiverClass);
// Finally, modify the guard to test for the original abstract class.

View File

@ -130,6 +130,7 @@ type.error.extend.ERROR_NON_PUBLIC_CLASS=Can not extend/implement non-public cla
type.error.extend.ERROR_NO_ACCESSIBLE_CONSTRUCTOR=Can not extend class {0} as it has no public or protected constructors.
type.error.extend.ERROR_MULTIPLE_SUPERCLASSES=Can not extend multiple classes {0}. At most one of the specified types can be a class, the rest must all be interfaces.
type.error.extend.ERROR_NO_COMMON_LOADER=Can not find a common class loader for ScriptObject and {0}.
type.error.extend.ERROR_FINAL_FINALIZER=Can not extend class because {0} has a final finalize method.
type.error.no.constructor.matches.args=Can not construct {0} with the passed arguments; they do not match any of its constructor signatures.
type.error.no.method.matches.args=Can not invoke method {0} with the passed arguments; they do not match any of its method signatures.
type.error.method.not.constructor=Java method {0} can't be used as a constructor.

View File

@ -32,9 +32,10 @@ var RunnableImpl1 = Java.extend(java.lang.Runnable, function() { print("I'm runn
var RunnableImpl2 = Java.extend(java.lang.Runnable, function() { print("I'm runnable 2!") })
var r1 = new RunnableImpl1()
var r2 = new RunnableImpl2()
var r3 = new RunnableImpl2(function() { print("I'm runnable 3!") })
var RunnableImpl3 = Java.extend(RunnableImpl2);
var r3 = new RunnableImpl3({ run: function() { print("I'm runnable 3!") }})
r1.run()
r2.run()
r3.run()
print("r1.class === r2.class: " + (r1.class === r2.class))
print("r2.class === r3.class: " + (r2.class === r3.class))
print("r1.class !== r2.class: " + (r1.class !== r2.class))
print("r2.class !== r3.class: " + (r2.class !== r3.class))

View File

@ -1,5 +1,5 @@
I'm runnable 1!
I'm runnable 2!
I'm runnable 3!
r1.class === r2.class: false
r2.class === r3.class: true
r1.class !== r2.class: true
r2.class !== r3.class: true

View File

@ -46,7 +46,8 @@ var R2 = Java.extend(java.lang.Runnable, {
var r1 = new R1
var r2 = new R2
// Create one with an instance-override too
var r3 = new R2(function() { print("r3.run() invoked") })
var R3 = Java.extend(R2)
var r3 = new R3({ run: function() { print("r3.run() invoked") }})
// Run 'em - we're passing them through a Thread to make sure they indeed
// are full-blown Runnables
@ -60,9 +61,9 @@ runInThread(r2)
runInThread(r3)
// Two class-override classes differ
print("r1.class != r2.class: " + (r1.class != r2.class))
// However, adding instance-overrides doesn't change the class
print("r2.class == r3.class: " + (r2.class == r3.class))
print("r1.class !== r2.class: " + (r1.class !== r2.class))
// instance-override class also differs
print("r2.class !== r3.class: " + (r2.class !== r3.class))
function checkAbstract(r) {
try {
@ -77,10 +78,10 @@ function checkAbstract(r) {
// overrides nor instance overrides are present
var RAbstract = Java.extend(java.lang.Runnable, {})
checkAbstract(new RAbstract()) // class override (empty)
checkAbstract(new RAbstract() {}) // class+instance override (empty)
checkAbstract(new (Java.extend(RAbstract))() {}) // class+instance override (empty)
// Check we delegate to superclass if neither class
// overrides nor instance overrides are present
var ExtendsList = Java.extend(java.util.ArrayList, {})
print("(new ExtendsList).size() = " + (new ExtendsList).size())
print("(new ExtendsList(){}).size() = " + (new ExtendsList(){}).size())
print("(new (Java.extend(ExtendsList)){}).size() = " + (new (Java.extend(ExtendsList)){}).size())

View File

@ -1,9 +1,9 @@
R1.run() invoked
R2.run() invoked
r3.run() invoked
r1.class != r2.class: true
r2.class == r3.class: true
r1.class !== r2.class: true
r2.class !== r3.class: true
Got exception: java.lang.UnsupportedOperationException
Got exception: java.lang.UnsupportedOperationException
(new ExtendsList).size() = 0
(new ExtendsList(){}).size() = 0
(new (Java.extend(ExtendsList)){}).size() = 0

View File

@ -51,6 +51,21 @@ try {
print(e)
}
// Can't extend a class with explicit non-overridable finalizer
try {
Java.extend(model("ClassWithFinalFinalizer"))
} catch(e) {
print(e)
}
// Can't extend a class with inherited non-overridable finalizer
try {
Java.extend(model("ClassWithInheritedFinalFinalizer"))
} catch(e) {
print(e)
}
// Can't extend two classes
try {
Java.extend(java.lang.Thread,java.lang.Number)

View File

@ -1,6 +1,8 @@
TypeError: Can not extend final class jdk.nashorn.test.models.FinalClass.
TypeError: Can not extend class jdk.nashorn.test.models.NoAccessibleConstructorClass as it has no public or protected constructors.
TypeError: Can not extend/implement non-public class/interface jdk.nashorn.test.models.NonPublicClass.
TypeError: Can not extend class because jdk.nashorn.test.models.ClassWithFinalFinalizer has a final finalize method.
TypeError: Can not extend class because jdk.nashorn.test.models.ClassWithFinalFinalizer has a final finalize method.
TypeError: Can not extend multiple classes java.lang.Number and java.lang.Thread. At most one of the specified types can be a class, the rest must all be interfaces.
abcdabcd
run-object

View File

@ -33,8 +33,8 @@ import java.lang.reflect.Proxy;
import java.util.Objects;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.testng.annotations.Test;
/**
@ -130,6 +130,23 @@ public class ScriptEngineSecurityTest {
}
}
@Test
public void securitySystemExitFromFinalizerThread() throws ScriptException {
if (System.getSecurityManager() == null) {
// pass vacuously
return;
}
final ScriptEngineManager m = new ScriptEngineManager();
final ScriptEngine e = m.getEngineByName("nashorn");
e.eval("var o = Java.extend(Java.type('javax.imageio.spi.ServiceRegistry'), { deregisterAll: this.exit.bind(null, 1234)});\n" +
"new o(new java.util.ArrayList().iterator())");
System.gc();
System.runFinalization();
// NOTE: this test just exits the VM if it fails.
}
@Test
public void securitySystemLoadLibrary() {
if (System.getSecurityManager() == null) {

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2010, 2013, 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 jdk.nashorn.test.models;
public class ClassWithFinalFinalizer {
protected final void finalize() {
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2010, 2013, 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 jdk.nashorn.test.models;
public class ClassWithInheritedFinalFinalizer extends ClassWithFinalFinalizer {
}