8343306: javac is failing to determine if a class and a sealed interface are disjoint

Reviewed-by: jlahoda, mcimadamore
This commit is contained in:
Vicente Romero 2024-11-08 12:27:31 +00:00
parent 0c281acfb4
commit 96eed7fa6c
2 changed files with 69 additions and 24 deletions

View File

@ -1672,6 +1672,12 @@ public class Types {
// where
class DisjointChecker {
Set<Pair<ClassSymbol, ClassSymbol>> pairsSeen = new HashSet<>();
/* there are three cases for ts and ss:
* - one is a class and the other one is an interface (case I)
* - both are classes (case II)
* - both are interfaces (case III)
* all those cases are covered in JLS 23, section: "5.1.6.1 Allowed Narrowing Reference Conversion"
*/
private boolean areDisjoint(ClassSymbol ts, ClassSymbol ss) {
Pair<ClassSymbol, ClassSymbol> newPair = new Pair<>(ts, ss);
/* if we are seeing the same pair again then there is an issue with the sealed hierarchy
@ -1679,32 +1685,38 @@ public class Types {
*/
if (!pairsSeen.add(newPair))
return false;
if (isSubtype(erasure(ts.type), erasure(ss.type))) {
return false;
}
// if both are classes or both are interfaces, shortcut
if (ts.isInterface() == ss.isInterface() && isSubtype(erasure(ss.type), erasure(ts.type))) {
return false;
}
if (ts.isInterface() && !ss.isInterface()) {
/* so ts is interface but ss is a class
* an interface is disjoint from a class if the class is disjoint form the interface
*/
return areDisjoint(ss, ts);
}
// a final class that is not subtype of ss is disjoint
if (!ts.isInterface() && ts.isFinal()) {
if (ts.isInterface() != ss.isInterface()) { // case I: one is a class and the other one is an interface
ClassSymbol isym = ts.isInterface() ? ts : ss; // isym is the interface and csym the class
ClassSymbol csym = isym == ts ? ss : ts;
if (!isSubtype(erasure(csym.type), erasure(isym.type))) {
if (csym.isFinal()) {
return true;
} else if (csym.isSealed()) {
return areDisjoint(isym, csym.getPermittedSubclasses());
} else if (isym.isSealed()) {
// if the class is not final and not sealed then it has to be freely extensible
return areDisjoint(csym, isym.getPermittedSubclasses());
}
// if at least one is sealed
if (ts.isSealed() || ss.isSealed()) {
// permitted subtypes have to be disjoint with the other symbol
ClassSymbol sealedOne = ts.isSealed() ? ts : ss;
ClassSymbol other = sealedOne == ts ? ss : ts;
return sealedOne.getPermittedSubclasses().stream().allMatch(type -> areDisjoint((ClassSymbol)type.tsym, other));
} // now both are classes or both are interfaces
} else if (!ts.isInterface()) { // case II: both are classes
return !isSubtype(erasure(ss.type), erasure(ts.type)) && !isSubtype(erasure(ts.type), erasure(ss.type));
} else { // case III: both are interfaces
if (!isSubtype(erasure(ts.type), erasure(ss.type)) && !isSubtype(erasure(ss.type), erasure(ts.type))) {
if (ts.isSealed()) {
return areDisjoint(ss, ts.getPermittedSubclasses());
} else if (ss.isSealed()) {
return areDisjoint(ts, ss.getPermittedSubclasses());
}
}
}
// at this point we haven't been able to statically prove that the classes or interfaces are disjoint
return false;
}
boolean areDisjoint(ClassSymbol csym, List<Type> permittedSubtypes) {
return permittedSubtypes.stream().allMatch(psubtype -> areDisjoint(csym, (ClassSymbol) psubtype.tsym));
}
}
private TypeRelation isCastable = new TypeRelation() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -1178,6 +1178,39 @@ class SealedCompilationTests extends CompilationTestCase {
I[] i = (I[]) c;
}
}
""",
"""
class Test {
sealed interface I permits C1 {}
non-sealed class C1 implements I {}
class C2 extends C1 {}
class C3 {}
I m(int s, C3 c3) {
I i = (I)c3;
}
}
""",
"""
class Test {
sealed interface I permits C1 {}
non-sealed class C1 implements I {}
class C2 extends C1 {}
class C3 {}
I m(int s, C3 c3) {
I i = (C1)c3;
}
}
""",
"""
class Test {
sealed interface I permits C1 {}
non-sealed class C1 implements I {}
class C2 extends C1 {}
class C3 {}
I m(int s, C3 c3) {
I i = (C2)c3;
}
}
"""
)) {
assertFail("compiler.err.prob.found.req", s);