Skip to content

Commit 9563621

Browse files
authored
Merge pull request #18474 from ThanHenderson/14990
Properly handle resolve-time invokeDynamic errors for OJDK MHs
2 parents a099118 + d60b284 commit 9563621

File tree

4 files changed

+72
-64
lines changed

4 files changed

+72
-64
lines changed

jcl/src/java.base/share/classes/java/lang/invoke/MethodHandleResolver.java

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private static final Object resolveInvokeDynamic(long j9class, String name, Stri
245245
/*[ELSE] JAVA_SPEC_VERSION >= 11*/
246246
try {
247247
type = MethodTypeHelper.vmResolveFromMethodDescriptorString(methodDescriptor, access.getClassloader(classObject), null);
248-
/*[ENDIF] JAVA_SPEC_VERSION >= 11 */
248+
/*[ENDIF] JAVA_SPEC_VERSION >= 11*/
249249

250250
int bsmIndex = UNSAFE.getShort(bsmData);
251251
int bsmArgCount = UNSAFE.getShort(bsmData + BSM_ARGUMENT_COUNT_OFFSET);
@@ -265,13 +265,10 @@ private static final Object resolveInvokeDynamic(long j9class, String name, Stri
265265

266266
Object[] appendixResult = new Object[1];
267267

268-
/* result[0] stores a MemberName object, which specifies the caller method (generated bytecodes), or a Throwable.
269-
*
270-
* This leads to a type check in the interpreter for the object stored in result[0].
271-
*
272-
* TODO: Investigate if the Throwable can be wrapped in a MemberName. This will help prevent type checks in the
273-
* interpreter since result[0] will always be a MemberName. This will improve performance.
274-
*/
268+
/*[IF JAVA_SPEC_VERSION >= 11]*/
269+
try {
270+
/*[ENDIF] JAVA_SPEC_VERSION >= 11*/
271+
/* result[0] stores a MemberName object, which specifies the caller method (generated bytecodes). */
275272
result[0] = MethodHandleNatives.linkCallSite(classObject,
276273
/* The second parameter is not used in Java 8 and Java 18+ (JDK bug: 8272614). */
277274
/*[IF (JAVA_SPEC_VERSION > 8) & (JAVA_SPEC_VERSION < 18)]*/
@@ -281,23 +278,39 @@ private static final Object resolveInvokeDynamic(long j9class, String name, Stri
281278

282279
/* result[1] stores a MethodHandle object, which is used as the last argument to the caller method. */
283280
result[1] = appendixResult[0];
284-
/*[IF JAVA_SPEC_VERSION < 11]*/
285281
} catch (Throwable e) {
286-
if (type == null) {
287-
throw new BootstrapMethodError(e);
288-
}
289-
290-
/* linkCallSite may correctly throw a BootstrapMethodError. */
282+
/*[IF JAVA_SPEC_VERSION < 11]*/
283+
/* Before Java 11, if linkCallSite throws a BootstrapMethodError due to the CallSite binding
284+
* resolving to null, throw it at resolution time. This ensures that resolution will be
285+
* reattempted on the subsequent visit to the invokedynamic instruction in the interpreter.
286+
*/
291287
if (e instanceof BootstrapMethodError) {
292288
throw e;
293289
}
294-
295-
/* Any other throwables are wrapped in an invoke-time BootstrapMethodError exception throw. */
290+
/*[ENDIF] JAVA_SPEC_VERSION < 11*/
291+
/* Any throwables are wrapped in an invoke-time BootstrapMethodError exception throw. */
296292
try {
293+
MethodHandle resultHandle;
297294
MethodHandle thrower = MethodHandles.throwException(type.returnType(), BootstrapMethodError.class);
298295
MethodHandle constructor = IMPL_LOOKUP.findConstructor(BootstrapMethodError.class, MethodType.methodType(void.class, Throwable.class));
299-
MethodHandle resultHandle = MethodHandles.foldArguments(thrower, constructor.bindTo(e));
296+
297+
MethodHandle combiner = null;
298+
if (e instanceof BootstrapMethodError) {
299+
Throwable cause = e.getCause();
300+
if (cause == null) {
301+
combiner = constructor.bindTo(e.getMessage());
302+
} else {
303+
combiner = constructor.bindTo(cause);
304+
}
305+
} else {
306+
combiner = constructor.bindTo(e);
307+
}
308+
resultHandle = MethodHandles.foldArguments(thrower, combiner);
309+
/*[IF JAVA_SPEC_VERSION >= 11]*/
310+
MemberName memberName = resultHandle.internalForm().vmentry;
311+
/*[ELSE] JAVA_SPEC_VERSION >= 11*/
300312
MemberName memberName = resultHandle.internalForm().compileToBytecode();
313+
/*[ENDIF] JAVA_SPEC_VERSION >= 11*/
301314
result[0] = memberName;
302315
result[1] = resultHandle;
303316
} catch (IllegalAccessException iae) {
@@ -306,7 +319,6 @@ private static final Object resolveInvokeDynamic(long j9class, String name, Stri
306319
throw new Error(nsme);
307320
}
308321
}
309-
/*[ENDIF] JAVA_SPEC_VERSION < 11 */
310322
return (Object)result;
311323
/*[ELSE] OPENJDK_METHODHANDLES*/
312324
MethodHandle result = null;
@@ -332,7 +344,7 @@ private static final Object resolveInvokeDynamic(long j9class, String name, Stri
332344
int bsmArgCount = UNSAFE.getShort(bsmData + BSM_ARGUMENT_COUNT_OFFSET);
333345
long bsmArgs = bsmData + BSM_ARGUMENTS_OFFSET;
334346
MethodHandle bsm = getCPMethodHandleAt(internalConstantPool, bsmIndex);
335-
if (null == bsm) {
347+
if (bsm == null) {
336348
/*[MSG "K05cd", "unable to resolve 'bootstrap_method_ref' in '{0}' at index {1}"]*/
337349
throw new NullPointerException(Msg.getString("K05cd", classObject.toString(), bsmIndex)); //$NON-NLS-1$
338350
}

runtime/vm/BytecodeInterpreter.hpp

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8865,29 +8865,23 @@ class INTERPRETER_CLASS
88658865
j9object_t invokeCacheArray = (ramConstantPool->ramClass->callSites)[index];
88668866

88678867
if (J9_EXPECTED(NULL != invokeCacheArray)) {
8868-
J9Class *clazz = J9OBJECT_CLAZZ(_currentThread, invokeCacheArray);
8869-
if (J9CLASS_IS_ARRAY(clazz)) {
8870-
/* Fetch target method and appendix from invokeCacheArray (2 element array)
8871-
* Stack transitions from:
8872-
* args <- SP
8873-
* MH
8874-
* To:
8875-
* invokeCacheArray[1] "appendix" <- SP
8876-
* args
8877-
* MH
8878-
*
8879-
* and sendMethod is ((J9Method *)((j.l.MemberName)invokeCacheArray[0]) + vmtargetOffset)
8880-
*/
8881-
j9object_t memberName = (j9object_t)J9JAVAARRAYOFOBJECT_LOAD(_currentThread, invokeCacheArray, 0);
8882-
_sendMethod = (J9Method *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, memberName, _vm->vmtargetOffset);
8868+
/* Fetch target method and appendix from invokeCacheArray (2 element array)
8869+
* Stack transitions from:
8870+
* args <- SP
8871+
* MH
8872+
* To:
8873+
* invokeCacheArray[1] "appendix" <- SP
8874+
* args
8875+
* MH
8876+
*
8877+
* and sendMethod is ((J9Method *)((j.l.MemberName)invokeCacheArray[0]) + vmtargetOffset)
8878+
*/
8879+
j9object_t memberName = (j9object_t)J9JAVAARRAYOFOBJECT_LOAD(_currentThread, invokeCacheArray, 0);
8880+
_sendMethod = (J9Method *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, memberName, _vm->vmtargetOffset);
88838881

8884-
j9object_t appendix = (j9object_t)J9JAVAARRAYOFOBJECT_LOAD(_currentThread, invokeCacheArray, 1);
8885-
if (NULL != appendix) {
8886-
*--_sp = (UDATA)appendix;
8887-
}
8888-
} else {
8889-
VM_VMHelpers::setExceptionPending(_currentThread, invokeCacheArray);
8890-
rc = GOTO_THROW_CURRENT_EXCEPTION;
8882+
j9object_t appendix = (j9object_t)J9JAVAARRAYOFOBJECT_LOAD(_currentThread, invokeCacheArray, 1);
8883+
if (NULL != appendix) {
8884+
*--_sp = (UDATA)appendix;
88918885
}
88928886
} else {
88938887
buildGenericSpecialStackFrame(REGISTER_ARGS, 0);

runtime/vm/resolvesupport.cpp

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2239,24 +2239,23 @@ resolveInvokeDynamic(J9VMThread *vmThread, J9ConstantPool *ramCP, UDATA callSite
22392239
/* Check if an exception is already pending */
22402240
if (vmThread->currentException != NULL) {
22412241
/* Already a pending exception */
2242-
result = vmThread->currentException;
2242+
result = NULL;
22432243
} else if (NULL == result) {
22442244
setCurrentExceptionUTF(vmThread, J9VMCONSTANTPOOL_JAVALANGNULLPOINTEREXCEPTION, NULL);
2245-
result = vmThread->currentException;
2246-
}
2247-
2248-
/* The result can be an array or exception. Ensure that the result and its elements are
2249-
* written/published before the result reference is stored.
2250-
*/
2251-
VM_AtomicSupport::writeBarrier();
2252-
2253-
J9MemoryManagerFunctions *gcFuncs = vmThread->javaVM->memoryManagerFunctions;
2254-
J9Class *j9class = J9_CLASS_FROM_CP(ramCP);
2255-
if (0 == gcFuncs->j9gc_objaccess_staticCompareAndSwapObject(vmThread, j9class, callSite, NULL, result)) {
2256-
/* Another thread beat this thread to updating the call site, ensure both threads
2257-
* return the same method handle.
2245+
} else {
2246+
/* The result is an array. Ensure that the result and its elements are
2247+
* written/published before the result reference is stored.
22582248
*/
2259-
result = *callSite;
2249+
VM_AtomicSupport::writeBarrier();
2250+
2251+
J9MemoryManagerFunctions *gcFuncs = vmThread->javaVM->memoryManagerFunctions;
2252+
J9Class *j9class = J9_CLASS_FROM_CP(ramCP);
2253+
if (0 == gcFuncs->j9gc_objaccess_staticCompareAndSwapObject(vmThread, j9class, callSite, NULL, result)) {
2254+
/* Another thread beat this thread to updating the call site, ensure both threads
2255+
* return the same method handle.
2256+
*/
2257+
result = *callSite;
2258+
}
22602259
}
22612260
#else /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */
22622261
/* Check if an exception is already pending */

test/functional/Jsr292/src/com/ibm/j9/jsr292/indyn/IndyTest.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -373,30 +373,33 @@ public void test_boostrap_return_constant_MethodType() {
373373
AssertJUnit.assertTrue(expected == mt);
374374
}
375375

376-
// test that if resolved CallSite is null, the same error is rethrown
376+
// Test to verify behaviour if a CallSite initially resolves to null
377377
@Test(groups = { "level.extended" })
378-
public void test_CallSiteNullErrorRethrown () {
378+
public void test_CallSiteNullErrorRethrown() {
379379
/* The bootstrap method associated with the indy call in test_CallSiteNullErrorRethrown
380380
* will return null the first time its called, and a valid CallSite for all repeat calls.
381381
*/
382382

383-
// Java 8: NullPointerException is expected on the first run
384-
// Java 11: BootstrapMethodError is expected on the first run
385383
try {
386384
com.ibm.j9.jsr292.indyn.GenIndyn.test_CallSiteNullErrorRethrown();
387385
Assert.fail("BootstrapMethodError or NullPointerException should be thrown.");
388-
} catch(BootstrapMethodError e) {
389-
Assert.assertTrue(VersionCheck.major() >= 11);
386+
} catch (BootstrapMethodError e) {
387+
// Java 8 (with OJDK MHs): BoostrapMethodError is expected on the first run
388+
// Java 11: BootstrapMethodError is expected on the first run
389+
Assert.assertTrue(
390+
(VersionCheck.major() >= 11)
391+
|| "true".equals(System.getProperty("openjdk.methodhandles", "false")));
390392
} catch (NullPointerException e) {
393+
// Java 8 (with OJ9 MHs): NullPointerException is expected on the first run
391394
Assert.assertTrue(VersionCheck.major() == 8);
392395
}
393396

394397
// Java 8: CallSite resolution is expected to succeed
395-
// Java 11 :The same BSME is expected on the second run
398+
// Java 11: The same BSME is expected on the second run
396399
try {
397400
com.ibm.j9.jsr292.indyn.GenIndyn.test_CallSiteNullErrorRethrown();
398401
Assert.assertTrue(VersionCheck.major() == 8);
399-
} catch ( java.lang.BootstrapMethodError e ) {
402+
} catch (java.lang.BootstrapMethodError e) {
400403
Assert.assertTrue(VersionCheck.major() >= 11);
401404
}
402405
}

0 commit comments

Comments
 (0)