Skip to content
Permalink
Browse files
8277105: Inconsistent handling of missing permitted subclasses
Reviewed-by: vromero
  • Loading branch information
Jan Lahoda committed Dec 6, 2021
1 parent adf3952 commit ab781874b27ee4fe1bc6b5fa2cd7997e451e2026
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 114 deletions.
@@ -207,7 +207,6 @@ 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) {
@@ -332,7 +331,6 @@ protected Flow(Context context) {
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);
}
@@ -693,7 +691,7 @@ public void visitSwitch(JCSwitch tree) {
}
if (!tree.hasTotalPattern && exhaustiveSwitch &&
!TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) &&
(constants == null || !isExhaustive(tree.selector.type, constants))) {
(constants == null || !isExhaustive(tree.selector.pos(), tree.selector.type, constants))) {
log.error(tree, Errors.NotExhaustiveStatement);
}
if (!tree.hasTotalPattern) {
@@ -728,7 +726,7 @@ public void visitSwitchExpression(JCSwitchExpression tree) {
}
}
if (!tree.hasTotalPattern && !TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) &&
!isExhaustive(tree.selector.type, constants)) {
!isExhaustive(tree.selector.pos(), tree.selector.type, constants)) {
log.error(tree, Errors.NotExhaustive);
}
alive = prevAlive;
@@ -751,7 +749,7 @@ private void handleConstantCaseLabel(Set<Symbol> constants, JCCaseLabel pat) {
}
}

private void transitiveCovers(Type seltype, Set<Symbol> covered) {
private void transitiveCovers(DiagnosticPosition pos, Type seltype, Set<Symbol> covered) {
List<Symbol> todo = List.from(covered);
while (todo.nonEmpty()) {
Symbol sym = todo.head;
@@ -773,7 +771,7 @@ private void transitiveCovers(Type seltype, Set<Symbol> covered) {
case TYP -> {
for (Type sup : types.directSupertypes(sym.type)) {
if (sup.tsym.kind == TYP) {
if (isTransitivelyCovered(seltype, sup.tsym, covered) &&
if (isTransitivelyCovered(pos, seltype, sup.tsym, covered) &&
covered.add(sup.tsym)) {
todo = todo.prepend(sup.tsym);
}
@@ -784,9 +782,8 @@ private void transitiveCovers(Type seltype, Set<Symbol> covered) {
}
}

private boolean isTransitivelyCovered(Type seltype, Symbol sealed, Set<Symbol> covered) {
DeferredCompletionFailureHandler.Handler prevHandler =
dcfh.setHandler(dcfh.speculativeCodeHandler);
private boolean isTransitivelyCovered(DiagnosticPosition pos, Type seltype,
Symbol sealed, Set<Symbol> covered) {
try {
if (covered.stream().anyMatch(c -> sealed.isSubClass(c, types)))
return true;
@@ -796,30 +793,30 @@ private boolean isTransitivelyCovered(Type seltype, Symbol sealed, Set<Symbol> c
.filter(s -> {
return types.isCastable(seltype, s.type/*, types.noWarnings*/);
})
.allMatch(s -> isTransitivelyCovered(seltype, s, covered));
.allMatch(s -> isTransitivelyCovered(pos, seltype, 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);
chk.completionError(pos, cf);
return true;
}
}

private boolean isExhaustive(Type seltype, Set<Symbol> covered) {
transitiveCovers(seltype, covered);
private boolean isExhaustive(DiagnosticPosition pos, Type seltype, Set<Symbol> covered) {
transitiveCovers(pos, seltype, covered);
return switch (seltype.getTag()) {
case CLASS -> {
if (seltype.isCompound()) {
if (seltype.isIntersection()) {
yield ((Type.IntersectionClassType) seltype).getComponents().stream().anyMatch(t -> isExhaustive(t, covered));
yield ((Type.IntersectionClassType) seltype).getComponents()
.stream()
.anyMatch(t -> isExhaustive(pos, t, covered));
}
yield false;
}
yield covered.contains(seltype.tsym);
}
case TYPEVAR -> isExhaustive(((TypeVar) seltype).getUpperBound(), covered);
case TYPEVAR -> isExhaustive(pos, ((TypeVar) seltype).getUpperBound(), covered);
default -> false;
};
}
@@ -369,102 +369,6 @@ private int test(S obj) {
""");
}

@Test
public void testInaccessiblePermitted(Path base) throws IOException {
Path current = base.resolve(".");
Path libSrc = current.resolve("lib-src");

tb.writeJavaFiles(libSrc,
"""
package lib;
public sealed interface S permits A, B {}
""",
"""
package lib;
public final class A implements S {}
""",
"""
package lib;
final class B implements S {}
""");

Path libClasses = current.resolve("libClasses");

Files.createDirectories(libClasses);

new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION)
.outdir(libClasses)
.files(tb.findJavaFiles(libSrc))
.run();

Path src = current.resolve("src");
tb.writeJavaFiles(src,
"""
package test;
import lib.*;
public class Test {
private int test(S obj) {
return switch (obj) {
case A a -> 0;
};
}
}
""");

Path classes = current.resolve("libClasses");

Files.createDirectories(libClasses);

var log =
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION,
"-XDrawDiagnostics",
"-Xlint:-preview",
"--class-path", libClasses.toString())
.outdir(classes)
.files(tb.findJavaFiles(src))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);

List<String> expectedErrors = List.of(
"Test.java:5:16: compiler.err.not.exhaustive",
"- compiler.note.preview.filename: Test.java, DEFAULT",
"- compiler.note.preview.recompile",
"1 error");

if (!expectedErrors.equals(log)) {
throw new AssertionError("Incorrect errors, expected: " + expectedErrors +
", actual: " + log);
}

Path bClass = libClasses.resolve("lib").resolve("B.class");

Files.delete(bClass);

var log2 =
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION,
"-XDrawDiagnostics",
"-Xlint:-preview",
"--class-path", libClasses.toString())
.outdir(classes)
.files(tb.findJavaFiles(src))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);

if (!expectedErrors.equals(log2)) {
throw new AssertionError("Incorrect errors, expected: " + expectedErrors +
", actual: " + log2);
}

}

@Test
public void testExhaustiveStatement1(Path base) throws Exception {
doTest(base,

1 comment on commit ab78187

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on ab78187 Dec 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.