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

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


The following commit(s) were added to refs/heads/GROOVY_5_0_X by this push:
     new 1f001e6fba GROOVY-11758: detect final super method that shadows 
interface method
1f001e6fba is described below

commit 1f001e6fba40fa40e6f86b9fef98043dc6490e02
Author: Eric Milles <[email protected]>
AuthorDate: Fri Sep 19 19:19:48 2025 -0500

    GROOVY-11758: detect final super method that shadows interface method
---
 .../groovy/classgen/ClassCompletionVerifier.java   | 96 ++++++++++++++--------
 .../transform/trait/TraitASTTransformation.java    |  8 +-
 .../groovy/transform/trait/TraitComposer.java      | 42 +++++-----
 src/test/groovy/groovy/InterfaceTest.groovy        | 16 ++++
 .../traitx/TraitASTTransformationTest.groovy       |  2 +-
 5 files changed, 101 insertions(+), 63 deletions(-)

diff --git 
a/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java 
b/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
index c470489b99..7caca8c80b 100644
--- a/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
@@ -396,11 +396,72 @@ public class ClassCompletionVerifier extends 
ClassCodeVisitorSupport {
     }
 
     private void checkMethodsForWeakerAccess(final ClassNode cn) {
-        for (MethodNode method : cn.getMethods()) {
-            checkMethodForWeakerAccessPrivileges(method, cn);
+        for (MethodNode cnMethod : cn.getMethods()) {
+            if (!cnMethod.isPublic() && !cnMethod.isStatic()) {
+            sc: for (MethodNode scMethod : 
cn.getSuperClass().getMethods(cnMethod.getName())) {
+                    if (!scMethod.isStatic()
+                            && !scMethod.isPrivate()
+                            && (cnMethod.isPrivate()
+                            || (cnMethod.isProtected() && scMethod.isPublic())
+                            || (cnMethod.isPackageScope() && 
(scMethod.isPublic() || scMethod.isProtected())))) {
+                        if 
(ParameterUtils.parametersEqual(cnMethod.getParameters(), 
scMethod.getParameters())) {
+                            addWeakerAccessError(cn, cnMethod, scMethod);
+                            break sc;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Verifier: checks weaker access of cn's methods against abstract or 
default interface methods
+
+        // GROOVY-11758: check for non-public final super class method that 
shadows an interface method
+        Map<String, MethodNode> interfaceMethods = 
ClassNodeUtils.getDeclaredMethodsFromInterfaces(cn);
+        if (!interfaceMethods.isEmpty()) {
+            for (MethodNode cnMethod : cn.getMethods()) {
+                if (!cnMethod.isPrivate() && !cnMethod.isStatic()) {
+                    interfaceMethods.remove(cnMethod.getTypeDescriptor());
+                }
+            }
+            for (MethodNode publicMethod : interfaceMethods.values()) {
+                for (MethodNode scMethod : 
cn.getSuperClass().getMethods(publicMethod.getName())) {
+                    if (scMethod.isFinal() && !scMethod.isPublic() && 
!scMethod.isPrivate() && !scMethod.isStatic()
+                            && 
ParameterUtils.parametersEqual(scMethod.getParameters(), 
publicMethod.getParameters())) {
+                        addWeakerAccessError2(cn, scMethod, publicMethod);
+                    }
+                }
+            }
         }
     }
 
+    private void addWeakerAccessError(final ClassNode cn, final MethodNode 
cnMethod, final MethodNode scMethod) {
+        StringBuilder msg = new StringBuilder();
+        msg.append(cnMethod.getName());
+        appendParamsDescription(cnMethod.getParameters(), msg);
+        msg.append(" in ");
+        msg.append(cn.getName());
+        msg.append(" cannot override ");
+        msg.append(scMethod.getName());
+        msg.append(" in ");
+        msg.append(scMethod.getDeclaringClass().getName());
+        msg.append("; attempting to assign weaker access privileges; was ");
+        msg.append(scMethod.isPublic() ? "public" : (scMethod.isProtected() ? 
"protected" : "package-private"));
+
+        addError(msg.toString(), cnMethod);
+    }
+
+    private void addWeakerAccessError2(final ClassNode cn, final MethodNode 
scMethod, final MethodNode ifMethod) {
+        StringBuilder msg = new StringBuilder();
+        msg.append("inherited final method ");
+        msg.append(scMethod.getName());
+        appendParamsDescription(scMethod.getParameters(), msg);
+        msg.append(" from ");
+        msg.append(scMethod.getDeclaringClass().getName());
+        msg.append(" cannot shadow the public method in ");
+        msg.append(ifMethod.getDeclaringClass().getName());
+        addError(msg.toString(), cn);
+    }
+
     private void checkMethodsForOverridingFinal(final ClassNode cn) {
         final int skips = ACC_SYNTHETIC | ACC_STATIC | ACC_PRIVATE;
         for (MethodNode method : cn.getMethods()) {
@@ -507,22 +568,6 @@ out:        for (ClassNode sc : superTypes) {
         msg.append(')');
     }
 
-    private void addWeakerAccessError(final ClassNode cn, final MethodNode 
method, final Parameter[] parameters, final MethodNode superMethod) {
-        StringBuilder msg = new StringBuilder();
-        msg.append(method.getName());
-        appendParamsDescription(parameters, msg);
-        msg.append(" in ");
-        msg.append(cn.getName());
-        msg.append(" cannot override ");
-        msg.append(superMethod.getName());
-        msg.append(" in ");
-        msg.append(superMethod.getDeclaringClass().getName());
-        msg.append("; attempting to assign weaker access privileges; was ");
-        msg.append(superMethod.isPublic() ? "public" : 
(superMethod.isProtected() ? "protected" : "package-private"));
-
-        addError(msg.toString(), method);
-    }
-
     @Override
     public void visitMethod(final MethodNode node) {
         inConstructor = false;
@@ -567,21 +612,6 @@ out:        for (ClassNode sc : superTypes) {
         }
     }
 
-    private void checkMethodForWeakerAccessPrivileges(final MethodNode mn, 
final ClassNode cn) {
-        if (mn.isPublic()) return;
-        Parameter[] params = mn.getParameters();
-        for (MethodNode superMethod : 
cn.getSuperClass().getMethods(mn.getName())) {
-            Parameter[] superParams = superMethod.getParameters();
-            if (!ParameterUtils.parametersEqual(params, superParams)) continue;
-            if ((mn.isPrivate() && !superMethod.isPrivate())
-                    || (mn.isProtected() && !superMethod.isProtected() && 
!superMethod.isPackageScope() && !superMethod.isPrivate())
-                    || (!mn.isPrivate() && !mn.isProtected() && !mn.isPublic() 
&& (superMethod.isPublic() || superMethod.isProtected()))) {
-                addWeakerAccessError(cn, mn, params, superMethod);
-                return;
-            }
-        }
-    }
-
     private void checkOverloadingPrivateAndPublic(final MethodNode node) {
         if (isStaticallyCompiled(currentClass)) return; // GROOVY-11627
         boolean mixed = false;
diff --git 
a/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java 
b/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
index 50cf1eac1f..aea4019036 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
@@ -507,13 +507,7 @@ public class TraitASTTransformation extends 
AbstractASTTransformation implements
     }
 
     private static boolean methodNeedsReplacement(final ClassNode cNode, final 
MethodNode mNode) {
-        // no method found, we need to replace
-        if (mNode == null) return true;
-        // method is in current class, nothing to be done
-        if (mNode.getDeclaringClass() == cNode) return false;
-        // do not overwrite final
-        if ((mNode.getModifiers() & ACC_FINAL) != 0) return false;
-        return true;
+        return mNode == null || mNode.getDeclaringClass() != cNode; // do not 
replace trait method
     }
 
     private void processField(final FieldNode field, final MethodNode 
initializer, final MethodNode staticInitializer,
diff --git 
a/src/main/java/org/codehaus/groovy/transform/trait/TraitComposer.java 
b/src/main/java/org/codehaus/groovy/transform/trait/TraitComposer.java
index c0dc22ba09..a8e6f10769 100644
--- a/src/main/java/org/codehaus/groovy/transform/trait/TraitComposer.java
+++ b/src/main/java/org/codehaus/groovy/transform/trait/TraitComposer.java
@@ -93,12 +93,14 @@ public abstract class TraitComposer {
      * to call this method on a class node which does not implement a trait.
      */
     public static void doExtendTraits(final ClassNode cn, final SourceUnit su, 
final CompilationUnit cu) {
-        if (cn.isInterface()) return;
+        if (cn.isInterface()) {
+            return;
+        }
         if (Traits.isTrait(cn)) {
             checkTraitAllowed(cn, su);
             return;
         }
-        if (!cn.getNameWithoutPackage().endsWith(Traits.TRAIT_HELPER)) {
+        if (!cn.getName().endsWith(Traits.TRAIT_HELPER)) {
             GroovyClassVisitor visitor = new SuperCallTraitTransformer(su);
             for (ClassNode trait : Traits.findTraits(cn)) {
                 applyTrait(trait, cn, Traits.findHelpers(trait), su);
@@ -119,13 +121,13 @@ public abstract class TraitComposer {
         }
     }
 
-    private static void applyTrait(final ClassNode trait, final ClassNode 
cNode, final TraitHelpersTuple helpers, SourceUnit unit) {
+    private static void applyTrait(final ClassNode trait, final ClassNode 
cNode, final TraitHelpersTuple helpers, final SourceUnit unit) {
         ClassNode helperClassNode = helpers.getHelper();
         ClassNode fieldHelperClassNode = helpers.getFieldHelper();
         ClassNode staticFieldHelperClassNode = helpers.getStaticFieldHelper();
         Map<String, ClassNode> genericsSpec = 
GenericsUtils.createGenericsSpec(trait, 
GenericsUtils.createGenericsSpec(cNode));
 
-        List<MethodNode> hMethods = new 
ArrayList<>(helperClassNode.getMethods());
+        var hMethods = new ArrayList<MethodNode>(helperClassNode.getMethods());
         if (hMethods.size() > 1) {
             
hMethods.sort(Comparator.comparing(MethodNodeUtils::methodDescriptorWithoutReturnType));
         }
@@ -371,19 +373,15 @@ public abstract class TraitComposer {
         forwarder.addAnnotation(bridgeAnnotation);
 
         MethodNode existingMethod = findExistingMethod(targetNode, forwarder);
-        if (existingMethod != null) {
-            if (!forwarder.isStatic() && existingMethod.isStatic()) {
-                // found an existing static method that is going to conflict 
with interface
-                unit.addError(createException(trait, targetNode, forwarder, 
existingMethod));
-                return;
+        if (existingMethod != null && existingMethod.isStatic() && 
!forwarder.isStatic()) {
+            // found an existing static method that is going to conflict with 
interface
+            unit.addError(createException(trait, targetNode, forwarder, 
existingMethod));
+        } else {
+            if (!shouldSkipMethod(targetNode, forwarder.getName(), 
forwarderParams)) {
+                targetNode.addMethod(forwarder);
             }
+            createSuperForwarder(targetNode, forwarder, genericsSpec);
         }
-
-        if (!shouldSkipMethod(targetNode, forwarder.getName(), 
forwarderParams)) {
-            targetNode.addMethod(forwarder);
-        }
-
-        createSuperForwarder(targetNode, forwarder, genericsSpec);
     }
 
     private static SyntaxException createException(ClassNode trait, ClassNode 
targetNode, MethodNode forwarder, MethodNode existingMethod) {
@@ -414,10 +412,10 @@ public abstract class TraitComposer {
         return new SyntaxException(message, errorTarget);
     }
 
-    private static GenericsType[] removeNonPlaceHolders(GenericsType[] 
oldTypes) {
-        if (oldTypes==null || oldTypes.length==0) return oldTypes;
-        ArrayList<GenericsType> l = new ArrayList<>(Arrays.asList(oldTypes));
-        Iterator<GenericsType> it = l.iterator();
+    private static GenericsType[] removeNonPlaceHolders(final GenericsType[] 
oldTypes) {
+        if (oldTypes == null || oldTypes.length == 0) return oldTypes;
+        var list = new ArrayList<GenericsType>(Arrays.asList(oldTypes));
+        Iterator<GenericsType> it = list.iterator();
         boolean modified = false;
         while (it.hasNext()) {
             GenericsType gt = it.next();
@@ -427,8 +425,8 @@ public abstract class TraitComposer {
             }
         }
         if (!modified) return oldTypes;
-        if (l.isEmpty()) return null;
-        return l.toArray(GenericsType.EMPTY_ARRAY);
+        if (list.isEmpty()) return null;
+        return list.toArray(GenericsType[]::new);
     }
 
     /**
@@ -436,7 +434,7 @@ public abstract class TraitComposer {
      * @param forwarder a forwarder method
      * @param genericsSpec
      */
-    private static void createSuperForwarder(ClassNode targetNode, MethodNode 
forwarder, final Map<String,ClassNode> genericsSpec) {
+    private static void createSuperForwarder(final ClassNode targetNode, final 
MethodNode forwarder, final Map<String,ClassNode> genericsSpec) {
         List<ClassNode> interfaces = new 
ArrayList<>(Traits.collectAllInterfacesReverseOrder(targetNode, new 
LinkedHashSet<>()));
         String name = forwarder.getName();
         Parameter[] forwarderParameters = forwarder.getParameters();
diff --git a/src/test/groovy/groovy/InterfaceTest.groovy 
b/src/test/groovy/groovy/InterfaceTest.groovy
index 2b55ab0cf0..629c141552 100644
--- a/src/test/groovy/groovy/InterfaceTest.groovy
+++ b/src/test/groovy/groovy/InterfaceTest.groovy
@@ -135,6 +135,22 @@ final class InterfaceTest extends CompilableTestSupport {
         '''
     }
 
+    // GROOVY-11758
+    void testSuperClassFinalMethodAndInterfaceMethod() {
+        def err = shouldNotCompile '''
+            interface A {
+                def m()
+            }
+            class B {
+                protected final m() {
+                }
+            }
+            class C extends B implements A {
+            }
+        '''
+        assert err =~ /inherited final method m\(\) from B cannot shadow the 
public method in A/
+    }
+
     // GROOVY-11753
     void testSuperClassCovariantOfParameterizedInterface() {
         assertScript '''
diff --git 
a/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
 
b/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
index b697369a4f..194d8fbf22 100644
--- 
a/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
+++ 
b/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
@@ -441,7 +441,7 @@ final class TraitASTTransformationTest {
     }
 
     @ParameterizedTest
-    @ValueSource(strings=['public',/*TODO:'protected',*/'private'])
+    @ValueSource(strings=['public','private'])
     void testFinalPropertyGetterDefinedInSuperClass(String modifier) {
         assertScript shell, """
             trait Foo {

Reply via email to