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 bc6d7c4a05a644d4d55717c36592a7a4763f5e09 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..da1f2b1a1e --- /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() } < 1500 // was ~26000 with property check + assert measure(1e6) { C.method() } < 1500 // was ~26000 with property check + assert count == 0 + } finally { + ExpandoMetaClass.disableGlobally() + } + } +}
