8277105: Inconsistent handling of missing permitted subclasses

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2021-12-06 15:43:42 +00:00
parent adf39522c1
commit ab781874b2
3 changed files with 214 additions and 114 deletions
src/jdk.compiler/share/classes/com/sun/tools/javac/comp
test/langtools/tools/javac

@ -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 @@ 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);
}
@ -693,7 +691,7 @@ public class Flow {
}
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 class Flow {
}
}
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 @@ public class Flow {
}
}
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 @@ public class Flow {
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 @@ public class Flow {
}
}
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 @@ public class Flow {
.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 @@ public class Exhaustiveness extends TestRunner {
""");
}
@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,

@ -0,0 +1,199 @@
/*
* Copyright (c) 2021, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8277105
* @summary Verify missing permitted subtype is handled properly for both casts and pattern switches.
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.util
* @build toolbox.ToolBox toolbox.JavacTask
* @run main MissingPermittedSubtypes
*/
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import toolbox.TestRunner;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.ToolBox;
public class MissingPermittedSubtypes extends TestRunner {
private static final String JAVA_VERSION = System.getProperty("java.specification.version");
ToolBox tb;
public static void main(String... args) throws Exception {
new MissingPermittedSubtypes().runTests();
}
MissingPermittedSubtypes() {
super(System.err);
tb = new ToolBox();
}
public void runTests() throws Exception {
runTests(m -> new Object[] { Paths.get(m.getName()) });
}
@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, B1, B2 {}
""",
"""
package lib;
public final class A implements S {}
""",
"""
package lib;
final class B1 implements S {}
""",
"""
package lib;
final class B2 implements S, Runnable {
public void run() {}
}
""");
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 b1Class = libClasses.resolve("lib").resolve("B1.class");
Files.delete(b1Class);
Path b2Class = libClasses.resolve("lib").resolve("B2.class");
Files.delete(b2Class);
{
Path src1 = current.resolve("src1");
tb.writeJavaFiles(src1,
"""
package test;
import lib.*;
public class Test1 {
private void test(S obj) {
int i = switch (obj) {
case A a -> 0;
};
Runnable r = () -> {obj = null;};
}
}
""");
Path classes1 = current.resolve("classes1");
Files.createDirectories(classes1);
var log =
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION,
"-XDrawDiagnostics",
"-Xlint:-preview",
"--class-path", libClasses.toString())
.outdir(classes1)
.files(tb.findJavaFiles(src1))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);
List<String> expectedErrors = List.of(
"Test1.java:5:24: compiler.err.cant.access: lib.B1, (compiler.misc.class.file.not.found: lib.B1)",
"Test1.java:8:29: compiler.err.cant.ref.non.effectively.final.var: obj, (compiler.misc.lambda)",
"- compiler.note.preview.filename: Test1.java, DEFAULT",
"- compiler.note.preview.recompile",
"2 errors");
if (!expectedErrors.equals(log)) {
throw new AssertionError("Incorrect errors, expected: " + expectedErrors +
", actual: " + log);
}
}
{
Path src2 = current.resolve("src2");
tb.writeJavaFiles(src2,
"""
package test;
import lib.*;
public class Test1 {
private void test(S obj) {
Runnable r = (Runnable) obj;
String s = (String) obj;
}
}
""");
Path classes2 = current.resolve("classes2");
Files.createDirectories(classes2);
var log =
new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION,
"-XDrawDiagnostics",
"-Xlint:-preview",
"--class-path", libClasses.toString())
.outdir(classes2)
.files(tb.findJavaFiles(src2))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);
List<String> expectedErrors = List.of(
"Test1.java:5:19: compiler.err.cant.access: lib.B1, (compiler.misc.class.file.not.found: lib.B1)",
"Test1.java:6:26: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: lib.S, java.lang.String)",
"2 errors");
if (!expectedErrors.equals(log)) {
throw new AssertionError("Incorrect errors, expected: " + expectedErrors +
", actual: " + log);
}
}
}
}