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)
+    }
+}

Reply via email to