8277105: Inconsistent handling of missing permitted subclasses
Reviewed-by: vromero
This commit is contained in:
parent
adf39522c1
commit
ab781874b2
@ -207,7 +207,6 @@ public class Flow {
|
|||||||
private final JCDiagnostic.Factory diags;
|
private final JCDiagnostic.Factory diags;
|
||||||
private Env<AttrContext> attrEnv;
|
private Env<AttrContext> attrEnv;
|
||||||
private Lint lint;
|
private Lint lint;
|
||||||
private final DeferredCompletionFailureHandler dcfh;
|
|
||||||
private final boolean allowEffectivelyFinalInInnerClasses;
|
private final boolean allowEffectivelyFinalInInnerClasses;
|
||||||
|
|
||||||
public static Flow instance(Context context) {
|
public static Flow instance(Context context) {
|
||||||
@ -332,7 +331,6 @@ public class Flow {
|
|||||||
lint = Lint.instance(context);
|
lint = Lint.instance(context);
|
||||||
rs = Resolve.instance(context);
|
rs = Resolve.instance(context);
|
||||||
diags = JCDiagnostic.Factory.instance(context);
|
diags = JCDiagnostic.Factory.instance(context);
|
||||||
dcfh = DeferredCompletionFailureHandler.instance(context);
|
|
||||||
Source source = Source.instance(context);
|
Source source = Source.instance(context);
|
||||||
allowEffectivelyFinalInInnerClasses = Feature.EFFECTIVELY_FINAL_IN_INNER_CLASSES.allowedInSource(source);
|
allowEffectivelyFinalInInnerClasses = Feature.EFFECTIVELY_FINAL_IN_INNER_CLASSES.allowedInSource(source);
|
||||||
}
|
}
|
||||||
@ -693,7 +691,7 @@ public class Flow {
|
|||||||
}
|
}
|
||||||
if (!tree.hasTotalPattern && exhaustiveSwitch &&
|
if (!tree.hasTotalPattern && exhaustiveSwitch &&
|
||||||
!TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) &&
|
!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);
|
log.error(tree, Errors.NotExhaustiveStatement);
|
||||||
}
|
}
|
||||||
if (!tree.hasTotalPattern) {
|
if (!tree.hasTotalPattern) {
|
||||||
@ -728,7 +726,7 @@ public class Flow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!tree.hasTotalPattern && !TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) &&
|
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);
|
log.error(tree, Errors.NotExhaustive);
|
||||||
}
|
}
|
||||||
alive = prevAlive;
|
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);
|
List<Symbol> todo = List.from(covered);
|
||||||
while (todo.nonEmpty()) {
|
while (todo.nonEmpty()) {
|
||||||
Symbol sym = todo.head;
|
Symbol sym = todo.head;
|
||||||
@ -773,7 +771,7 @@ public class Flow {
|
|||||||
case TYP -> {
|
case TYP -> {
|
||||||
for (Type sup : types.directSupertypes(sym.type)) {
|
for (Type sup : types.directSupertypes(sym.type)) {
|
||||||
if (sup.tsym.kind == TYP) {
|
if (sup.tsym.kind == TYP) {
|
||||||
if (isTransitivelyCovered(seltype, sup.tsym, covered) &&
|
if (isTransitivelyCovered(pos, seltype, sup.tsym, covered) &&
|
||||||
covered.add(sup.tsym)) {
|
covered.add(sup.tsym)) {
|
||||||
todo = todo.prepend(sup.tsym);
|
todo = todo.prepend(sup.tsym);
|
||||||
}
|
}
|
||||||
@ -784,9 +782,8 @@ public class Flow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTransitivelyCovered(Type seltype, Symbol sealed, Set<Symbol> covered) {
|
private boolean isTransitivelyCovered(DiagnosticPosition pos, Type seltype,
|
||||||
DeferredCompletionFailureHandler.Handler prevHandler =
|
Symbol sealed, Set<Symbol> covered) {
|
||||||
dcfh.setHandler(dcfh.speculativeCodeHandler);
|
|
||||||
try {
|
try {
|
||||||
if (covered.stream().anyMatch(c -> sealed.isSubClass(c, types)))
|
if (covered.stream().anyMatch(c -> sealed.isSubClass(c, types)))
|
||||||
return true;
|
return true;
|
||||||
@ -796,30 +793,30 @@ public class Flow {
|
|||||||
.filter(s -> {
|
.filter(s -> {
|
||||||
return types.isCastable(seltype, s.type/*, types.noWarnings*/);
|
return types.isCastable(seltype, s.type/*, types.noWarnings*/);
|
||||||
})
|
})
|
||||||
.allMatch(s -> isTransitivelyCovered(seltype, s, covered));
|
.allMatch(s -> isTransitivelyCovered(pos, seltype, s, covered));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (CompletionFailure cf) {
|
} catch (CompletionFailure cf) {
|
||||||
//safe to ignore, the symbol will be un-completed when the speculative handler is removed.
|
chk.completionError(pos, cf);
|
||||||
return false;
|
return true;
|
||||||
} finally {
|
|
||||||
dcfh.setHandler(prevHandler);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isExhaustive(Type seltype, Set<Symbol> covered) {
|
private boolean isExhaustive(DiagnosticPosition pos, Type seltype, Set<Symbol> covered) {
|
||||||
transitiveCovers(seltype, covered);
|
transitiveCovers(pos, seltype, covered);
|
||||||
return switch (seltype.getTag()) {
|
return switch (seltype.getTag()) {
|
||||||
case CLASS -> {
|
case CLASS -> {
|
||||||
if (seltype.isCompound()) {
|
if (seltype.isCompound()) {
|
||||||
if (seltype.isIntersection()) {
|
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 false;
|
||||||
}
|
}
|
||||||
yield covered.contains(seltype.tsym);
|
yield covered.contains(seltype.tsym);
|
||||||
}
|
}
|
||||||
case TYPEVAR -> isExhaustive(((TypeVar) seltype).getUpperBound(), covered);
|
case TYPEVAR -> isExhaustive(pos, ((TypeVar) seltype).getUpperBound(), covered);
|
||||||
default -> false;
|
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
|
@Test
|
||||||
public void testExhaustiveStatement1(Path base) throws Exception {
|
public void testExhaustiveStatement1(Path base) throws Exception {
|
||||||
doTest(base,
|
doTest(base,
|
||||||
|
199
test/langtools/tools/javac/sealed/MissingPermittedSubtypes.java
Normal file
199
test/langtools/tools/javac/sealed/MissingPermittedSubtypes.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user