8072596: Arrays.asList results in ClassCastException with a JS array

Reviewed-by: lagergren, sundar
This commit is contained in:
Attila Szegedi 2015-02-12 16:43:33 +01:00
parent d1c194480f
commit 8d084dc298
15 changed files with 584 additions and 125 deletions

View File

@ -40,7 +40,6 @@
var Arrays = Java.type("java.util.Arrays");
var BufferedReader = Java.type("java.io.BufferedReader");
var FileWriter = Java.type("java.io.FileWriter");
var List = Java.type("java.util.List");
var LocalDateTime = Java.type("java.time.LocalDateTime");
var InputStreamReader = Java.type("java.io.InputStreamReader");
var PrintWriter = Java.type("java.io.PrintWriter");
@ -122,7 +121,7 @@ EOF
// execute code command
function exec(args) {
// build child process and start it!
new ProcessBuilder(Java.to(args.split(' '), List))
new ProcessBuilder(Arrays.asList(args.split(' ')))
.inheritIO()
.start()
.waitFor();

View File

@ -42,7 +42,6 @@
var Arrays = Java.type("java.util.Arrays");
var BufferedReader = Java.type("java.io.BufferedReader");
var InputStreamReader = Java.type("java.io.InputStreamReader");
var List = Java.type("java.util.List");
var ProcessBuilder = Java.type("java.lang.ProcessBuilder");
var System = Java.type("java.lang.System");
@ -67,7 +66,7 @@
}
} else {
// build child process and start it!
new ProcessBuilder(Java.to(args, List))
new ProcessBuilder(Arrays.asList(args))
.inheritIO()
.start()
.waitFor();

View File

@ -97,6 +97,8 @@ import jdk.internal.dynalink.beans.BeansLinker;
import jdk.internal.dynalink.linker.GuardingDynamicLinker;
import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.linker.LinkerServices;
import jdk.internal.dynalink.linker.MethodHandleTransformer;
import jdk.internal.dynalink.linker.MethodTypeConversionStrategy;
import jdk.internal.dynalink.support.AutoDiscovery;
import jdk.internal.dynalink.support.BottomGuardingDynamicLinker;
@ -132,6 +134,7 @@ public class DynamicLinkerFactory {
private int unstableRelinkThreshold = DEFAULT_UNSTABLE_RELINK_THRESHOLD;
private GuardedInvocationFilter prelinkFilter;
private MethodTypeConversionStrategy autoConversionStrategy;
private MethodHandleTransformer internalObjectsFilter;
/**
* Sets the class loader for automatic discovery of available linkers. If not set explicitly, then the thread
@ -283,6 +286,15 @@ public class DynamicLinkerFactory {
this.autoConversionStrategy = autoConversionStrategy;
}
/**
* Sets a method handle transformer that is supposed to act as the implementation of this linker factory's linkers'
* services {@link LinkerServices#filterInternalObjects(java.lang.invoke.MethodHandle)} method.
* @param internalObjectsFilter a method handle transformer filtering out internal objects, or null.
*/
public void setInternalObjectsFilter(final MethodHandleTransformer internalObjectsFilter) {
this.internalObjectsFilter = internalObjectsFilter;
}
/**
* Creates a new dynamic linker consisting of all the prioritized, autodiscovered, and fallback linkers as well as
* the pre-link filter.
@ -350,8 +362,8 @@ public class DynamicLinkerFactory {
}
return new DynamicLinker(new LinkerServicesImpl(new TypeConverterFactory(typeConverters,
autoConversionStrategy), composite), prelinkFilter, runtimeContextArgCount, syncOnRelink,
unstableRelinkThreshold);
autoConversionStrategy), composite, internalObjectsFilter), prelinkFilter, runtimeContextArgCount,
syncOnRelink, unstableRelinkThreshold);
}
private static ClassLoader getThreadContextClassLoader() {

View File

@ -570,7 +570,7 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
private static final MethodHandle CONSTANT_NULL_DROP_ANNOTATED_METHOD = MethodHandles.dropArguments(
MethodHandles.constant(Object.class, null), 0, AnnotatedDynamicMethod.class);
private static final MethodHandle GET_ANNOTATED_METHOD = privateLookup.findVirtual(AnnotatedDynamicMethod.class,
"getTarget", MethodType.methodType(MethodHandle.class, MethodHandles.Lookup.class));
"getTarget", MethodType.methodType(MethodHandle.class, MethodHandles.Lookup.class, LinkerServices.class));
private static final MethodHandle GETTER_INVOKER = MethodHandles.invoker(MethodType.methodType(Object.class, Object.class));
private GuardedInvocationComponent getPropertyGetter(final CallSiteDescriptor callSiteDescriptor,
@ -593,7 +593,7 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
final MethodHandle typedGetter = linkerServices.asType(getPropertyGetterHandle, type.changeReturnType(
AnnotatedDynamicMethod.class));
final MethodHandle callSiteBoundMethodGetter = MethodHandles.insertArguments(
GET_ANNOTATED_METHOD, 1, callSiteDescriptor.getLookup());
GET_ANNOTATED_METHOD, 1, callSiteDescriptor.getLookup(), linkerServices);
final MethodHandle callSiteBoundInvoker = MethodHandles.filterArguments(GETTER_INVOKER, 0,
callSiteBoundMethodGetter);
// Object(AnnotatedDynamicMethod, Object)->Object(AnnotatedDynamicMethod, T0)
@ -873,8 +873,8 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
}
@SuppressWarnings("unused")
MethodHandle getTarget(final MethodHandles.Lookup lookup) {
final MethodHandle inv = method.getTarget(lookup);
MethodHandle getTarget(final MethodHandles.Lookup lookup, final LinkerServices linkerServices) {
final MethodHandle inv = linkerServices.filterInternalObjects(method.getTarget(lookup));
assert inv != null;
return inv;
}

View File

@ -165,6 +165,10 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
private static MethodHandle LIST_GUARD = Guards.getInstanceOfGuard(List.class);
private static MethodHandle MAP_GUARD = Guards.getInstanceOfGuard(Map.class);
private enum CollectionType {
ARRAY, LIST, MAP
};
private GuardedInvocationComponent getElementGetter(final CallSiteDescriptor callSiteDescriptor,
final LinkerServices linkerServices, final List<String> operations) throws Exception {
final MethodType callSiteType = callSiteDescriptor.getMethodType();
@ -178,27 +182,27 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
// Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
// in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
final GuardedInvocationComponent gic;
final boolean isMap;
final CollectionType collectionType;
if(declaredType.isArray()) {
gic = new GuardedInvocationComponent(MethodHandles.arrayElementGetter(declaredType));
isMap = false;
gic = createInternalFilteredGuardedInvocationComponent(MethodHandles.arrayElementGetter(declaredType), linkerServices);
collectionType = CollectionType.ARRAY;
} else if(List.class.isAssignableFrom(declaredType)) {
gic = new GuardedInvocationComponent(GET_LIST_ELEMENT);
isMap = false;
gic = createInternalFilteredGuardedInvocationComponent(GET_LIST_ELEMENT, linkerServices);
collectionType = CollectionType.LIST;
} else if(Map.class.isAssignableFrom(declaredType)) {
gic = new GuardedInvocationComponent(GET_MAP_ELEMENT);
isMap = true;
gic = createInternalFilteredGuardedInvocationComponent(GET_MAP_ELEMENT, linkerServices);
collectionType = CollectionType.MAP;
} else if(clazz.isArray()) {
gic = getClassGuardedInvocationComponent(MethodHandles.arrayElementGetter(clazz), callSiteType);
isMap = false;
gic = getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(MethodHandles.arrayElementGetter(clazz)), callSiteType);
collectionType = CollectionType.ARRAY;
} else if(List.class.isAssignableFrom(clazz)) {
gic = new GuardedInvocationComponent(GET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class,
ValidationType.INSTANCE_OF);
isMap = false;
gic = createInternalFilteredGuardedInvocationComponent(GET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class, ValidationType.INSTANCE_OF,
linkerServices);
collectionType = CollectionType.LIST;
} else if(Map.class.isAssignableFrom(clazz)) {
gic = new GuardedInvocationComponent(GET_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class,
ValidationType.INSTANCE_OF);
isMap = true;
gic = createInternalFilteredGuardedInvocationComponent(GET_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class, ValidationType.INSTANCE_OF,
linkerServices);
collectionType = CollectionType.MAP;
} else {
// Can't retrieve elements for objects that are neither arrays, nor list, nor maps.
return nextComponent;
@ -208,7 +212,7 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
final String fixedKey = getFixedKey(callSiteDescriptor);
// Convert the key to a number if we're working with a list or array
final Object typedFixedKey;
if(!isMap && fixedKey != null) {
if(collectionType != CollectionType.MAP && fixedKey != null) {
typedFixedKey = convertKeyToInteger(fixedKey, linkerServices);
if(typedFixedKey == null) {
// key is not numeric, it can never succeed
@ -227,15 +231,21 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
}
final MethodHandle checkGuard;
if(invocation == GET_LIST_ELEMENT) {
switch(collectionType) {
case LIST:
checkGuard = convertArgToInt(RANGE_CHECK_LIST, linkerServices, callSiteDescriptor);
} else if(invocation == GET_MAP_ELEMENT) {
break;
case MAP:
// TODO: A more complex solution could be devised for maps, one where we do a get() first, and fold it
// into a GWT that tests if it returned null, and if it did, do another GWT with containsKey()
// that returns constant null (on true), or falls back to next component (on false)
checkGuard = CONTAINS_MAP;
} else {
checkGuard = linkerServices.filterInternalObjects(CONTAINS_MAP);
break;
case ARRAY:
checkGuard = convertArgToInt(RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);
break;
default:
throw new AssertionError();
}
final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation),
nextComponent.getGuardedInvocation().getInvocation());
@ -243,6 +253,18 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
gic.getValidatorClass(), gic.getValidationType());
}
private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent(
final MethodHandle invocation, final LinkerServices linkerServices) {
return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation));
}
private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent(
final MethodHandle invocation, final MethodHandle guard, final Class<?> validatorClass,
final ValidationType validationType, final LinkerServices linkerServices) {
return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation), guard,
validatorClass, validationType);
}
private static String getFixedKey(final CallSiteDescriptor callSiteDescriptor) {
return callSiteDescriptor.getNameTokenCount() == 2 ? null : callSiteDescriptor.getNameToken(
CallSiteDescriptor.NAME_OPERAND);
@ -381,37 +403,38 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
// dealing with an array, or a list or map, but hey...
// Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
// in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
final boolean isMap;
final CollectionType collectionType;
if(declaredType.isArray()) {
gic = new GuardedInvocationComponent(MethodHandles.arrayElementSetter(declaredType));
isMap = false;
gic = createInternalFilteredGuardedInvocationComponent(MethodHandles.arrayElementSetter(declaredType), linkerServices);
collectionType = CollectionType.ARRAY;
} else if(List.class.isAssignableFrom(declaredType)) {
gic = new GuardedInvocationComponent(SET_LIST_ELEMENT);
isMap = false;
gic = createInternalFilteredGuardedInvocationComponent(SET_LIST_ELEMENT, linkerServices);
collectionType = CollectionType.LIST;
} else if(Map.class.isAssignableFrom(declaredType)) {
gic = new GuardedInvocationComponent(PUT_MAP_ELEMENT);
isMap = true;
gic = createInternalFilteredGuardedInvocationComponent(PUT_MAP_ELEMENT, linkerServices);
collectionType = CollectionType.MAP;
} else if(clazz.isArray()) {
gic = getClassGuardedInvocationComponent(MethodHandles.arrayElementSetter(clazz), callSiteType);
isMap = false;
gic = getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(
MethodHandles.arrayElementSetter(clazz)), callSiteType);
collectionType = CollectionType.ARRAY;
} else if(List.class.isAssignableFrom(clazz)) {
gic = new GuardedInvocationComponent(SET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class,
ValidationType.INSTANCE_OF);
isMap = false;
gic = createInternalFilteredGuardedInvocationComponent(SET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class, ValidationType.INSTANCE_OF,
linkerServices);
collectionType = CollectionType.LIST;
} else if(Map.class.isAssignableFrom(clazz)) {
gic = new GuardedInvocationComponent(PUT_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class,
ValidationType.INSTANCE_OF);
isMap = true;
gic = createInternalFilteredGuardedInvocationComponent(PUT_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType),
Map.class, ValidationType.INSTANCE_OF, linkerServices);
collectionType = CollectionType.MAP;
} else {
// Can't set elements for objects that are neither arrays, nor list, nor maps.
gic = null;
isMap = false;
collectionType = null;
}
// In contrast to, say, getElementGetter, we only compute the nextComponent if the target object is not a map,
// as maps will always succeed in setting the element and will never need to fall back to the next component
// operation.
final GuardedInvocationComponent nextComponent = isMap ? null : getGuardedInvocationComponent(
final GuardedInvocationComponent nextComponent = collectionType == CollectionType.MAP ? null : getGuardedInvocationComponent(
callSiteDescriptor, linkerServices, operations);
if(gic == null) {
return nextComponent;
@ -421,7 +444,7 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
final String fixedKey = getFixedKey(callSiteDescriptor);
// Convert the key to a number if we're working with a list or array
final Object typedFixedKey;
if(!isMap && fixedKey != null) {
if(collectionType != CollectionType.MAP && fixedKey != null) {
typedFixedKey = convertKeyToInteger(fixedKey, linkerServices);
if(typedFixedKey == null) {
// key is not numeric, it can never succeed
@ -439,7 +462,8 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
return gic.replaceInvocation(binder.bind(invocation));
}
final MethodHandle checkGuard = convertArgToInt(invocation == SET_LIST_ELEMENT ? RANGE_CHECK_LIST :
assert collectionType == CollectionType.LIST || collectionType == CollectionType.ARRAY;
final MethodHandle checkGuard = convertArgToInt(collectionType == CollectionType.LIST ? RANGE_CHECK_LIST :
RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);
final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation),
nextComponent.getGuardedInvocation().getInvocation());

View File

@ -139,7 +139,8 @@ class OverloadedMethod {
final MethodHandle bound = SELECT_METHOD.bindTo(this);
final MethodHandle collecting = SingleDynamicMethod.collectArguments(bound, argNum).asType(
callSiteType.changeReturnType(MethodHandle.class));
invoker = MethodHandles.foldArguments(MethodHandles.exactInvoker(this.callSiteType), collecting);
invoker = linkerServices.asTypeLosslessReturn(MethodHandles.foldArguments(
MethodHandles.exactInvoker(this.callSiteType), collecting), callSiteType);
}
MethodHandle getInvoker() {

View File

@ -165,10 +165,11 @@ abstract class SingleDynamicMethod extends DynamicMethod {
* @return the adapted method handle.
*/
static MethodHandle getInvocation(final MethodHandle target, final MethodType callSiteType, final LinkerServices linkerServices) {
final MethodType methodType = target.type();
final MethodHandle filteredTarget = linkerServices.filterInternalObjects(target);
final MethodType methodType = filteredTarget.type();
final int paramsLen = methodType.parameterCount();
final boolean varArgs = target.isVarargsCollector();
final MethodHandle fixTarget = varArgs ? target.asFixedArity() : target;
final MethodHandle fixTarget = varArgs ? filteredTarget.asFixedArity() : filteredTarget;
final int fixParamsLen = varArgs ? paramsLen - 1 : paramsLen;
final int argsLen = callSiteType.parameterCount();
if(argsLen < fixParamsLen) {
@ -204,7 +205,7 @@ abstract class SingleDynamicMethod extends DynamicMethod {
if(varArgType.isAssignableFrom(callSiteLastArgType)) {
// Call site signature guarantees we'll always be passed a single compatible array; just link directly
// to the method, introducing necessary conversions. Also, preserve it being a variable arity method.
return createConvertingInvocation(target, linkerServices, callSiteType).asVarargsCollector(
return createConvertingInvocation(filteredTarget, linkerServices, callSiteType).asVarargsCollector(
callSiteLastArgType);
}

View File

@ -180,6 +180,15 @@ public interface LinkerServices {
*/
public Comparison compareConversion(Class<?> sourceType, Class<?> targetType1, Class<?> targetType2);
/**
* Modifies the method handle so that any parameters that can receive potentially internal language runtime objects
* will have a filter added on them to prevent them from escaping, potentially by wrapping them.
* It can also potentially add an unwrapping filter to the return value.
* @param target the target method handle
* @return a method handle with parameters and/or return type potentially filtered for wrapping and unwrapping.
*/
public MethodHandle filterInternalObjects(final MethodHandle target);
/**
* If we could just use Java 8 constructs, then {@code asTypeSafeReturn} would be a method with default
* implementation. Since we can't do that, we extract common default implementations into this static class.

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2015, 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-2015 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;
import java.lang.invoke.MethodHandle;
/**
* A generic interface describing operations that transform method handles.
*/
public interface MethodHandleTransformer {
/**
* Transforms a method handle.
* @param target the method handle being transformed.
* @return transformed method handle.
*/
public MethodHandle transform(final MethodHandle target);
}

View File

@ -0,0 +1,177 @@
/*
* Copyright (c) 2015, 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-2015 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.support;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import jdk.internal.dynalink.DynamicLinkerFactory;
import jdk.internal.dynalink.linker.MethodHandleTransformer;
/**
* Default implementation for a {@link DynamicLinkerFactory#setInternalObjectsFilter(MethodHandleTransformer)}.
* Given a method handle of {@code Object(Object)} type for filtering parameter and another one of the same type for
* filtering return values, applies them to passed method handles where their parameter types and/or return value types
* are declared to be {@link Object}.
*/
public class DefaultInternalObjectFilter implements MethodHandleTransformer {
private static final MethodHandle FILTER_VARARGS = new Lookup(MethodHandles.lookup()).findStatic(
DefaultInternalObjectFilter.class, "filterVarArgs", MethodType.methodType(Object[].class, MethodHandle.class, Object[].class));
private final MethodHandle parameterFilter;
private final MethodHandle returnFilter;
private final MethodHandle varArgFilter;
/**
* Creates a new filter.
* @param parameterFilter the filter for method parameters. Must be of type {@code Object(Object)}, or null.
* @param returnFilter the filter for return values. Must be of type {@code Object(Object)}, or null.
* @throws IllegalArgumentException if one or both filters are not of the expected type.
*/
public DefaultInternalObjectFilter(final MethodHandle parameterFilter, final MethodHandle returnFilter) {
this.parameterFilter = checkHandle(parameterFilter, "parameterFilter");
this.returnFilter = checkHandle(returnFilter, "returnFilter");
this.varArgFilter = parameterFilter == null ? null : FILTER_VARARGS.bindTo(parameterFilter);
}
@Override
public MethodHandle transform(final MethodHandle target) {
assert target != null;
MethodHandle[] filters = null;
final MethodType type = target.type();
final boolean isVarArg = target.isVarargsCollector();
final int paramCount = type.parameterCount();
final MethodHandle paramsFiltered;
// Filter parameters
if (parameterFilter != null) {
int firstFilter = -1;
// Ignore receiver, start from argument 1
for(int i = 1; i < paramCount; ++i) {
final Class<?> paramType = type.parameterType(i);
final boolean filterVarArg = isVarArg && i == paramCount - 1 && paramType == Object[].class;
if (filterVarArg || paramType == Object.class) {
if (filters == null) {
firstFilter = i;
filters = new MethodHandle[paramCount - firstFilter];
}
filters[i - firstFilter] = filterVarArg ? varArgFilter : parameterFilter;
}
}
paramsFiltered = filters != null ? MethodHandles.filterArguments(target, firstFilter, filters) : target;
} else {
paramsFiltered = target;
}
// Filter return value if needed
final MethodHandle returnFiltered = returnFilter != null && type.returnType() == Object.class ? MethodHandles.filterReturnValue(paramsFiltered, returnFilter) : paramsFiltered;
// Preserve varargs collector state
return isVarArg && !returnFiltered.isVarargsCollector() ? returnFiltered.asVarargsCollector(type.parameterType(paramCount - 1)) : returnFiltered;
}
private static MethodHandle checkHandle(final MethodHandle handle, final String handleKind) {
if (handle != null) {
final MethodType objectObjectType = MethodType.methodType(Object.class, Object.class);
if (!handle.type().equals(objectObjectType)) {
throw new IllegalArgumentException("Method type for " + handleKind + " must be " + objectObjectType);
}
}
return handle;
}
@SuppressWarnings("unused")
private static Object[] filterVarArgs(final MethodHandle parameterFilter, final Object[] args) throws Throwable {
Object[] newArgs = null;
for(int i = 0; i < args.length; ++i) {
final Object arg = args[i];
final Object newArg = parameterFilter.invokeExact(arg);
if (arg != newArg) {
if (newArgs == null) {
newArgs = args.clone();
}
newArgs[i] = newArg;
}
}
return newArgs == null ? args : newArgs;
}
}

View File

@ -90,6 +90,7 @@ import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.GuardingDynamicLinker;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.linker.LinkerServices;
import jdk.internal.dynalink.linker.MethodHandleTransformer;
/**
* Default implementation of the {@link LinkerServices} interface.
@ -103,17 +104,21 @@ public class LinkerServicesImpl implements LinkerServices {
private final TypeConverterFactory typeConverterFactory;
private final GuardingDynamicLinker topLevelLinker;
private final MethodHandleTransformer internalObjectsFilter;
/**
* Creates a new linker services object.
*
* @param typeConverterFactory the type converter factory exposed by the services.
* @param topLevelLinker the top level linker used by the services.
* @param internalObjectsFilter a method handle transformer that is supposed to act as the implementation of this
* services' {@link #filterInternalObjects(java.lang.invoke.MethodHandle)} method.
*/
public LinkerServicesImpl(final TypeConverterFactory typeConverterFactory,
final GuardingDynamicLinker topLevelLinker) {
final GuardingDynamicLinker topLevelLinker, final MethodHandleTransformer internalObjectsFilter) {
this.typeConverterFactory = typeConverterFactory;
this.topLevelLinker = topLevelLinker;
this.internalObjectsFilter = internalObjectsFilter;
}
@Override
@ -152,6 +157,11 @@ public class LinkerServicesImpl implements LinkerServices {
}
}
@Override
public MethodHandle filterInternalObjects(final MethodHandle target) {
return internalObjectsFilter != null ? internalObjectsFilter.transform(target) : target;
}
/**
* 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.

View File

@ -119,6 +119,7 @@ public final class Bootstrap {
return unboxReturnType(target, newType);
}
});
factory.setInternalObjectsFilter(NashornBeansLinker.createHiddenObjectFilter());
final int relinkThreshold = Options.getIntProperty("nashorn.unstable.relink.threshold", NASHORN_DEFAULT_UNSTABLE_RELINK_THRESHOLD);
if (relinkThreshold > -1) {
factory.setUnstableRelinkThreshold(relinkThreshold);

View File

@ -40,10 +40,11 @@ import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.GuardingDynamicLinker;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.linker.LinkerServices;
import jdk.internal.dynalink.linker.MethodHandleTransformer;
import jdk.internal.dynalink.support.DefaultInternalObjectFilter;
import jdk.internal.dynalink.support.Guards;
import jdk.internal.dynalink.support.Lookup;
import jdk.nashorn.api.scripting.ScriptUtils;
import jdk.nashorn.internal.objects.NativeArray;
import jdk.nashorn.internal.runtime.ConsString;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ScriptObject;
@ -52,10 +53,14 @@ import jdk.nashorn.internal.runtime.options.Options;
/**
* This linker delegates to a {@code BeansLinker} but passes it a special linker services object that has a modified
* {@code asType} method that will ensure that we never pass internal engine objects that should not be externally
* observable (currently ConsString and ScriptObject) to Java APIs, but rather that we flatten it into a String. We can't just add
* this functionality as custom converters via {@code GuaardingTypeConverterFactory}, since they are not consulted when
* the target method handle parameter signature is {@code Object}.
* {@code compareConversion} method that favors conversion of {@link ConsString} to either {@link String} or
* {@link CharSequence}. It also provides a {@link #createHiddenObjectFilter()} method for use with bootstrap that will
* ensure that we never pass internal engine objects that should not be externally observable (currently ConsString and
* ScriptObject) to Java APIs, but rather that we flatten it into a String. We can't just add this functionality as
* custom converters via {@code GuaardingTypeConverterFactory}, since they are not consulted when
* the target method handle parameter signature is {@code Object}. This linker also makes sure that primitive
* {@link String} operations can be invoked on a {@link ConsString}, and allows invocation of objects implementing
* the {@link FunctionalInterface} attribute.
*/
public class NashornBeansLinker implements GuardingDynamicLinker {
// System property to control whether to wrap ScriptObject->ScriptObjectMirror for
@ -63,16 +68,12 @@ public class NashornBeansLinker implements GuardingDynamicLinker {
private static final boolean MIRROR_ALWAYS = Options.getBooleanProperty("nashorn.mirror.always", true);
private static final MethodHandle EXPORT_ARGUMENT;
private static final MethodHandle EXPORT_NATIVE_ARRAY;
private static final MethodHandle EXPORT_SCRIPT_OBJECT;
private static final MethodHandle IMPORT_RESULT;
private static final MethodHandle FILTER_CONSSTRING;
static {
final Lookup lookup = new Lookup(MethodHandles.lookup());
EXPORT_ARGUMENT = lookup.findOwnStatic("exportArgument", Object.class, Object.class);
EXPORT_NATIVE_ARRAY = lookup.findOwnStatic("exportNativeArray", Object.class, NativeArray.class);
EXPORT_SCRIPT_OBJECT = lookup.findOwnStatic("exportScriptObject", Object.class, ScriptObject.class);
IMPORT_RESULT = lookup.findOwnStatic("importResult", Object.class, Object.class);
FILTER_CONSSTRING = lookup.findOwnStatic("consStringFilter", Object.class, Object.class);
}
@ -115,9 +116,10 @@ public class NashornBeansLinker implements GuardingDynamicLinker {
}
return new GuardedInvocation(
// drop 'thiz' passed from the script.
MH.dropArguments(desc.getLookup().unreflect(m), 1, callType.parameterType(1)),
Guards.getInstanceOfGuard(m.getDeclaringClass())).asTypeSafeReturn(
new NashornBeansLinkerServices(linkerServices), callType);
MH.dropArguments(linkerServices.filterInternalObjects(desc.getLookup().unreflect(m)), 1,
callType.parameterType(1)), Guards.getInstanceOfGuard(
m.getDeclaringClass())).asTypeSafeReturn(
new NashornBeansLinkerServices(linkerServices), callType);
}
}
return getGuardedInvocation(beansLinker, linkRequest, linkerServices);
@ -141,21 +143,6 @@ public class NashornBeansLinker implements GuardingDynamicLinker {
return exportArgument(arg, MIRROR_ALWAYS);
}
@SuppressWarnings("unused")
private static Object exportNativeArray(final NativeArray arg) {
return exportArgument(arg, MIRROR_ALWAYS);
}
@SuppressWarnings("unused")
private static Object exportScriptObject(final ScriptObject arg) {
return exportArgument(arg, MIRROR_ALWAYS);
}
@SuppressWarnings("unused")
private static Object exportScriptArray(final NativeArray arg) {
return exportArgument(arg, MIRROR_ALWAYS);
}
static Object exportArgument(final Object arg, final boolean mirrorAlways) {
if (arg instanceof ConsString) {
return arg.toString();
@ -208,6 +195,10 @@ public class NashornBeansLinker implements GuardingDynamicLinker {
return FUNCTIONAL_IFACE_METHOD.get(clazz);
}
static MethodHandleTransformer createHiddenObjectFilter() {
return new DefaultInternalObjectFilter(EXPORT_ARGUMENT, MIRROR_ALWAYS ? IMPORT_RESULT : null);
}
private static class NashornBeansLinkerServices implements LinkerServices {
private final LinkerServices linkerServices;
@ -217,50 +208,7 @@ public class NashornBeansLinker implements GuardingDynamicLinker {
@Override
public MethodHandle asType(final MethodHandle handle, final MethodType fromType) {
final MethodType handleType = handle.type();
final int paramCount = handleType.parameterCount();
assert fromType.parameterCount() == handleType.parameterCount();
MethodType newFromType = fromType;
MethodHandle[] filters = null;
for(int i = 0; i < paramCount; ++i) {
final MethodHandle filter = argConversionFilter(handleType.parameterType(i), fromType.parameterType(i));
if (filter != null) {
if (filters == null) {
filters = new MethodHandle[paramCount];
}
// "erase" specific type with Object type or else we'll get filter mismatch
newFromType = newFromType.changeParameterType(i, Object.class);
filters[i] = filter;
}
}
final MethodHandle typed = linkerServices.asType(handle, newFromType);
MethodHandle result = filters != null ? MethodHandles.filterArguments(typed, 0, filters) : typed;
// Filter Object typed return value for possible ScriptObjectMirror. We convert
// ScriptObjectMirror as ScriptObject (if it is mirror from current global).
if (MIRROR_ALWAYS && areBothObjects(handleType.returnType(), fromType.returnType())) {
result = MethodHandles.filterReturnValue(result, IMPORT_RESULT);
}
return result;
}
private static MethodHandle argConversionFilter(final Class<?> handleType, final Class<?> fromType) {
if (handleType == Object.class) {
if (fromType == Object.class) {
return EXPORT_ARGUMENT;
} else if (fromType == NativeArray.class) {
return EXPORT_NATIVE_ARRAY;
} else if (fromType == ScriptObject.class) {
return EXPORT_SCRIPT_OBJECT;
}
}
return null;
}
private static boolean areBothObjects(final Class<?> handleType, final Class<?> fromType) {
return handleType == Object.class && fromType == Object.class;
return linkerServices.asType(handle, fromType);
}
@Override
@ -296,5 +244,10 @@ public class NashornBeansLinker implements GuardingDynamicLinker {
}
return linkerServices.compareConversion(sourceType, targetType1, targetType2);
}
@Override
public MethodHandle filterInternalObjects(MethodHandle target) {
return linkerServices.filterInternalObjects(target);
}
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* JDK-8072596: Arrays.asList results in ClassCastException with a JS array
*
* @test
* @run
*/
var arr = java.util.Arrays.asList("hello world".split(' '));
// We split it into a list of two elements: [hello, world]
Assert.assertTrue(arr instanceof java.util.List);
Assert.assertEquals(arr.length, 2);
Assert.assertEquals(arr[0], "hello");
Assert.assertEquals(arr[1], "world");
var Jdk8072596TestSubject = Java.type("jdk.nashorn.test.models.Jdk8072596TestSubject");
var testSubject = new Jdk8072596TestSubject({bar: 0});
testSubject.test1(true, {foo: 1}, {bar: 2});
testSubject.test2(true, {foo: 1}, {bar: 2}, {baz: 3}, {bing: 4});
var h = "h";
var ello = "ello";
testSubject.test3(true, {foo: 5}, /* ConsString, why not */ h + ello, [6, 7], 8);
Jdk8072596TestSubject.test4({foo: 9});
// Test wrapping setters arguments and unwrapping getters return values on list.
var list = new java.util.ArrayList();
list.add(null);
var obj0 = {valueOf: function() { return 0; }};
var obj1 = {foo: 10};
list[obj0] = obj1;
testSubject.testListHasWrappedObject(list);
// NOTE: can't use Assert.assertSame(obj1, list[obj0]), as the arguments would end up being wrapped...
Assert.assertTrue(obj1 === list[obj0]);
// Test wrapping setters arguments and unwrapping getters return values on array.
var arr2 = new (Java.type("java.lang.Object[]"))(1);
var obj2 = {bar: 11};
arr2[obj0] = obj2;
testSubject.testArrayHasWrappedObject(arr2);
Assert.assertTrue(obj2 === arr2[obj0]);
// Test wrapping setters index and arguments and getters index, and unwrapping getters return values on map.
// Since ScriptObjectMirror.equals() uses underlying ScriptObject identity, using them as map keys works.
var map = new java.util.HashMap();
var obj3 = {bar: 12};
map[obj0] = obj3;
testSubject.testMapHasWrappedObject(map, obj0);
Assert.assertTrue(obj3 === map[obj0]);

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2015, 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;
import java.util.List;
import java.util.Map;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.runtime.ScriptObject;
import org.testng.Assert;
public class Jdk8072596TestSubject {
public Jdk8072596TestSubject(final Object x) {
Assert.assertTrue(x instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)x).get("bar"), 0);
}
// Test having to wrap some arguments but not others
public void test1(final String x, final Object y, final ScriptObject w) {
Assert.assertEquals(x, "true");
Assert.assertTrue(y instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)y).get("foo"), 1);
Assert.assertEquals(w.get("bar"), 2);
}
// Test having to wrap some arguments but not others, and a vararg array
public void test2(String x, final Object y, final ScriptObject w, final Object... z) {
test1(x, y, w);
Assert.assertEquals(z.length, 2);
Assert.assertTrue(z[0] instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)z[0]).get("baz"), 3);
Assert.assertTrue(z[1] instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)z[1]).get("bing"), 4);
}
// Test mixed (wrappable and non-wrappable) elements in a vararg array
public void test3(final Object... z) {
Assert.assertEquals(z.length, 5);
Assert.assertEquals(z[0], true);
Assert.assertTrue(z[1] instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)z[1]).get("foo"), 5);
Assert.assertEquals(z[2], "hello");
Assert.assertTrue(z[3] instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)z[3]).getSlot(0), 6);
Assert.assertEquals(((ScriptObjectMirror)z[3]).getSlot(1), 7);
Assert.assertEquals(z[4], 8);
}
// test wrapping the first argument of a static method
public static void test4(final Object x) {
Assert.assertTrue(x instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)x).get("foo"), 9);
}
public void testListHasWrappedObject(final List<?> l) {
Assert.assertEquals(l.size(), 1);
Assert.assertTrue(l.get(0) instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)l.get(0)).get("foo"), 10);
}
public void testArrayHasWrappedObject(final Object[] a) {
Assert.assertEquals(a.length, 1);
Assert.assertTrue(a[0] instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)a[0]).get("bar"), 11);
}
public void testMapHasWrappedObject(final Map<?, ?> m, final Object key) {
Assert.assertEquals(m.size(), 1);
Assert.assertTrue(key instanceof ScriptObjectMirror);
Assert.assertTrue(m.get(key) instanceof ScriptObjectMirror);
Assert.assertEquals(((ScriptObjectMirror)m.get(key)).get("bar"), 12);
}
}