Skip to content

Commit 01a8385

Browse files
committed
GROOVY-11292, GROOVY-11750, GROOVY-11768: non-sealed super class
4_0_X backport
1 parent 0db2fa3 commit 01a8385

4 files changed

Lines changed: 93 additions & 64 deletions

File tree

src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,15 @@ public boolean isSealed() {
9696
List<AnnotationStub> annotations = classData.annotations;
9797
if (annotations != null) {
9898
for (AnnotationStub stub : annotations) {
99-
if (stub.className.equals("groovy.transform.Sealed")) {
99+
if ("groovy.transform.Sealed".equals(stub.className)) {
100100
return true;
101101
}
102102
}
103103
}
104104
// check Java "sealed"
105105
try {
106106
return ReflectionUtils.isSealed(getTypeClass());
107-
} catch (NoClassDefFoundError ignored) {
107+
} catch (AssertionError | LinkageError ignore) {
108108
}
109109
return false;
110110
}

src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -315,53 +315,53 @@ private void checkAbstractDeclaration(final MethodNode methodNode) {
315315
}
316316

317317
private void checkClassForExtendingFinalOrSealed(final ClassNode cn) {
318-
boolean sealed = Boolean.TRUE.equals(cn.getNodeMetaData(groovy.transform.Sealed.class));
319-
if (sealed && cn.getPermittedSubclasses().isEmpty()) {
320-
addError("Sealed " + getDescription(cn) + " has no explicit or implicit permitted subclasses.", cn);
321-
return;
322-
}
323318
boolean isFinal = isFinal(cn.getModifiers());
324-
if (sealed && isFinal) {
325-
addError("The " + getDescription(cn) + " cannot be both final and sealed.", cn);
326-
return;
319+
boolean isSealed = Boolean.TRUE.equals(cn.getNodeMetaData(groovy.transform.Sealed.class));
320+
boolean isNonSealed = cn.getAnnotations().stream().anyMatch(an -> an.getClassNode().getName().equals("groovy.transform.NonSealed")); // GROOVY-11768
321+
322+
ClassNode sc = cn.getSuperClass();
323+
if (sc != null && isFinal(sc.getModifiers())) {
324+
addError("You are not allowed to extend the final " + getDescription(sc) + ".", cn);
327325
}
328-
boolean explicitNonSealed = nonSealed(cn);
329-
ClassNode superCN = cn.getSuperClass();
330-
boolean sealedSuper = superCN != null && superCN.isSealed();
331-
boolean sealedInterface = Arrays.stream(cn.getInterfaces()).anyMatch(ClassNode::isSealed);
332-
boolean nonSealedSuper = superCN != null && nonSealed(superCN);
333-
boolean nonSealedInterface = Arrays.stream(cn.getInterfaces()).anyMatch(this::nonSealed);
334326

335-
if (explicitNonSealed && !(sealedSuper || sealedInterface || nonSealedSuper || nonSealedInterface)) {
336-
addError("The " + getDescription(cn) + " cannot be non-sealed as it has no sealed parent.", cn);
337-
return;
327+
if (isFinal && isNonSealed) {
328+
addError("The " + getDescription(cn) + " cannot be both final and non-sealed.", cn);
338329
}
339-
if (sealedSuper || sealedInterface) {
340-
if (sealed && explicitNonSealed) {
341-
addError("The " + getDescription(cn) + " cannot be both sealed and non-sealed.", cn);
342-
return;
330+
if (isSealed) {
331+
if (isFinal) {
332+
addError("The " + getDescription(cn) + " cannot be both final and sealed.", cn);
343333
}
344-
if (isFinal && explicitNonSealed) {
345-
addError("The " + getDescription(cn) + " cannot be both final and non-sealed.", cn);
346-
return;
334+
if (isNonSealed) {
335+
addError("The " + getDescription(cn) + " cannot be both sealed and non-sealed.", cn);
347336
}
348-
if (sealedSuper) {
349-
checkSealedParent(cn, superCN);
337+
if (cn.getPermittedSubclasses().isEmpty()) {
338+
addError("Sealed " + getDescription(cn) + " has no explicit or implicit permitted subclasses.", cn);
350339
}
351-
if (sealedInterface) {
352-
for (ClassNode candidate : cn.getInterfaces()) {
353-
if (candidate.isSealed()) {
354-
checkSealedParent(cn, candidate);
355-
}
340+
}
341+
342+
boolean sealedSuper = sc != null && sc.isSealed();
343+
boolean nonSealedSuper = sc != null && isNonSealed(sc);
344+
boolean sealedInterface = Arrays.stream(cn.getInterfaces()).anyMatch(ClassNode::isSealed);
345+
boolean nonSealedInterface = Arrays.stream(cn.getInterfaces()).anyMatch(this::isNonSealed);
346+
347+
if (isNonSealed && !(sealedSuper || sealedInterface || nonSealedSuper || nonSealedInterface)) {
348+
addError("The " + getDescription(cn) + " cannot be non-sealed as it has no sealed parent.", cn);
349+
}
350+
if (sealedSuper) {
351+
checkSealedParent(cn, sc);
352+
}
353+
if (sealedInterface) {
354+
for (ClassNode si : cn.getInterfaces()) {
355+
if (si.isSealed()) {
356+
checkSealedParent(cn, si);
356357
}
357358
}
358359
}
359-
if (superCN == null || !isFinal(superCN.getModifiers())) return;
360-
addError("You are not allowed to extend the final " + getDescription(superCN) + ".", cn);
361360
}
362361

363-
private boolean nonSealed(final ClassNode cn) {
364-
if (Boolean.TRUE.equals(cn.getNodeMetaData(groovy.transform.NonSealed.class))) {
362+
private boolean isNonSealed(final ClassNode cn) {
363+
if (cn.isPrimaryClassNode()
364+
&& Boolean.TRUE.equals(cn.getNodeMetaData(groovy.transform.NonSealed.class))) {
365365
return true;
366366
}
367367
ClassNode sc = cn.getSuperClass(); // GROOVY-11292, GROOVY-11750: check super class

src/main/java/org/codehaus/groovy/classgen/Verifier.java

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import groovy.transform.CompileStatic;
2525
import groovy.transform.Generated;
2626
import groovy.transform.Internal;
27-
import groovy.transform.NonSealed;
28-
import groovy.transform.Sealed;
2927
import groovy.transform.stc.POJO;
3028
import org.apache.groovy.ast.tools.ClassNodeUtils;
3129
import org.apache.groovy.util.BeanUtils;
@@ -165,8 +163,6 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
165163
public static final String __TIMESTAMP = "__timeStamp";
166164
public static final String __TIMESTAMP__ = "__timeStamp__239_neverHappen";
167165

168-
private static final Class<Sealed> SEALED_TYPE = Sealed.class;
169-
private static final Class<NonSealed> NON_SEALED_TYPE = NonSealed.class;
170166
private static final Parameter[] SET_METACLASS_PARAMS = {new Parameter(ClassHelper.METACLASS_TYPE, "mc")};
171167

172168
private ClassNode classNode;
@@ -272,7 +268,7 @@ public void visitClass(final ClassNode node) {
272268
checkForDuplicateMethods(node);
273269
checkForDuplicateConstructors(node);
274270
addCovariantMethods(node);
275-
detectNonSealedClasses(node);
271+
detectNonSealedType(node);
276272
checkFinalVariables(node);
277273
}
278274

@@ -286,21 +282,15 @@ private static void detectInvalidRecordComponentNames(final ClassNode node) {
286282
}
287283
}
288284

289-
private static void detectNonSealedClasses(final ClassNode node) {
285+
private static void detectNonSealedType(final ClassNode node) {
290286
if (isFinal(node.getModifiers())) return;
291-
if (Boolean.TRUE.equals(node.getNodeMetaData(SEALED_TYPE))) return;
292-
if (Boolean.TRUE.equals(node.getNodeMetaData(NON_SEALED_TYPE))) return;
293-
ClassNode sn = node.getSuperClass();
294-
boolean found = false;
295-
while (sn != null && !sn.equals(ClassHelper.OBJECT_TYPE)) {
296-
if (sn.isSealed()) {
297-
found = true;
298-
break;
287+
if (Boolean.TRUE.equals(node.getNodeMetaData(groovy.transform.Sealed.class))) return;
288+
if (Boolean.TRUE.equals(node.getNodeMetaData(groovy.transform.NonSealed.class))) return;
289+
for (ClassNode sc = node.getSuperClass(); sc != null && !isObjectType(sc); sc = sc.getSuperClass()) {
290+
if (sc.isSealed()) {
291+
node.putNodeMetaData(groovy.transform.NonSealed.class, Boolean.TRUE);
292+
return;
299293
}
300-
sn = sn.getSuperClass();
301-
}
302-
if (found) {
303-
node.putNodeMetaData(NON_SEALED_TYPE, Boolean.TRUE);
304294
}
305295
}
306296

src/test/groovy/bugs/Groovy11292.groovy

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ final class Groovy11292 {
3030

3131
@Test
3232
void testClassWithNonSealedParent1() {
33+
assertScript '''import java.lang.ref.SoftReference // non-sealed type
34+
35+
class TestReference<T> extends SoftReference<T> {
36+
TestReference(T referent) {
37+
super(referent)
38+
}
39+
}
40+
41+
assert new TestReference(null)
42+
'''
43+
}
44+
45+
@Test
46+
void testClassWithNonSealedParent2() {
3347
assumeTrue(isAtLeastJdk('17.0'))
3448

3549
def config = new CompilerConfiguration(
@@ -74,17 +88,42 @@ final class Groovy11292 {
7488
}
7589
}
7690

91+
// GROOVY-11768
7792
@Test
78-
void testClassWithNonSealedParent2() {
79-
assertScript '''import java.lang.ref.SoftReference // non-sealed type
93+
void testClassWithNonSealedParent3() {
94+
assumeTrue(isAtLeastJdk('17.0'))
8095

81-
class TestReference<T> extends SoftReference<T> {
82-
TestReference(T referent) {
83-
super(referent)
84-
}
85-
}
96+
def config = new CompilerConfiguration(
97+
targetDirectory: File.createTempDir(),
98+
jointCompilationOptions: [memStub: true]
99+
)
86100

87-
assert new TestReference(null)
88-
'''
101+
def parentDir = File.createTempDir()
102+
try {
103+
def a = new File(parentDir, 'A.java')
104+
a.write '''
105+
public abstract sealed class A permits B {}
106+
'''
107+
def b = new File(parentDir, 'B.java')
108+
b.write '''
109+
public abstract non-sealed class B extends A {}
110+
'''
111+
def c = new File(parentDir, 'C.java')
112+
c.write '''
113+
public class C extends B {}
114+
'''
115+
def d = new File(parentDir, 'D.groovy')
116+
d.write '''
117+
class D extends C {}
118+
'''
119+
120+
def loader = new GroovyClassLoader(this.class.classLoader)
121+
def cu = new JavaAwareCompilationUnit(config, loader)
122+
cu.addSources(a, b, c, d)
123+
cu.compile()
124+
} finally {
125+
config.targetDirectory.deleteDir()
126+
parentDir.deleteDir()
127+
}
89128
}
90129
}

0 commit comments

Comments
 (0)