From d0030175c8eeb0ab78824b59adeea6663d4cb118 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Fri, 17 Apr 2026 19:11:49 -0500 Subject: [PATCH] GROOVY-11929: drop abstract methods from methodsForSuper if no MOP match --- src/main/java/groovy/lang/MetaClassImpl.java | 12 ++-- .../runtime/metaclass/MetaMethodIndex.java | 10 ++-- src/test/groovy/bugs/Groovy11929.groovy | 58 +++++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 src/test/groovy/bugs/Groovy11929.groovy diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java index 3afaf40f471..7fb33f371ca 100644 --- a/src/main/java/groovy/lang/MetaClassImpl.java +++ b/src/main/java/groovy/lang/MetaClassImpl.java @@ -518,8 +518,7 @@ public void methodNameAction(final Class c, final MetaMethodIndex.Cache e) { int matchedMethod = mopArrayIndex(method, c); if (matchedMethod >= 0) { methods.set(i, mopMethods[matchedMethod]); - } else if (!useThis && !isDGM(method) && (isBridge(method) - || c == method.getDeclaringClass().getTheClass())) { + } else if (!useThis && notSuperUseful(method, c)) { methods.remove(i--); // not fit for super usage } } @@ -534,8 +533,7 @@ public void methodNameAction(final Class c, final MetaMethodIndex.Cache e) { if (matchedMethod >= 0) { if (useThis) e.methods = mopMethods[matchedMethod]; else e.methodsForSuper = mopMethods[matchedMethod]; - } else if (!useThis && !isDGM(method) && (isBridge(method) - || c == method.getDeclaringClass().getTheClass())) { + } else if (!useThis && notSuperUseful(method, c)) { e.methodsForSuper = null; // not fit for super usage } } @@ -576,6 +574,12 @@ private int mopArrayIndex(final MetaMethod method, final String mopName) { return -1; } + private boolean notSuperUseful(final MetaMethod method, final Class c) { + return !isDGM(method) && (isBridge(method) + || method.isAbstract() // GROOVY-11929 + || c == method.getDeclaringClass().getTheClass()); + } + private boolean isBridge(final MetaMethod method) { return (method.getModifiers() & Opcodes.ACC_BRIDGE) != 0; } diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaMethodIndex.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaMethodIndex.java index 983baacc443..57df90a4154 100644 --- a/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaMethodIndex.java +++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/MetaMethodIndex.java @@ -207,13 +207,15 @@ public Object addMethodToList(final Object o, final MetaMethod toIndex) { * should be kept. */ private static boolean isOverridden(final MetaMethod inIndex, final MetaMethod toIndex) { - // do not overwrite private methods - if (inIndex.isPrivate()) return false; + // don't overwrite private method + if (inIndex.isPrivate()) { + return false; + } CachedClass inIndexDC = inIndex.getDeclaringClass(); CachedClass toIndexDC = toIndex.getDeclaringClass(); if (inIndexDC == toIndexDC) { - return isNonRealMethod(toIndex) || inIndex.isSynthetic(); // GROOVY-10136, GROOVY-10594 + return inIndex.isSynthetic() || isNonRealMethod(toIndex); // GROOVY-10136, GROOVY-10594 } // interface vs instance method; be careful... @@ -221,7 +223,7 @@ private static boolean isOverridden(final MetaMethod inIndex, final MetaMethod t && inIndexDC.isInterface() != toIndexDC.isInterface() && !(toIndex instanceof ClosureMetaMethod || toIndex instanceof ClosureStaticMetaMethod)) { // GROOVY-3493 // this is the old logic created for GROOVY-2391 and GROOVY-7879, which was labeled as "do not overwrite interface methods with instance methods" - return (isNonRealMethod(inIndex) || !inIndexDC.isInterface() || toIndexDC.isInterface()) && !toIndexDC.isAssignableFrom(inIndexDC.getTheClass()); + return (toIndexDC.isInterface() || !inIndexDC.isInterface() || isNonRealMethod(inIndex)) && !toIndexDC.isAssignableFrom(inIndexDC.getTheClass()); } // prefer most-specific or most-recent for type disjunction diff --git a/src/test/groovy/bugs/Groovy11929.groovy b/src/test/groovy/bugs/Groovy11929.groovy new file mode 100644 index 00000000000..06429a47dd8 --- /dev/null +++ b/src/test/groovy/bugs/Groovy11929.groovy @@ -0,0 +1,58 @@ +/* + * 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.junit.jupiter.api.Test + +import static groovy.test.GroovyAssert.assertScript + +final class Groovy11929 { + + @Test + void testInvokeSpecial() { + assertScript ''' + interface Dao { + T save(T t) + } + + abstract class AbstractDao implements Dao { + @Override + T save(T t) { t } + } + + interface EntityDao extends Dao { + @Override + Entity save(Entity entity) // not replaced by super$2$save(Object) + } + + class EntityDaoImpl extends AbstractDao implements EntityDao { + @Override + Entity save(Entity entity) { super.save(entity) } + } + + class Entity { + long id + } + + def entity = new Entity() + def dao = new EntityDaoImpl() + assert dao.save(entity) === entity + ''' + } +}