8046905: apply on apply is broken

Reviewed-by: hannesw, lagergren
This commit is contained in:
Attila Szegedi 2014-06-23 10:59:33 +02:00
parent f4c68a9cfc
commit 7aa808a993
5 changed files with 224 additions and 16 deletions

View File

@ -35,6 +35,7 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.SwitchPoint;
import java.util.Collections;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.LinkRequest;
@ -593,6 +594,12 @@ public abstract class ScriptFunction extends ScriptObject {
private GuardedInvocation createApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc, final LinkRequest request, final Object[] args) {
final MethodType descType = desc.getMethodType();
final int paramCount = descType.parameterCount();
if(descType.parameterType(paramCount - 1).isArray()) {
// This is vararg invocation of apply or call. This can normally only happen when we do a recursive
// invocation of createApplyOrCallCall (because we're doing apply-of-apply). In this case, create delegate
// linkage by unpacking the vararg invocation and use pairArguments to introduce the necessary spreader.
return createVarArgApplyOrCallCall(isApply, desc, request, args);
}
final boolean passesThis = paramCount > 2;
final boolean passesArgs = paramCount > 3;
@ -647,6 +654,7 @@ public abstract class ScriptFunction extends ScriptObject {
System.arraycopy(args, 3, tmp, 0, tmp.length);
appliedArgs[2] = NativeFunction.toApplyArgs(tmp);
} else {
assert !isApply;
System.arraycopy(args, 3, appliedArgs, 2, args.length - 3);
}
} else if (isFailedApplyToCall) {
@ -705,8 +713,7 @@ public abstract class ScriptFunction extends ScriptObject {
}
final MethodType guardType = guard.type();
// Original function guard will expect the invoked function in parameter position 0, but we're passing it in
// position 1.
// We need to account for the dropped (apply|call) function argument.
guard = MH.dropArguments(guard, 0, descType.parameterType(0));
// Take the "isApplyFunction" guard, and bind it to this function.
MethodHandle applyFnGuard = MH.insertArguments(IS_APPLY_FUNCTION, 2, this);
@ -718,7 +725,72 @@ public abstract class ScriptFunction extends ScriptObject {
return appliedInvocation.replaceMethods(inv, guard);
}
private static MethodHandle bindImplicitThis(final Object fn, final MethodHandle mh) {
/*
* This method is used for linking nested apply. Specialized apply and call linking will create a variable arity
* call site for an apply call; when createApplyOrCallCall sees a linking request for apply or call with
* Nashorn-style variable arity call site (last argument type is Object[]) it'll delegate to this method.
* This method converts the link request from a vararg to a non-vararg one (unpacks the array), then delegates back
* to createApplyOrCallCall (with which it is thus mutually recursive), and adds appropriate argument spreaders to
* invocation and the guard of whatever createApplyOrCallCall returned to adapt it back into a variable arity
* invocation. It basically reduces the problem of vararg call site linking of apply and call back to the (already
* solved by createApplyOrCallCall) non-vararg call site linking.
*/
private GuardedInvocation createVarArgApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc,
final LinkRequest request, final Object[] args) {
final MethodType descType = desc.getMethodType();
final int paramCount = descType.parameterCount();
final Object[] varArgs = (Object[])args[paramCount - 1];
// -1 'cause we're not passing the vararg array itself
final int copiedArgCount = args.length - 1;
int varArgCount = varArgs.length;
// Spread arguments for the delegate createApplyOrCallCall invocation.
final Object[] spreadArgs = new Object[copiedArgCount + varArgCount];
System.arraycopy(args, 0, spreadArgs, 0, copiedArgCount);
System.arraycopy(varArgs, 0, spreadArgs, copiedArgCount, varArgCount);
// Spread call site descriptor for the delegate createApplyOrCallCall invocation. We drop vararg array and
// replace it with a list of Object.class.
final MethodType spreadType = descType.dropParameterTypes(paramCount - 1, paramCount).appendParameterTypes(
Collections.<Class<?>>nCopies(varArgCount, Object.class));
final CallSiteDescriptor spreadDesc = desc.changeMethodType(spreadType);
// Delegate back to createApplyOrCallCall with the spread (that is, reverted to non-vararg) request/
final LinkRequest spreadRequest = request.replaceArguments(spreadDesc, spreadArgs);
final GuardedInvocation spreadInvocation = createApplyOrCallCall(isApply, spreadDesc, spreadRequest, spreadArgs);
// Add spreader combinators to returned invocation and guard.
return spreadInvocation.replaceMethods(
// Use standard ScriptObject.pairArguments on the invocation
pairArguments(spreadInvocation.getInvocation(), descType),
// Use our specialized spreadGuardArguments on the guard (see below).
spreadGuardArguments(spreadInvocation.getGuard(), descType));
}
private static MethodHandle spreadGuardArguments(final MethodHandle guard, final MethodType descType) {
final MethodType guardType = guard.type();
final int guardParamCount = guardType.parameterCount();
final int descParamCount = descType.parameterCount();
final int spreadCount = guardParamCount - descParamCount + 1;
if (spreadCount <= 0) {
// Guard doesn't dip into the varargs
return guard;
}
final MethodHandle arrayConvertingGuard;
// If the last parameter type of the guard is an array, then it is already itself a guard for a vararg apply
// invocation. We must filter the last argument with toApplyArgs otherwise deeper levels of nesting will fail
// with ClassCastException of NativeArray to Object[].
if(guardType.parameterType(guardParamCount - 1).isArray()) {
arrayConvertingGuard = MH.filterArguments(guard, guardParamCount - 1, NativeFunction.TO_APPLY_ARGS);
} else {
arrayConvertingGuard = guard;
}
return ScriptObject.adaptHandleToVarArgCallSite(arrayConvertingGuard, descParamCount);
}
private static MethodHandle bindImplicitThis(final Object fn, final MethodHandle mh) {
final MethodHandle bound;
if(fn instanceof ScriptFunction && ((ScriptFunction)fn).needsWrappedThis()) {
bound = MH.filterArguments(mh, 1, SCRIPTFUNCTION_GLOBALFILTER);

View File

@ -2499,18 +2499,7 @@ public abstract class ScriptObject implements PropertyAccess {
}
if (isCallerVarArg) {
final int spreadArgs = parameterCount - callCount + 1;
return MH.filterArguments(
MH.asSpreader(
methodHandle,
Object[].class,
spreadArgs),
callCount - 1,
MH.insertArguments(
TRUNCATINGFILTER,
0,
spreadArgs)
);
return adaptHandleToVarArgCallSite(methodHandle, callCount);
}
if (callCount < parameterCount) {
@ -2541,6 +2530,21 @@ public abstract class ScriptObject implements PropertyAccess {
return methodHandle;
}
static MethodHandle adaptHandleToVarArgCallSite(final MethodHandle mh, final int callSiteParamCount) {
final int spreadArgs = mh.type().parameterCount() - callSiteParamCount + 1;
return MH.filterArguments(
MH.asSpreader(
mh,
Object[].class,
spreadArgs),
callSiteParamCount - 1,
MH.insertArguments(
TRUNCATINGFILTER,
0,
spreadArgs)
);
}
@SuppressWarnings("unused")
private static Object[] truncatingFilter(final int n, final Object[] array) {
final int length = array == null ? 0 : array.length;

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2010, 2014, 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-8046905: apply on apply is broken
*
* @test
* @run
*/
var apply = Function.prototype.apply;
var call = Function.prototype.call;
var sort = Array.prototype.sort;
var join = Array.prototype.join;
// Running three times so that we test an already linked call site too:
// i==0: linking initially with assumed optimistic returned type int.
// i==1: linking after deoptimization with returned type Object.
// i==2: re-running code linked in previous iteration. This will
// properly exercise the guards too.
print("1 level of apply")
for(i = 0; i < 3; ++i) {
print(sort.apply([4,3,2,1]))
}
print("2 levels of apply")
for(i = 0; i < 3; ++i) {
print(apply.apply(sort,[[4,3,2,1]]))
}
print("3 levels of apply")
for(i = 0; i < 3; ++i) {
print(apply.apply(apply,[sort,[[4,3,2,1]]]))
}
print("4 levels of apply")
for(i = 0; i < 3; ++i) {
print(apply.apply(apply,[apply,[sort,[[4,3,2,1]]]]))
}
print("5 levels of apply")
for(i = 0; i < 3; ++i) {
print(apply.apply(apply,[apply,[apply,[sort,[[4,3,2,1]]]]]))
}
print("Many levels of apply!")
for(i = 0; i < 3; ++i) {
print(apply.apply(apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[sort,[[4,3,2,1]]]]]]]]]]]]]]]]]]]]]]))
}
print("different invocations that'll trigger relinking")
var invocation = [sort,[[4,3,2,1]]];
for(i = 0; i < 4; ++i) {
print(apply.apply(apply,[apply,invocation]))
// First change after i==1, so it relinks an otherwise stable linkage
if(i == 1) {
invocation = [sort,[[8,7,6,5]]];
} else if(i == 2) {
invocation = [join,[[8,7,6,5],["-"]]];
}
}
print("Many levels of call!")
for(i = 0; i < 3; ++i) {
print(call.call(call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,sort,[4,3,2,1]))
}
print("call apply call apply call... a lot");
for(i = 0; i < 3; ++i) {
print(apply.call(call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [sort, [4,3,2,1]]]]]]]))
}
print("apply call apply call apply... a lot");
for(i = 0; i < 3; ++i) {
print(call.apply(apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, sort, [[4,3,2,1]]]]]]]]]))
}

View File

@ -0,0 +1,41 @@
1 level of apply
1,2,3,4
1,2,3,4
1,2,3,4
2 levels of apply
1,2,3,4
1,2,3,4
1,2,3,4
3 levels of apply
1,2,3,4
1,2,3,4
1,2,3,4
4 levels of apply
1,2,3,4
1,2,3,4
1,2,3,4
5 levels of apply
1,2,3,4
1,2,3,4
1,2,3,4
Many levels of apply!
1,2,3,4
1,2,3,4
1,2,3,4
different invocations that'll trigger relinking
1,2,3,4
1,2,3,4
5,6,7,8
8-7-6-5
Many levels of call!
1,2,3,4
1,2,3,4
1,2,3,4
call apply call apply call... a lot
1,2,3,4
1,2,3,4
1,2,3,4
apply call apply call apply... a lot
1,2,3,4
1,2,3,4
1,2,3,4

View File

@ -67,7 +67,7 @@ makeFuncExpectError("eval(\"x4\", x3);", ReferenceError);
makeFuncAndCall("with({5.0000000000000000000000: String()}){(false); }");
makeFuncAndCall("try { var x = undefined, x = 5.0000000000000000000000; } catch(x) { x = undefined; }");
makeFuncAndCall("(function (x){ x %= this}(false))");
// makeFuncAndCall("eval.apply.apply(function(){ eval('') })");
makeFuncAndCall("eval.apply.apply(function(){ eval('') })");
makeFuncAndCall("(false % !this) && 0");
makeFuncAndCall("with({8: 'fafafa'.replace()}){ }");
makeFuncAndCall("(function (x) '' )(true)");