8274363: Transitively sealed classes not considered exhaustive in switches

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2021-10-01 08:54:18 +00:00
parent 1887028408
commit 292d7bb1d5
2 changed files with 64 additions and 18 deletions

View File

@ -207,6 +207,7 @@ public class Flow {
private final JCDiagnostic.Factory diags;
private Env<AttrContext> attrEnv;
private Lint lint;
private final DeferredCompletionFailureHandler dcfh;
private final boolean allowEffectivelyFinalInInnerClasses;
public static Flow instance(Context context) {
@ -331,6 +332,7 @@ public class Flow {
lint = Lint.instance(context);
rs = Resolve.instance(context);
diags = JCDiagnostic.Factory.instance(context);
dcfh = DeferredCompletionFailureHandler.instance(context);
Source source = Source.instance(context);
allowEffectivelyFinalInInnerClasses = Feature.EFFECTIVELY_FINAL_IN_INNER_CLASSES.allowedInSource(source);
}
@ -770,12 +772,9 @@ public class Flow {
case TYP -> {
for (Type sup : types.directSupertypes(sym.type)) {
if (sup.tsym.kind == TYP && sup.tsym.isAbstract() && sup.tsym.isSealed()) {
boolean hasAll = ((ClassSymbol) sup.tsym).permitted
.stream()
.allMatch(covered::contains);
if (hasAll && covered.add(sup.tsym)) {
if (sup.tsym.kind == TYP) {
if (isTransitivelyCovered(sup.tsym, covered) &&
covered.add(sup.tsym)) {
todo = todo.prepend(sup.tsym);
}
}
@ -785,6 +784,26 @@ public class Flow {
}
}
private boolean isTransitivelyCovered(Symbol sealed, Set<Symbol> covered) {
DeferredCompletionFailureHandler.Handler prevHandler =
dcfh.setHandler(dcfh.speculativeCodeHandler);
try {
if (covered.stream().anyMatch(c -> sealed.isSubClass(c, types)))
return true;
if (sealed.kind == TYP && sealed.isAbstract() && sealed.isSealed()) {
return ((ClassSymbol) sealed).permitted
.stream()
.allMatch(s -> isTransitivelyCovered(s, covered));
}
return false;
} catch (CompletionFailure cf) {
//safe to ignore, the symbol will be un-completed when the speculative handler is removed.
return false;
} finally {
dcfh.setHandler(prevHandler);
}
}
private boolean isExhaustive(Type seltype, Set<Symbol> covered) {
transitiveCovers(covered);
return switch (seltype.getTag()) {

View File

@ -23,7 +23,7 @@
/**
* @test
* @bug 8262891 8268871
* @bug 8262891 8268871 8274363
* @summary Check exhaustiveness of switches over sealed types.
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
@ -775,23 +775,50 @@ public class Exhaustiveness extends TestRunner {
"1 error");
}
@Test
public void testTransitiveSealed(Path base) throws Exception {
doTest(base,
new String[0],
"""
package test;
public class Test {
sealed interface A {}
sealed interface B1 extends A {}
sealed interface B2 extends A {}
sealed interface C extends A {}
final class D1 implements B1, C {}
final class D2 implements B2, C {}
void test(A arg) {
int i = switch (arg) {
case B1 b1 -> 1;
case B2 b2 -> 2;
};
}
}
""");
}
private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException {
Path current = base.resolve(".");
Path libSrc = current.resolve("lib-src");
for (String code : libraryCode) {
tb.writeJavaFiles(libSrc, code);
}
Path libClasses = current.resolve("libClasses");
Files.createDirectories(libClasses);
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION)
.outdir(libClasses)
.files(tb.findJavaFiles(libSrc))
.run();
if (libraryCode.length != 0) {
Path libSrc = current.resolve("lib-src");
for (String code : libraryCode) {
tb.writeJavaFiles(libSrc, code);
}
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION)
.outdir(libClasses)
.files(tb.findJavaFiles(libSrc))
.run();
}
Path src = current.resolve("src");
tb.writeJavaFiles(src, testCode);