Skip to content

Commit 9a62e7d

Browse files
committed
8343377: Performance regression in reflective invocation of native methods
1 parent c388455 commit 9a62e7d

File tree

4 files changed

+64
-20
lines changed

4 files changed

+64
-20
lines changed

src/java.base/share/classes/jdk/internal/reflect/DirectMethodHandleAccessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class DirectMethodHandleAccessor extends MethodAccessorImpl {
4747
* Creates a MethodAccessorImpl for a non-native method.
4848
*/
4949
static MethodAccessorImpl methodAccessor(Method method, MethodHandle target) {
50-
assert !Modifier.isNative(method.getModifiers());
50+
assert !MethodHandleAccessorFactory.isSignaturePolymorphicMethod(method);
5151

5252
return new DirectMethodHandleAccessor(method, target, false);
5353
}

src/java.base/share/classes/jdk/internal/reflect/MethodHandleAccessorFactory.java

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,7 @@
2828
import java.lang.invoke.MethodHandle;
2929
import java.lang.invoke.MethodHandles;
3030
import java.lang.invoke.MethodType;
31+
import java.lang.invoke.VarHandle;
3132
import java.lang.reflect.Constructor;
3233
import java.lang.reflect.Executable;
3334
import java.lang.reflect.Field;
@@ -347,36 +348,34 @@ static void ensureClassInitialized(Class<?> defc) {
347348
* Native accessor, i.e. VM reflection implementation, is used if one of
348349
* the following conditions is met:
349350
* 1. during VM early startup before method handle support is fully initialized
350-
* 2. a Java native method
351-
* 3. -Djdk.reflect.useNativeAccessorOnly=true is set
351+
* 2. -Djdk.reflect.useNativeAccessorOnly=true is set
352+
* 3. a signature polymorphic method
352353
* 4. the member takes a variable number of arguments and the last parameter
353354
* is not an array (see details below)
354355
* 5. the member's method type has an arity >= 255
355356
*
357+
* Conditions 3-5 are due to the restrictions of method handles.
356358
* Otherwise, direct invocation of method handles is used.
357359
*/
358360
private static boolean useNativeAccessor(Executable member) {
359361
if (!VM.isJavaLangInvokeInited())
360362
return true;
361363

362-
if (Modifier.isNative(member.getModifiers()))
364+
if (ReflectionFactory.useNativeAccessorOnly()) // for testing only
363365
return true;
364366

365-
if (ReflectionFactory.useNativeAccessorOnly()) // for testing only
367+
// java.lang.invoke cannot find the underlying native stubs of signature
368+
// polymorphic methods that core reflection must invoke.
369+
// Fall back to use the native implementation instead.
370+
if (member instanceof Method method && isSignaturePolymorphicMethod(method))
366371
return true;
367372

368-
// MethodHandle::withVarargs on a member with varargs modifier bit set
369-
// verifies that the last parameter of the member must be an array type.
370-
// The JVMS does not require the last parameter descriptor of the method descriptor
371-
// is an array type if the ACC_VARARGS flag is set in the access_flags item.
372-
// Hence the reflection implementation does not check the last parameter type
373-
// if ACC_VARARGS flag is set. Workaround this by invoking through
374-
// the native accessor.
375-
int paramCount = member.getParameterCount();
376-
if (member.isVarArgs() &&
377-
(paramCount == 0 || !(member.getParameterTypes()[paramCount-1].isArray()))) {
373+
// java.lang.invoke fails to create MH for bad ACC_VARARGS methods with no
374+
// trailing array, but core reflection ignores ACC_VARARGS flag like the JVM does.
375+
// Fall back to use the native implementation instead.
376+
if (isInvalidVarArgs(member))
378377
return true;
379-
}
378+
380379
// A method handle cannot be created if its type has an arity >= 255
381380
// as the method handle's invoke method consumes an extra argument
382381
// of the method handle itself. Fall back to use the native implementation.
@@ -406,6 +405,48 @@ private static int slotCount(Executable member) {
406405
(Modifier.isStatic(member.getModifiers()) ? 0 : 1);
407406
}
408407

408+
/**
409+
* Signature-polymorphic methods. Lookup has special rules for these methods,
410+
* but core reflection must observe them as they are declared, and reflective
411+
* invocation must invoke the native method stubs that throw UOE.
412+
*
413+
* @param method the method to check
414+
* @return {@code true} if this method is signature polymorphic
415+
* @jls 15.12 Method Invocation Expressions
416+
*/
417+
public static boolean isSignaturePolymorphicMethod(Method method) {
418+
// Native; has variable arity parameter
419+
if (!method.isVarArgs() || !Modifier.isNative(method.getModifiers())) {
420+
return false;
421+
}
422+
// Declared in MethodHandle or VarHandle
423+
var declaringClass = method.getDeclaringClass();
424+
if (declaringClass != MethodHandle.class && declaringClass != VarHandle.class) {
425+
return false;
426+
}
427+
// Single parameter of declared type Object[]
428+
Class<?>[] parameters = reflectionFactory.getExecutableSharedParameterTypes(method);
429+
return parameters.length == 1 && parameters[0] == Object[].class;
430+
}
431+
432+
/**
433+
* Lookup always calls MethodHandle::setVarargs on a member with varargs modifier
434+
* bit set, which verifies that the last parameter of the member must be an array type.
435+
* Thus, Lookup cannot create MethodHandle for such methods or constructors.
436+
* The JVMS does not require that the last parameter descriptor of the method descriptor
437+
* is an array type if the ACC_VARARGS flag is set in the access_flags item.
438+
* Core reflection also has no variable arity support and ignores the ACC_VARARGS flag,
439+
* treating them as regular arguments.
440+
*/
441+
private static boolean isInvalidVarArgs(Executable member) {
442+
if (!member.isVarArgs())
443+
return false;
444+
445+
Class<?>[] parameters = reflectionFactory.getExecutableSharedParameterTypes(member);
446+
var count = parameters.length;
447+
return count == 0 || !parameters[count - 1].isArray();
448+
}
449+
409450
/*
410451
* Delay initializing these static fields until java.lang.invoke is fully initialized.
411452
*/
@@ -414,4 +455,5 @@ static class LazyStaticHolder {
414455
}
415456

416457
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
458+
private static final ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
417459
}

test/jdk/java/lang/invoke/MethodHandleInvokeUOE.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -21,15 +21,16 @@
2121
* questions.
2222
*/
2323

24-
/* @test
24+
/*
25+
* @test
26+
* @bug 8343377
2527
* @summary Test MethodHandle::invokeExact and MethodHandle::invoke throws
2628
* UnsupportedOperationException when called via Method::invoke
2729
* @run testng test.java.lang.invoke.MethodHandleInvokeUOE
2830
*/
2931

3032
package test.java.lang.invoke;
3133

32-
import org.testng.*;
3334
import org.testng.annotations.*;
3435

3536
import java.lang.invoke.MethodHandle;

test/jdk/java/lang/invoke/VarHandles/VarHandleTestReflection.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
/*
2525
* @test
26+
* @bug 8335638 8343377
2627
* @run testng VarHandleTestReflection
2728
*/
2829

0 commit comments

Comments
 (0)