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

emilles 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 04c585502a GROOVY-11292, GROOVY-11750, GROOVY-11768: non-sealed super 
class
04c585502a is described below

commit 04c585502a6f2c48dddf28a38421fb5499afbbcb
Author: Eric Milles <[email protected]>
AuthorDate: Fri Oct 3 10:07:53 2025 -0500

    GROOVY-11292, GROOVY-11750, GROOVY-11768: non-sealed super class
---
 .../apache/groovy/ast/tools/ClassNodeUtils.java    | 31 ++++-----
 .../groovy/ast/decompiled/DecompiledClassNode.java |  5 +-
 .../groovy/classgen/ClassCompletionVerifier.java   | 76 ++++++++++------------
 .../org/codehaus/groovy/classgen/Verifier.java     | 26 +++-----
 .../groovy/runtime/ProxyGeneratorAdapter.java      |  3 +-
 .../org/codehaus/groovy/vmplugin/v8/Java8.java     |  8 +--
 src/test/groovy/bugs/Groovy11292.groovy            | 71 +++++++++++++-------
 7 files changed, 110 insertions(+), 110 deletions(-)

diff --git a/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java 
b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java
index cd67d9849f..84086948ef 100644
--- a/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java
+++ b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java
@@ -18,7 +18,6 @@
  */
 package org.apache.groovy.ast.tools;
 
-import groovy.transform.NonSealed;
 import org.apache.groovy.util.BeanUtils;
 import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.ClassNode;
@@ -27,13 +26,11 @@ import org.codehaus.groovy.ast.FieldNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.PropertyNode;
-import org.codehaus.groovy.ast.decompiled.DecompiledClassNode;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.MapExpression;
 import org.codehaus.groovy.ast.expr.SpreadExpression;
 import org.codehaus.groovy.ast.expr.TupleExpression;
 import org.codehaus.groovy.ast.stmt.Statement;
-import org.codehaus.groovy.reflection.ReflectionUtils;
 import org.codehaus.groovy.transform.AbstractASTTransformation;
 
 import java.lang.reflect.Modifier;
@@ -50,7 +47,6 @@ import java.util.Queue;
 import java.util.Set;
 import java.util.function.Predicate;
 
-import static java.lang.reflect.Modifier.isFinal;
 import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
@@ -367,23 +363,22 @@ public class ClassNodeUtils {
      * @since 5.0.0
      */
     public static boolean isNonSealed(final ClassNode cn) {
-        if (cn instanceof DecompiledClassNode) {
-            Class<?> typeClass;
+        if (cn.isArray() || isPrimitiveType(cn)) {
+            return false;
+        }
+        if (cn.isPrimaryClassNode()) {
+            if 
(Boolean.TRUE.equals(cn.getNodeMetaData(groovy.transform.NonSealed.class))) 
return true;
+        } else {
+            // GROOVY-11292, GROOVY-11750: check super class
             try {
-                typeClass = cn.getTypeClass();
-            } catch (NoClassDefFoundError e) {
-                return false;
+                Class<?> tc = cn.getTypeClass();
+                Class<?> sc = tc.getSuperclass();
+                return sc != null && sc.isSealed() && 
!(Modifier.isFinal(tc.getModifiers()) || tc.isSealed());
+            } catch (AssertionError | LinkageError ignore) {
             }
-
-            final Class<?> superclass = typeClass.getSuperclass();
-            if (null == superclass) return false;
-            return ReflectionUtils.isSealed(superclass) && 
!(Modifier.isFinal(typeClass.getModifiers()) || 
ReflectionUtils.isSealed(typeClass));
         }
-
-        if (Boolean.TRUE.equals(cn.getNodeMetaData(NonSealed.class))) return 
true;
-        final ClassNode superClass = cn.getSuperClass();
-        if (null == superClass) return false;
-        return superClass.isSealed() && !(isFinal(cn.getModifiers()) || 
cn.isSealed());
+        ClassNode sc = cn.getSuperClass();
+        return sc != null && sc.isSealed() && 
!(Modifier.isFinal(cn.getModifiers()) || cn.isSealed());
     }
 
     /**
diff --git 
a/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java 
b/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java
index 1957cd46bb..c21808f1a6 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java
@@ -27,7 +27,6 @@ import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.MixinNode;
 import org.codehaus.groovy.ast.RecordComponentNode;
 import org.codehaus.groovy.classgen.Verifier;
-import org.codehaus.groovy.reflection.ReflectionUtils;
 import org.objectweb.asm.Opcodes;
 
 import java.lang.reflect.Modifier;
@@ -103,8 +102,8 @@ public class DecompiledClassNode extends ClassNode {
         }
         // check Java "sealed"
         try {
-            return ReflectionUtils.isSealed(getTypeClass());
-        } catch (NoClassDefFoundError ignored) {
+            return getTypeClass().isSealed();
+        } catch (AssertionError | LinkageError ignored) {
         }
         return false;
     }
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java 
b/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
index 7caca8c80b..b3c6c14dbc 100644
--- a/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
@@ -18,7 +18,6 @@
  */
 package org.codehaus.groovy.classgen;
 
-import groovy.transform.Sealed;
 import org.apache.groovy.ast.tools.AnnotatedNodeUtils;
 import org.apache.groovy.ast.tools.ClassNodeUtils;
 import org.apache.groovy.ast.tools.MethodNodeUtils;
@@ -52,12 +51,12 @@ import org.codehaus.groovy.syntax.Types;
 import org.codehaus.groovy.transform.trait.Traits;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import static java.lang.reflect.Modifier.isAbstract;
 import static java.lang.reflect.Modifier.isFinal;
@@ -301,53 +300,48 @@ public class ClassCompletionVerifier extends 
ClassCodeVisitorSupport {
     }
 
     private void checkClassForExtendingFinalOrSealed(final ClassNode cn) {
-        boolean sealed = Boolean.TRUE.equals(cn.getNodeMetaData(Sealed.class));
-        if (sealed && cn.getPermittedSubclasses().isEmpty()) {
-            addError("Sealed " + getDescription(cn) + " has no explicit or 
implicit permitted subclasses.", cn);
-            return;
-        }
         boolean isFinal = isFinal(cn.getModifiers());
-        if (sealed && isFinal) {
-            addError("The " + getDescription(cn) + " cannot be both final and 
sealed.", cn);
-            return;
-        }
-        boolean explicitNonSealed = nonSealed(cn);
-        ClassNode superCN = cn.getSuperClass();
-        boolean sealedSuper = superCN != null && superCN.isSealed();
-        boolean sealedInterface = 
Arrays.stream(cn.getInterfaces()).anyMatch(ClassNode::isSealed);
-        boolean nonSealedSuper = superCN != null && nonSealed(superCN);
-        boolean nonSealedInterface = 
Arrays.stream(cn.getInterfaces()).anyMatch(this::nonSealed);
-
-        if (explicitNonSealed && !(sealedSuper || sealedInterface || 
nonSealedSuper || nonSealedInterface)) {
-            addError("The " + getDescription(cn) + " cannot be non-sealed as 
it has no sealed parent.", cn);
-            return;
+        boolean isSealed = AnnotatedNodeUtils.hasAnnotation(cn, 
ClassHelper.SEALED_TYPE);
+        boolean isNonSealed = AnnotatedNodeUtils.hasAnnotation(cn, new 
ClassNode(groovy.transform.NonSealed.class)); // GROOVY-11768
+
+        ClassNode sc = cn.getSuperClass();
+        if (sc != null && isFinal(sc.getModifiers())) {
+            addError("You are not allowed to extend the final " + 
getDescription(sc) + ".", cn);
         }
-        if (sealedSuper || sealedInterface) {
-            if (sealed && explicitNonSealed) {
-                addError("The " + getDescription(cn) + " cannot be both sealed 
and non-sealed.", cn);
-                return;
+
+        if (isFinal && isNonSealed) {
+            addError("The " + getDescription(cn) + " cannot be both final and 
non-sealed.", cn);
+        }
+        if (isSealed) {
+            if (isFinal) {
+                addError("The " + getDescription(cn) + " cannot be both final 
and sealed.", cn);
             }
-            if (isFinal && explicitNonSealed) {
-                addError("The " + getDescription(cn) + " cannot be both final 
and non-sealed.", cn);
-                return;
+            if (isNonSealed) {
+                addError("The " + getDescription(cn) + " cannot be both sealed 
and non-sealed.", cn);
             }
-            if (sealedSuper) {
-                checkSealedParent(cn, superCN);
+            if (cn.getPermittedSubclasses().isEmpty()) {
+                addError("Sealed " + getDescription(cn) + " has no explicit or 
implicit permitted subclasses.", cn);
             }
-            if (sealedInterface) {
-                for (ClassNode candidate : cn.getInterfaces()) {
-                    if (candidate.isSealed()) {
-                        checkSealedParent(cn, candidate);
-                    }
+        }
+
+        boolean sealedSuper = sc != null && sc.isSealed();
+        boolean nonSealedSuper = sc != null && ClassNodeUtils.isNonSealed(sc);
+        boolean sealedInterface = 
Stream.of(cn.getInterfaces()).anyMatch(ClassNode::isSealed);
+        boolean nonSealedInterface = 
Stream.of(cn.getInterfaces()).anyMatch(ClassNodeUtils::isNonSealed);
+
+        if (isNonSealed && !(sealedSuper || sealedInterface || nonSealedSuper 
|| nonSealedInterface)) {
+            addError("The " + getDescription(cn) + " cannot be non-sealed as 
it has no sealed parent.", cn);
+        }
+        if (sealedSuper) {
+            checkSealedParent(cn, sc);
+        }
+        if (sealedInterface) {
+            for (ClassNode si : cn.getInterfaces()) {
+                if (si.isSealed()) {
+                    checkSealedParent(cn, si);
                 }
             }
         }
-        if (superCN == null || !isFinal(superCN.getModifiers())) return;
-        addError("You are not allowed to extend the final " + 
getDescription(superCN) + ".", cn);
-    }
-
-    private boolean nonSealed(final ClassNode node) {
-        return ClassNodeUtils.isNonSealed(node);
     }
 
     private void checkSealedParent(final ClassNode cn, final ClassNode parent) 
{
diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java 
b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index 0a37b824de..fe9758d0ab 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -24,8 +24,6 @@ import groovy.lang.MetaClass;
 import groovy.transform.CompileStatic;
 import groovy.transform.Generated;
 import groovy.transform.Internal;
-import groovy.transform.NonSealed;
-import groovy.transform.Sealed;
 import groovy.transform.stc.POJO;
 import org.apache.groovy.ast.tools.ClassNodeUtils;
 import org.apache.groovy.ast.tools.MethodNodeUtils;
@@ -166,8 +164,6 @@ public class Verifier implements GroovyClassVisitor, 
Opcodes {
     public static final String __TIMESTAMP = "__timeStamp";
     public static final String __TIMESTAMP__ = "__timeStamp__239_neverHappen";
 
-    private static final Class<Sealed> SEALED_TYPE = Sealed.class;
-    private static final Class<NonSealed> NON_SEALED_TYPE = NonSealed.class;
     private static final Parameter[] SET_METACLASS_PARAMS = {new 
Parameter(ClassHelper.METACLASS_TYPE, "mc")};
 
     private ClassNode classNode;
@@ -276,7 +272,7 @@ public class Verifier implements GroovyClassVisitor, 
Opcodes {
         checkForDuplicateMethods(node);
         checkForDuplicateConstructors(node);
         addCovariantMethods(node);
-        detectNonSealedClasses(node);
+        detectNonSealedType(node);
         checkFinalVariables(node);
     }
 
@@ -326,21 +322,15 @@ public class Verifier implements GroovyClassVisitor, 
Opcodes {
         }
     }
 
-    private static void detectNonSealedClasses(final ClassNode node) {
+    private static void detectNonSealedType(final ClassNode node) {
         if (isFinal(node.getModifiers())) return;
-        if (Boolean.TRUE.equals(node.getNodeMetaData(SEALED_TYPE))) return;
-        if (Boolean.TRUE.equals(node.getNodeMetaData(NON_SEALED_TYPE))) return;
-        ClassNode sn = node.getSuperClass();
-        boolean found = false;
-        while (sn != null && !sn.equals(ClassHelper.OBJECT_TYPE)) {
-            if (sn.isSealed()) {
-                found = true;
-                break;
+        if 
(Boolean.TRUE.equals(node.getNodeMetaData(groovy.transform.Sealed.class))) 
return;
+        if 
(Boolean.TRUE.equals(node.getNodeMetaData(groovy.transform.NonSealed.class))) 
return;
+        for (ClassNode sc = node.getSuperClass(); sc != null && 
!isObjectType(sc); sc = sc.getSuperClass()) {
+            if (sc.isSealed()) {
+                node.putNodeMetaData(groovy.transform.NonSealed.class, 
Boolean.TRUE);
+                return;
             }
-            sn = sn.getSuperClass();
-        }
-        if (found) {
-            node.putNodeMetaData(NON_SEALED_TYPE, Boolean.TRUE);
         }
     }
 
diff --git 
a/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java 
b/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
index 920909a304..4bf6e2c747 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
@@ -57,7 +57,6 @@ import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 
 import static 
org.codehaus.groovy.control.CompilerConfiguration.ASM_API_VERSION;
-import static org.codehaus.groovy.reflection.ReflectionUtils.isSealed;
 import static org.objectweb.asm.Opcodes.AASTORE;
 import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
 import static org.objectweb.asm.Opcodes.ACC_FINAL;
@@ -203,7 +202,7 @@ public class ProxyGeneratorAdapter extends ClassVisitor {
         this.implClasses.add(superClass);
         if (generateDelegateField) {
             implClasses.add(delegateClass);
-            Collections.addAll(this.implClasses, 
Arrays.stream(delegateClass.getInterfaces()).filter(c -> 
!isSealed(c)).toArray(Class[]::new));
+            Collections.addAll(this.implClasses, 
Arrays.stream(delegateClass.getInterfaces()).filter(c -> 
!c.isSealed()).toArray(Class[]::new));
         }
         if (interfaces != null) {
             Collections.addAll(this.implClasses, interfaces);
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java 
b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
index 9a6e8bb0e6..46d5f25d9b 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
@@ -73,7 +73,6 @@ import java.util.Arrays;
 import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * Java 8 based functions.
@@ -507,10 +506,11 @@ public class Java8 implements VMPlugin {
     }
 
     private void makePermittedSubclasses(final CompileUnit cu, final ClassNode 
classNode, final Class<?> clazz) {
-        if (!ReflectionUtils.isSealed(clazz)) return;
-        List<ClassNode> permittedSubclasses = 
Arrays.stream(ReflectionUtils.getPermittedSubclasses(clazz))
+        if (!clazz.isSealed()) return;
+        List<ClassNode> permittedSubclasses =
+            Arrays.stream(clazz.getPermittedSubclasses())
                 .map(c -> makeClassNode(cu, c, c))
-                .collect(Collectors.toList());
+                .toList();
         classNode.setPermittedSubclasses(permittedSubclasses);
     }
 
diff --git a/src/test/groovy/bugs/Groovy11292.groovy 
b/src/test/groovy/bugs/Groovy11292.groovy
index b4421e7800..afdb025b01 100644
--- a/src/test/groovy/bugs/Groovy11292.groovy
+++ b/src/test/groovy/bugs/Groovy11292.groovy
@@ -23,14 +23,25 @@ import 
org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit
 import org.junit.Test
 
 import static groovy.test.GroovyAssert.assertScript
-import static groovy.test.GroovyAssert.isAtLeastJdk
-import static org.junit.Assume.assumeTrue
 
 final class Groovy11292 {
+
     @Test
-    void testClassWithNonSealedParent_1() {
-        assumeTrue(isAtLeastJdk('17.0'))
+    void testClassWithNonSealedParent1() {
+        assertScript '''import java.lang.ref.SoftReference // non-sealed type
+
+            class TestReference<T> extends SoftReference<T> {
+                TestReference(T referent) {
+                    super(referent)
+                }
+            }
 
+            assert new TestReference(null)
+        '''
+    }
+
+    @Test
+    void testClassWithNonSealedParent2() {
         def config = new CompilerConfiguration(
             targetDirectory: File.createTempDir(),
             jointCompilationOptions: [memStub: true]
@@ -56,12 +67,10 @@ final class Groovy11292 {
             '''
             def e = new File(parentDir, 'E.groovy')
             e.write '''
-                @groovy.transform.CompileStatic
                 class E extends B {}
             '''
             def f = new File(parentDir, 'F.groovy')
             f.write '''
-                @groovy.transform.CompileStatic
                 class F extends E {}
             '''
 
@@ -75,26 +84,40 @@ final class Groovy11292 {
         }
     }
 
+    // GROOVY-11768
     @Test
-    void testClassWithNonSealedParent_2() {
-        assertScript '''
-            import org.codehaus.groovy.util.Finalizable
-            class TestReference<T>
-                extends java.lang.ref.SoftReference<T>
-                implements org.codehaus.groovy.util.Reference<T, Finalizable> {
-
-                final Finalizable handler
+    void testClassWithNonSealedParent3() {
+        def config = new CompilerConfiguration(
+            targetDirectory: File.createTempDir(),
+            jointCompilationOptions: [memStub: true]
+        )
 
-                TestReference(T referent) {
-                    super(referent)
-                }
+        def parentDir = File.createTempDir()
+        try {
+            def a = new File(parentDir, 'A.java')
+            a.write '''
+                public abstract sealed class A permits B {}
+            '''
+            def b = new File(parentDir, 'B.java')
+            b.write '''
+                public abstract non-sealed class B extends A {}
+            '''
+            def c = new File(parentDir, 'C.java')
+            c.write '''
+                public class C extends B {}
+            '''
+            def d = new File(parentDir, 'D.groovy')
+            d.write '''
+                class D extends C {}
+            '''
 
-                @Override
-                Finalizable getHandler() {
-                    return handler
-                }
-            }
-            assert new TestReference(null)
-        '''
+            def loader = new GroovyClassLoader(this.class.classLoader)
+            def cu = new JavaAwareCompilationUnit(config, loader)
+            cu.addSources(a, b, c, d)
+            cu.compile()
+        } finally {
+            config.targetDirectory.deleteDir()
+            parentDir.deleteDir()
+        }
     }
 }

Reply via email to