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