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

paulk 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 8f668bdb20 GROOVY-11292: Class without sealed parent cannot be 
non-sealed (#2037)
8f668bdb20 is described below

commit 8f668bdb20a91d5688a3da9a30d0ccf99fdc164c
Author: Daniel Sun <[email protected]>
AuthorDate: Thu Jan 25 13:21:45 2024 +0800

    GROOVY-11292: Class without sealed parent cannot be non-sealed (#2037)
    
    * GROOVY-11292: Class without sealed parent cannot be non-sealed
    
    * Move `isNonSealed` to `ClassNodeUtils`
---
 .github/workflows/groovy-build-test-main.yml       |   2 +-
 .../apache/groovy/ast/tools/ClassNodeUtils.java    |  30 +++++++
 .../groovy/classgen/ClassCompletionVerifier.java   |   3 +-
 src/test/groovy/bugs/Groovy11292.groovy            | 100 +++++++++++++++++++++
 4 files changed, 132 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/groovy-build-test-main.yml 
b/.github/workflows/groovy-build-test-main.yml
index 5e342f8fec..10e379cff2 100644
--- a/.github/workflows/groovy-build-test-main.yml
+++ b/.github/workflows/groovy-build-test-main.yml
@@ -26,7 +26,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-22.04]
-        java: [11, 17]
+        java: [11, 17, 21]
     runs-on: ${{ matrix.os }}
     env:
       GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }}
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 1b8538d8b8..4020d3dd1a 100644
--- a/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java
+++ b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java
@@ -18,6 +18,7 @@
  */
 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;
@@ -26,11 +27,13 @@ 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;
@@ -47,6 +50,7 @@ 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;
@@ -325,6 +329,32 @@ public class ClassNodeUtils {
         return false;
     }
 
+    /**
+     * Check if the type is declared {@code non-sealed}
+     *
+     * @param cn the type
+     * @return the check result
+     */
+    public static boolean isNonSealed(final ClassNode cn) {
+        if (cn instanceof DecompiledClassNode) {
+            Class<?> typeClass;
+            try {
+                typeClass = cn.getTypeClass();
+            } catch (NoClassDefFoundError e) {
+                return false;
+            }
+
+            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());
+    }
+
     public static boolean hasStaticProperty(final ClassNode cNode, final 
String propName) {
         PropertyNode found = getStaticProperty(cNode, propName);
         if (found == null) {
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java 
b/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
index 870704af79..76936fe420 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.NonSealed;
 import groovy.transform.Sealed;
 import org.apache.groovy.ast.tools.AnnotatedNodeUtils;
 import org.apache.groovy.ast.tools.ClassNodeUtils;
@@ -368,7 +367,7 @@ public class ClassCompletionVerifier extends 
ClassCodeVisitorSupport {
     }
 
     private boolean nonSealed(final ClassNode node) {
-        return Boolean.TRUE.equals(node.getNodeMetaData(NonSealed.class));
+        return ClassNodeUtils.isNonSealed(node);
     }
 
     private void checkSealedParent(final ClassNode cn, final ClassNode parent) 
{
diff --git a/src/test/groovy/bugs/Groovy11292.groovy 
b/src/test/groovy/bugs/Groovy11292.groovy
new file mode 100644
index 0000000000..b4421e7800
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy11292.groovy
@@ -0,0 +1,100 @@
+/*
+ *  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.control.CompilerConfiguration
+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'))
+
+        def config = new CompilerConfiguration(
+            targetDirectory: File.createTempDir(),
+            jointCompilationOptions: [memStub: true]
+        )
+
+        def parentDir = File.createTempDir()
+        try {
+            def a = new File(parentDir, 'A.java')
+            a.write '''
+                public sealed class A permits B {}
+            '''
+            def b = new File(parentDir, 'B.java')
+            b.write '''
+                public non-sealed class B extends A {}
+            '''
+            def c = new File(parentDir, 'C.groovy')
+            c.write '''
+                class C extends B {}
+            '''
+            def d = new File(parentDir, 'D.groovy')
+            d.write '''
+                class D extends C {}
+            '''
+            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 {}
+            '''
+
+            def loader = new GroovyClassLoader(this.class.classLoader)
+            def cu = new JavaAwareCompilationUnit(config, loader)
+            cu.addSources(a, b, c, d, e, f)
+            cu.compile()
+        } finally {
+            config.targetDirectory.deleteDir()
+            parentDir.deleteDir()
+        }
+    }
+
+    @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
+
+                TestReference(T referent) {
+                    super(referent)
+                }
+
+                @Override
+                Finalizable getHandler() {
+                    return handler
+                }
+            }
+            assert new TestReference(null)
+        '''
+    }
+}

Reply via email to