This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 53e1b68634 GROOVY-11842: avoid IndyInterface polluting trace (#2369)
53e1b68634 is described below
commit 53e1b68634587d0f6171f2b22a40d187911a5279
Author: Jochen Theodorou <[email protected]>
AuthorDate: Thu Jan 22 06:23:24 2026 +0100
GROOVY-11842: avoid IndyInterface polluting trace (#2369)
* ensure the first call of invokedynamic is not polluting the stack trace
* IndyUsageTest will no longer work since there will be no IndyInterface
lines in the trace anymore
* adding test for deprecated methods to help backwards compatibility
* cleanup in IndyInterface
---
.../codehaus/groovy/vmplugin/v8/IndyInterface.java | 79 ++++++++++++----
src/test/groovy/indy/IndyUsageTest.groovy | 40 --------
.../vmplugin/v8/IndyInterfaceDeprecatedTest.groovy | 105 +++++++++++++++++++++
3 files changed, 165 insertions(+), 59 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java
b/src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java
index e813e3217f..e3d9d8f5b1 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java
@@ -153,30 +153,31 @@ public class IndyInterface {
public static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
/**
- * handle for the fromCache method
+ * handle for the fromCacheHandle method
*/
- private static final MethodHandle FROM_CACHE_METHOD;
+ private static final MethodHandle FROM_CACHE_HANDLE_METHOD;
/**
- * handle for the selectMethod method
+ * handle for the selectMethodHandle method
*/
- private static final MethodHandle SELECT_METHOD;
+ private static final MethodHandle SELECT_METHOD_HANDLE_METHOD;
+
+ /**
+ * shared invoker for cached method handles
+ */
+ private static final MethodHandle CACHED_INVOKER;
static {
try {
- MethodType mt = MethodType.methodType(Object.class,
CacheableCallSite.class, Class.class, String.class, int.class, Boolean.class,
Boolean.class, Boolean.class, Object.class, Object[].class);
- FROM_CACHE_METHOD = LOOKUP.findStatic(IndyInterface.class,
"fromCache", mt);
+ MethodType handleMt = MethodType.methodType(MethodHandle.class,
CacheableCallSite.class, Class.class, String.class, int.class, Boolean.class,
Boolean.class, Boolean.class, Object.class, Object[].class);
+ FROM_CACHE_HANDLE_METHOD = LOOKUP.findStatic(IndyInterface.class,
"fromCacheHandle", handleMt);
+ SELECT_METHOD_HANDLE_METHOD =
LOOKUP.findStatic(IndyInterface.class, "selectMethodHandle", handleMt);
} catch (Exception e) {
throw new GroovyBugError(e);
}
- try {
- MethodType mt = MethodType.methodType(Object.class,
CacheableCallSite.class, Class.class, String.class, int.class, Boolean.class,
Boolean.class, Boolean.class, Object.class, Object[].class);
- SELECT_METHOD = LOOKUP.findStatic(IndyInterface.class,
"selectMethod", mt);
- } catch (Exception e) {
- throw new GroovyBugError(e);
- }
+ CACHED_INVOKER =
MethodHandles.exactInvoker(MethodType.methodType(Object.class, Object[].class));
}
protected static SwitchPoint switchPoint = new SwitchPoint();
@@ -254,19 +255,39 @@ public class IndyInterface {
* Makes a fallback method for an invalidated method selection
*/
protected static MethodHandle makeFallBack(MutableCallSite mc, Class<?>
sender, String name, int callID, MethodType type, boolean safeNavigation,
boolean thisCall, boolean spreadCall) {
- return make(mc, sender, name, callID, type, safeNavigation, thisCall,
spreadCall, SELECT_METHOD);
+ return makeBoothandle(mc, sender, name, callID, type, safeNavigation,
thisCall, spreadCall, SELECT_METHOD_HANDLE_METHOD);
}
/**
* Makes an adapter method for method selection, i.e. get the cached
methodhandle(fast path) or fallback
*/
private static MethodHandle makeAdapter(MutableCallSite mc, Class<?>
sender, String name, int callID, MethodType type, boolean safeNavigation,
boolean thisCall, boolean spreadCall) {
- return make(mc, sender, name, callID, type, safeNavigation, thisCall,
spreadCall, FROM_CACHE_METHOD);
+ return makeBoothandle(mc, sender, name, callID, type, safeNavigation,
thisCall, spreadCall, FROM_CACHE_HANDLE_METHOD);
}
- private static MethodHandle make(MutableCallSite mc, Class<?> sender,
String name, int callID, MethodType type, boolean safeNavigation, boolean
thisCall, boolean spreadCall, MethodHandle originalMH) {
- MethodHandle mh = MethodHandles.insertArguments(originalMH, 0, mc,
sender, name, callID, safeNavigation, thisCall, spreadCall, /*dummy receiver:*/
1);
- return mh.asCollector(Object[].class,
type.parameterCount()).asType(type);
+ private static MethodHandle makeBoothandle(MutableCallSite mc, Class<?>
sender, String name, int callID, MethodType type, boolean safeNavigation,
boolean thisCall, boolean spreadCall, MethodHandle handleReturningMh) {
+ // Step 1: bind site-constant arguments (incl dummy receiver marker)
+ MethodHandle fromCacheBound = MethodHandles.insertArguments(
+ handleReturningMh,
+ 0, mc, sender, name, callID,
+ safeNavigation, thisCall, spreadCall,
+ /*dummy receiver*/ 1
+ );
+ // fromCacheBound: (Object receiver, Object[] args) → MethodHandle
+
+ // Step 2: fold into the shared invoker (MethodHandle, Object[]) →
Object
+ MethodHandle boothandle = MethodHandles.foldArguments(
+ CACHED_INVOKER, // (MethodHandle, Object[]) → Object
+ fromCacheBound // (Object, Object[]) → MethodHandle
+ );
+ // boothandle: (Object receiver, Object[] args) → Object
+
+ // Step 3: adapt to callsite type: collect all arguments into Object[]
and then asType
+ boothandle = boothandle
+ .asCollector(Object[].class, type.parameterCount())
+ .asType(type);
+
+ return boothandle;
}
private static class FallbackSupplier {
@@ -304,8 +325,18 @@ public class IndyInterface {
/**
* Get the cached methodhandle. if the related methodhandle is not found
in the inline cache, cache and return it.
+ * @deprecated Use the new boothandle-based approach instead.
*/
+ @Deprecated
public static Object fromCache(CacheableCallSite callSite, Class<?>
sender, String methodName, int callID, Boolean safeNavigation, Boolean
thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws
Throwable {
+ MethodHandle mh = fromCacheHandle(callSite, sender, methodName,
callID, safeNavigation, thisCall, spreadCall, dummyReceiver, arguments);
+ return mh.invokeExact(arguments);
+ }
+
+ /**
+ * Get the cached methodhandle. if the related methodhandle is not found
in the inline cache, cache and return it.
+ */
+ private static MethodHandle fromCacheHandle(CacheableCallSite callSite,
Class<?> sender, String methodName, int callID, Boolean safeNavigation, Boolean
thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws
Throwable {
FallbackSupplier fallbackSupplier = new FallbackSupplier(callSite,
sender, methodName, callID, safeNavigation, thisCall, spreadCall,
dummyReceiver, arguments);
MethodHandleWrapper mhw =
@@ -341,7 +372,7 @@ public class IndyInterface {
mhw.resetLatestHitCount();
}
- return mhw.getCachedMethodHandle().invokeExact(arguments);
+ return mhw.getCachedMethodHandle();
}
private static boolean bypassCache(Boolean spreadCall, Object[] arguments)
{
@@ -352,8 +383,18 @@ public class IndyInterface {
/**
* Core method for indy method selection using runtime types.
+ * @deprecated Use the new boothandle-based approach instead.
*/
+ @Deprecated
public static Object selectMethod(CacheableCallSite callSite, Class<?>
sender, String methodName, int callID, Boolean safeNavigation, Boolean
thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws
Throwable {
+ MethodHandle mh = selectMethodHandle(callSite, sender, methodName,
callID, safeNavigation, thisCall, spreadCall, dummyReceiver, arguments);
+ return mh.invokeExact(arguments);
+ }
+
+ /**
+ * Core method for indy method selection using runtime types.
+ */
+ private static MethodHandle selectMethodHandle(CacheableCallSite callSite,
Class<?> sender, String methodName, int callID, Boolean safeNavigation, Boolean
thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws
Throwable {
MethodHandleWrapper mhw = fallback(callSite, sender, methodName,
callID, safeNavigation, thisCall, spreadCall, dummyReceiver, arguments);
MethodHandle defaultTarget = callSite.getDefaultTarget();
@@ -370,7 +411,7 @@ public class IndyInterface {
doWithCallSite(callSite, arguments, (cs, receiver) ->
cs.put(receiver.getClass().getName(), mhw));
}
- return mhw.getCachedMethodHandle().invokeExact(arguments);
+ return mhw.getCachedMethodHandle();
}
private static MethodHandleWrapper fallback(CacheableCallSite callSite,
Class<?> sender, String methodName, int callID, Boolean safeNavigation, Boolean
thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) {
diff --git a/src/test/groovy/indy/IndyUsageTest.groovy
b/src/test/groovy/indy/IndyUsageTest.groovy
deleted file mode 100644
index 5e95ca7724..0000000000
--- a/src/test/groovy/indy/IndyUsageTest.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the 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.
- */
-package indy
-
-import org.junit.Test
-
-import static groovy.test.GroovyAssert.assertScript
-
-final class IndyUsageTest {
-
- @Test
- void testIndyIsUsedNested() {
- assertScript '''
- def foo() {
- throw new Exception('blah')
- }
- try {
- foo()
- } catch (e) {
- assert e.stackTrace.find { it.className ==
'org.codehaus.groovy.vmplugin.v8.IndyInterface' }
- }
- '''
- }
-}
diff --git
a/src/test/groovy/org/codehaus/groovy/vmplugin/v8/IndyInterfaceDeprecatedTest.groovy
b/src/test/groovy/org/codehaus/groovy/vmplugin/v8/IndyInterfaceDeprecatedTest.groovy
new file mode 100644
index 0000000000..d7a50dec72
--- /dev/null
+++
b/src/test/groovy/org/codehaus/groovy/vmplugin/v8/IndyInterfaceDeprecatedTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the 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.
+ */
+package org.codehaus.groovy.vmplugin.v8
+
+import org.junit.jupiter.api.Test
+
+import java.lang.invoke.MethodHandles
+import java.lang.invoke.MethodType
+
+import static org.junit.jupiter.api.Assertions.assertEquals
+
+final class IndyInterfaceDeprecatedTest {
+
+ private String foo() {
+ return "foo-result"
+ }
+
+ @Test
+ void testSelectMethodDeprecatedInstanceFoo() {
+ // Prepare call site type: (IndyInterfaceDeprecatedTest) -> Object
+ MethodHandles.Lookup lookup = MethodHandles.lookup()
+ MethodType type = MethodType.methodType(Object,
IndyInterfaceDeprecatedTest)
+ CacheableCallSite callSite = new CacheableCallSite(type, lookup)
+
+ // Provide non-null default/fallback targets (needed for guards in
Selector)
+ def dummyTarget = MethodHandles.dropArguments(
+ MethodHandles.constant(Object, null), 0, type.parameterArray()
+ )
+ callSite.defaultTarget = dummyTarget
+ callSite.fallbackTarget = dummyTarget
+
+ // Prepare invocation arguments
+ def receiver = new IndyInterfaceDeprecatedTest()
+ Object[] args = [receiver] as Object[]
+
+ // Call deprecated selectMethod with the requested flags
+ int callID = IndyInterface.CallType.METHOD.getOrderNumber() //
expected to be 0
+ Object result = IndyInterface.selectMethod(
+ callSite,
+ IndyInterfaceDeprecatedTest, // sender
+ 'foo', // methodName
+ callID,
+ Boolean.FALSE, // safeNavigation
+ Boolean.TRUE, // thisCall (instance method)
+ Boolean.FALSE, // spreadCall
+ 1, // dummyReceiver (marker only)
+ args
+ )
+
+ // Verify the local method foo was actually called
+ assertEquals(receiver.foo(), result)
+ }
+
+ @Test
+ void testFromCacheDeprecatedInstanceFoo() {
+ // Prepare call site type: (IndyInterfaceDeprecatedTest) -> Object
+ MethodHandles.Lookup lookup = MethodHandles.lookup()
+ MethodType type = MethodType.methodType(Object,
IndyInterfaceDeprecatedTest)
+ CacheableCallSite callSite = new CacheableCallSite(type, lookup)
+
+ // Provide non-null default/fallback targets (needed for guards in
Selector)
+ def dummyTarget = MethodHandles.dropArguments(
+ MethodHandles.constant(Object, null), 0, type.parameterArray()
+ )
+ callSite.defaultTarget = dummyTarget
+ callSite.fallbackTarget = dummyTarget
+
+ // Prepare invocation arguments
+ def receiver = new IndyInterfaceDeprecatedTest()
+ Object[] args = [receiver] as Object[]
+
+ // Call deprecated fromCache with the requested flags
+ int callID = IndyInterface.CallType.METHOD.getOrderNumber() //
expected to be 0
+ Object result = IndyInterface.fromCache(
+ callSite,
+ IndyInterfaceDeprecatedTest, // sender
+ 'foo', // methodName
+ callID,
+ Boolean.FALSE, // safeNavigation
+ Boolean.TRUE, // thisCall (instance method)
+ Boolean.FALSE, // spreadCall
+ 1, // dummyReceiver (marker only)
+ args
+ )
+
+ // Verify the local method foo was actually called
+ assertEquals(receiver.foo(), result)
+ }
+}