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)
+ '''
+ }
+}