This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY-5359
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 78ebdc7c120587c85623b0fb668f942edc469612
Author: Eric Milles <[email protected]>
AuthorDate: Thu Jan 22 20:54:41 2026 -0600

    GROOVY-5359: check super class static method before callable property
---
 src/main/java/groovy/lang/MetaClassImpl.java | 123 +++++++++++----------------
 src/test/groovy/bugs/Groovy5359.groovy       |  64 ++++++++++++++
 2 files changed, 114 insertions(+), 73 deletions(-)

diff --git a/src/main/java/groovy/lang/MetaClassImpl.java 
b/src/main/java/groovy/lang/MetaClassImpl.java
index 2dcd9c6ca0..0bffa6f2f3 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -1437,107 +1437,84 @@ public class MetaClassImpl implements MetaClass, 
MutableMetaClass {
         return null;
     }
 
-    public MetaMethod retrieveStaticMethod(String methodName, Object[] 
arguments) {
-        final MetaMethodIndex.Cache e = metaMethodIndex.getMethods(theClass, 
methodName);
-        MetaMethodIndex.MetaMethodCache cacheEntry;
+    public MetaMethod getMethodWithoutCaching(Class sender, String methodName, 
Class[] arguments, boolean isCallToSuper) {
+        MetaMethod method = null;
+        Object methods = getMethods(sender, methodName, isCallToSuper);
+        if (methods != null) {
+            method = (MetaMethod) chooseMethod(methodName, methods, arguments);
+        }
+        return method;
+    }
+
+    public MetaMethod retrieveStaticMethod(final String methodName, final 
Object[] arguments) {
+        MetaMethodIndex.Cache e = metaMethodIndex.getMethods(theClass, 
methodName);
         if (e != null) {
-            cacheEntry = e.cachedStaticMethod;
+            MetaMethodIndex.MetaMethodCache cacheEntry = e.cachedStaticMethod;
 
-            if (cacheEntry != null &&
-                    MetaClassHelper.sameClasses(cacheEntry.params, arguments, 
e.staticMethods instanceof MetaMethod)) {
+            if (cacheEntry != null && 
MetaClassHelper.sameClasses(cacheEntry.params, arguments, e.staticMethods 
instanceof MetaMethod)) {
                 return cacheEntry.method;
             }
 
-            final Class[] classes = 
MetaClassHelper.convertToTypeArray(arguments);
+            Class<?>[] classes = MetaClassHelper.convertToTypeArray(arguments);
             cacheEntry = new MetaMethodIndex.MetaMethodCache(classes, 
pickStaticMethod(methodName, classes));
 
             e.cachedStaticMethod = cacheEntry;
 
             return cacheEntry.method;
         }
-        return pickStaticMethod(methodName, 
MetaClassHelper.convertToTypeArray(arguments));
-    }
 
-    public MetaMethod getMethodWithoutCaching(Class sender, String methodName, 
Class[] arguments, boolean isCallToSuper) {
-        MetaMethod method = null;
-        Object methods = getMethods(sender, methodName, isCallToSuper);
-        if (methods != null) {
-            method = (MetaMethod) chooseMethod(methodName, methods, arguments);
-        }
-        return method;
+        return pickStaticMethod(methodName, 
MetaClassHelper.convertToTypeArray(arguments));
     }
 
     @Override
-    public Object invokeStaticMethod(Object object, String methodName, 
Object[] arguments) {
+    public Object invokeStaticMethod(final Object object, final String 
methodName, final Object[] arguments) {
         checkInitalised();
 
-        final Class sender = object instanceof Class ? (Class) object : 
object.getClass();
-        if (sender != theClass) {
-            MetaClass mc = registry.getMetaClass(sender);
-            return mc.invokeStaticMethod(sender, methodName, arguments);
-        }
-        if (sender == Class.class) {
-            return invokeMethod(object, methodName, arguments);
+        {
+            var sender = object instanceof Class ? (Class<?>) object : 
object.getClass();
+            if (sender != theClass) { MetaClass mc = 
registry.getMetaClass(sender);
+                return mc.invokeStaticMethod(sender, methodName, arguments);
+            }
+            if (sender == Class.class) {
+                return invokeMethod(object, methodName, arguments);
+            }
         }
 
-        if (arguments == null) arguments = EMPTY_ARGUMENTS;
+        Object[] nonNullArguments = arguments != null ? arguments.clone() : 
EMPTY_ARGUMENTS;
 
-        MetaMethod method = retrieveStaticMethod(methodName, arguments);
         // let's try to use the cache to find the method
-
+        MetaMethod method = retrieveStaticMethod(methodName, nonNullArguments);
         if (method != null) {
-            MetaClassHelper.unwrap(arguments);
-            return method.doMethodInvoke(object, arguments);
-        }
-        Object prop = null;
-        try {
-            prop = getProperty(theClass, theClass, methodName, false, false);
-        } catch (MissingPropertyException mpe) {
-            // ignore
+            MetaClassHelper.unwrap(nonNullArguments);
+            return method.doMethodInvoke(object, nonNullArguments);
         }
 
-        if (prop instanceof Closure) {
-            return invokeStaticClosureProperty(arguments, prop);
-        }
+        Class<?>[] argumentTypes = 
MetaClassHelper.convertToTypeArray(nonNullArguments);
+        MetaClassHelper.unwrap(nonNullArguments);
 
-        Object[] originalArguments = arguments.clone();
-        MetaClassHelper.unwrap(arguments);
-
-        Class superClass = sender.getSuperclass();
-        Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
-        while (superClass != Object.class && superClass != null) {
+        for (var superClass = theClass.getSuperclass(); superClass != null; 
superClass = superClass.getSuperclass()) {
             MetaClass mc = registry.getMetaClass(superClass);
-            method = mc.getStaticMetaMethod(methodName, argClasses);
-            if (method != null) return method.doMethodInvoke(object, 
arguments);
-
-            try {
-                prop = mc.getProperty(superClass, superClass, methodName, 
false, false);
-            } catch (MissingPropertyException mpe) {
-                // ignore
-            }
-
-            if (prop instanceof Closure) {
-                return invokeStaticClosureProperty(originalArguments, prop);
-            }
-
-            superClass = superClass.getSuperclass();
+            method = mc.getStaticMetaMethod(methodName, argumentTypes);
+            if (method != null) return method.doMethodInvoke(object, 
nonNullArguments);
         }
 
-        if (prop != null) {
-            MetaClass propMC = registry.getMetaClass(prop.getClass());
-            return propMC.invokeMethod(prop, CALL_METHOD, arguments);
+        Object propertyValue = null; // GROOVY-3284, GROOVY-3422, GROOVY-9779
+        try {
+            propertyValue = getProperty(theClass, theClass, methodName, false, 
false);
+        } catch (MissingPropertyException ignore) {
+        }
+        if (propertyValue != null) {
+            if (propertyValue instanceof Closure closure) {
+                return closure.getMetaClass().invokeMethod(theClass, closure, 
DO_CALL_METHOD, arguments, false, false);
+            } else {
+                return 
registry.getMetaClass(propertyValue.getClass()).invokeMethod(propertyValue, 
CALL_METHOD, arguments);
+            }
         }
 
-        return invokeStaticMissingMethod(sender, methodName, arguments);
-    }
-
-    private static Object invokeStaticClosureProperty(Object[] 
originalArguments, Object prop) {
-        Closure closure = (Closure) prop;
-        MetaClass delegateMetaClass = closure.getMetaClass();
-        return delegateMetaClass.invokeMethod(closure.getClass(), closure, 
DO_CALL_METHOD, originalArguments, false, false);
+        return invokeStaticMissingMethod(theClass, methodName, 
nonNullArguments);
     }
 
-    private Object invokeStaticMissingMethod(Class sender, String methodName, 
Object[] arguments) {
+    private Object invokeStaticMissingMethod(final Class<?> sender, final 
String methodName, final Object[] arguments) {
         MetaMethod metaMethod = getStaticMetaMethod(STATIC_METHOD_MISSING, 
METHOD_MISSING_ARGS);
         if (metaMethod != null) {
             return metaMethod.invoke(sender, new Object[]{methodName, 
arguments});
@@ -1545,24 +1522,24 @@ public class MetaClassImpl implements MetaClass, 
MutableMetaClass {
         throw new MissingMethodException(methodName, sender, arguments, true);
     }
 
-    private MetaMethod pickStaticMethod(String methodName, Class[] arguments) 
throws MethodSelectionException {
+    private MetaMethod pickStaticMethod(final String methodName, final 
Class<?>[] argumentTypes) throws MethodSelectionException {
         MetaMethod method = null;
         MethodSelectionException mse = null;
         Object methods = getStaticMethods(theClass, methodName);
 
         if (!(methods instanceof FastArray) || !((FastArray) 
methods).isEmpty()) {
             try {
-                method = (MetaMethod) chooseMethod(methodName, methods, 
arguments);
+                method = (MetaMethod) chooseMethod(methodName, methods, 
argumentTypes);
             } catch (MethodSelectionException msex) {
                 mse = msex;
             }
         }
         if (method == null && theClass != Class.class) {
             MetaClass cmc = registry.getMetaClass(Class.class);
-            method = cmc.pickMethod(methodName, arguments);
+            method = cmc.pickMethod(methodName, argumentTypes);
         }
         if (method == null) {
-            method = (MetaMethod) chooseMethod(methodName, methods, arguments);
+            method = (MetaMethod) chooseMethod(methodName, methods, 
argumentTypes);
         }
 
         if (method == null && mse != null) {
diff --git a/src/test/groovy/bugs/Groovy5359.groovy 
b/src/test/groovy/bugs/Groovy5359.groovy
new file mode 100644
index 0000000000..684c4de372
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy5359.groovy
@@ -0,0 +1,64 @@
+/*
+ *  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 bugs
+
+import org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack
+import org.junit.jupiter.api.Test
+
+final class Groovy5359 {
+
+    static class A {
+        static method() { 'A' }
+    }
+
+    static class B extends A {
+    }
+
+    static class C extends A {
+    }
+
+    @groovy.transform.CompileStatic
+    private long measure(Number count, Closure block) {
+        long t0 = System.currentTimeMillis()
+        count.times(block)
+        System.currentTimeMillis() - t0
+    }
+
+    @Test
+    void testStaticMethodOfSuperClass() {
+        ExpandoMetaClass.enableGlobally()
+        try {
+            int count = 0
+            C.metaClass.static.propertyMissing = { name ->
+                count += 1
+                throw new MissingPropertyExceptionNoStack('static property 
missing', getDelegate())
+            }
+
+            assert B.method() == 'A'
+            assert C.method() == 'A'
+            assert count == 0
+
+            assert measure(1e6) { B.method() } < 1000 // was ~26000 with 
property check
+            assert measure(1e6) { C.method() } < 1000 // was ~26000 with 
property check
+            assert count == 0
+        } finally {
+            ExpandoMetaClass.disableGlobally()
+        }
+    }
+}

Reply via email to