jdk-24/test/langtools/tools/javac/sealed/SealedDiffConfigurationsTest.java
2024-05-24 15:58:34 +00:00

732 lines
27 KiB
Java

/*
* Copyright (c) 2018, 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 8247352 8293348
* @summary test different configurations of sealed classes, same compilation unit, diff pkg or mdl, etc
* @library /tools/lib
* @enablePreview
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.util
* jdk.compiler/com.sun.tools.javac.code
* java.base/jdk.internal.classfile.impl
* @build toolbox.ToolBox toolbox.JavacTask
* @run main SealedDiffConfigurationsTest
*/
import java.util.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.IntStream;
import java.lang.classfile.*;
import java.lang.classfile.attribute.PermittedSubclassesAttribute;
import java.lang.classfile.constantpool.ClassEntry;
import java.lang.classfile.constantpool.ConstantPoolException;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.util.Assert;
import toolbox.TestRunner;
import toolbox.ToolBox;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.Task.OutputKind;
public class SealedDiffConfigurationsTest extends TestRunner {
ToolBox tb;
SealedDiffConfigurationsTest() {
super(System.err);
tb = new ToolBox();
}
protected void runTests() throws Exception {
runTests(m -> new Object[] { Paths.get(m.getName()) });
}
public static void main(String... args) throws Exception {
SealedDiffConfigurationsTest t = new SealedDiffConfigurationsTest();
t.runTests();
}
Path[] findJavaFiles(Path... paths) throws IOException {
return tb.findJavaFiles(paths);
}
@Test
public void testSameCompilationUnitPos(Path base) throws Exception {
Path src = base.resolve("src");
Path test = src.resolve("Test");
tb.writeJavaFiles(test,
"class Test {\n" +
" sealed class Sealed permits Sub1, Sub2 {}\n" +
" final class Sub1 extends Sealed {}\n" +
" final class Sub2 extends Sealed {}\n" +
"}");
Path out = base.resolve("out");
Files.createDirectories(out);
new JavacTask(tb)
.outdir(out)
.files(findJavaFiles(test))
.run()
.writeAll();
checkSealedClassFile(out, "Test$Sealed.class", List.of("Test$Sub1", "Test$Sub2"));
checkSubtypeClassFile(out, "Test$Sub1.class", "Test$Sealed", true);
checkSubtypeClassFile(out, "Test$Sub2.class", "Test$Sealed", true);
}
@Test
public void testSameCompilationUnitPos2(Path base) throws Exception {
Path src = base.resolve("src");
Path test = src.resolve("Test");
tb.writeJavaFiles(test,
"class Test {\n" +
" sealed class Sealed {}\n" +
" final class Sub1 extends Sealed {}\n" +
" final class Sub2 extends Sealed {}\n" +
"}");
Path out = base.resolve("out");
Files.createDirectories(out);
new JavacTask(tb)
.outdir(out)
.files(findJavaFiles(test))
.run()
.writeAll();
checkSealedClassFile(out, "Test$Sealed.class", List.of("Test$Sub1", "Test$Sub2"));
checkSubtypeClassFile(out, "Test$Sub1.class", "Test$Sealed", true);
checkSubtypeClassFile(out, "Test$Sub2.class", "Test$Sealed", true);
}
private void checkSealedClassFile(Path out, String cfName, List<String> expectedSubTypeNames) throws ConstantPoolException, Exception {
ClassModel sealedCF = ClassFile.of().parse(out.resolve(cfName));
Assert.check((sealedCF.flags().flagsMask() & ClassFile.ACC_FINAL) == 0, String.format("class at file %s must not be final", cfName));
PermittedSubclassesAttribute permittedSubclasses = sealedCF.findAttribute(Attributes.permittedSubclasses()).orElseThrow();
Assert.check(permittedSubclasses.permittedSubclasses().size() == expectedSubTypeNames.size());
List<String> subtypeNames = new ArrayList<>();
permittedSubclasses.permittedSubclasses().forEach(i -> {
try {
subtypeNames.add(i.name().stringValue());
} catch (ConstantPoolException ex) {
}
});
for (int i = 0; i < expectedSubTypeNames.size(); i++) {
Assert.check(expectedSubTypeNames.get(0).equals(subtypeNames.get(0)));
}
}
private void checkSubtypeClassFile(Path out, String cfName, String superClassName, boolean shouldBeFinal) throws Exception {
ClassModel subCF1 = ClassFile.of().parse(out.resolve(cfName));
if (shouldBeFinal) {
Assert.check((subCF1.flags().flagsMask() & ClassFile.ACC_FINAL) != 0, String.format("class at file %s must be final", cfName));
}
Assert.checkNull(subCF1.findAttribute(Attributes.permittedSubclasses()).orElse(null));
Assert.check(subCF1.superclass().orElseThrow().name().equalsString(superClassName));
}
@Test
public void testSamePackagePos(Path base) throws Exception {
Path src = base.resolve("src");
Path pkg = src.resolve("pkg");
Path sealed = pkg.resolve("Sealed");
Path sub1 = pkg.resolve("Sub1");
Path sub2 = pkg.resolve("Sub2");
tb.writeJavaFiles(sealed,
"package pkg;\n" +
"\n" +
"sealed class Sealed permits Sub1, Sub2 {\n" +
"}");
tb.writeJavaFiles(sub1,
"package pkg;\n" +
"\n" +
"final class Sub1 extends Sealed {\n" +
"}");
tb.writeJavaFiles(sub2,
"package pkg;\n" +
"\n" +
"final class Sub2 extends Sealed {\n" +
"}");
Path out = base.resolve("out");
Files.createDirectories(out);
new JavacTask(tb)
.outdir(out)
.files(findJavaFiles(pkg))
.run()
.writeAll();
checkSealedClassFile(out.resolve("pkg"), "Sealed.class", List.of("pkg/Sub1", "pkg/Sub1"));
checkSubtypeClassFile(out.resolve("pkg"), "Sub1.class", "pkg/Sealed", true);
checkSubtypeClassFile(out.resolve("pkg"), "Sub2.class", "pkg/Sealed", true);
}
@Test
public void testSameCompilationUnitNeg(Path base) throws Exception {
Path src = base.resolve("src");
Path test = src.resolve("Test");
tb.writeJavaFiles(test,
"class Test {\n" +
" sealed class Sealed permits Sub1 {}\n" +
" final class Sub1 extends Sealed {}\n" +
" class Sub2 extends Sealed {}\n" +
"}");
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics")
.files(findJavaFiles(test))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Test.java:4:5: compiler.err.cant.inherit.from.sealed: Test.Sealed",
"1 error");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Expected: " + expected);
}
}
@Test
public void testSameCompilationUnitNeg2(Path base) throws Exception {
Path src = base.resolve("src");
Path test = src.resolve("Test");
tb.writeJavaFiles(test,
"class Test {\n" +
" sealed class Sealed permits Sub1 {}\n" +
" class Sub1 extends Sealed {}\n" +
"}");
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics")
.files(findJavaFiles(test))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Test.java:3:5: compiler.err.non.sealed.sealed.or.final.expected",
"1 error");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Expected: " + expected);
}
}
@Test
public void testSamePackageNeg(Path base) throws Exception {
Path src = base.resolve("src");
Path pkg = src.resolve("pkg");
Path sealed = pkg.resolve("Sealed");
Path sub1 = pkg.resolve("Sub1");
Path sub2 = pkg.resolve("Sub2");
tb.writeJavaFiles(sealed,
"package pkg;\n" +
"\n" +
"sealed class Sealed permits Sub1 {\n" +
"}");
tb.writeJavaFiles(sub1,
"package pkg;\n" +
"\n" +
"final class Sub1 extends Sealed {\n" +
"}");
tb.writeJavaFiles(sub2,
"package pkg;\n" +
"\n" +
"class Sub2 extends Sealed {\n" +
"}");
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics")
.files(findJavaFiles(pkg))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Sub2.java:3:1: compiler.err.cant.inherit.from.sealed: pkg.Sealed",
"1 error"
);
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Expected: " + expected);
}
}
@Test
public void testSamePackageNeg2(Path base) throws Exception {
Path src = base.resolve("src");
Path pkg = src.resolve("pkg");
Path sealed = pkg.resolve("Sealed");
Path sub1 = pkg.resolve("Sub1");
tb.writeJavaFiles(sealed,
"package pkg;\n" +
"\n" +
"final class Sealed {\n" +
"}");
tb.writeJavaFiles(sub1,
"package pkg;\n" +
"\n" +
"class Sub1 extends Sealed {\n" +
"}");
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics")
.files(findJavaFiles(pkg))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Sub1.java:3:20: compiler.err.cant.inherit.from.final: pkg.Sealed",
"1 error");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Expected: " + expected);
}
}
@Test
public void testSamePackageNeg3(Path base) throws Exception {
Path src = base.resolve("src");
Path pkg = src.resolve("pkg");
Path sealed = pkg.resolve("Sealed");
Path sub1 = pkg.resolve("Sub1");
tb.writeJavaFiles(sealed,
"package pkg;\n" +
"\n" +
"sealed class Sealed permits Sub1{\n" +
"}");
tb.writeJavaFiles(sub1,
"package pkg;\n" +
"\n" +
"class Sub1 extends Sealed {\n" +
"}");
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics")
.files(findJavaFiles(pkg))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Sub1.java:3:1: compiler.err.non.sealed.sealed.or.final.expected",
"1 error");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Expected: " + expected);
}
}
@Test
public void testDiffPackageNeg(Path base) throws Exception {
Path src = base.resolve("src");
Path pkg1 = src.resolve("pkg1");
Path pkg2 = src.resolve("pkg2");
Path sealed = pkg1.resolve("Sealed");
Path sub1 = pkg2.resolve("Sub1");
Path sub2 = pkg2.resolve("Sub2");
tb.writeJavaFiles(sealed,
"package pkg1;\n" +
"import pkg2.*;\n" +
"public sealed class Sealed permits pkg2.Sub1 {\n" +
"}");
tb.writeJavaFiles(sub1,
"package pkg2;\n" +
"import pkg1.*;\n" +
"public final class Sub1 extends pkg1.Sealed {\n" +
"}");
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics")
.files(findJavaFiles(pkg1, pkg2))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Sealed.java:3:40: compiler.err.class.in.unnamed.module.cant.extend.sealed.in.diff.package: pkg1.Sealed",
"1 error");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Expected: " + expected);
}
}
@Test
public void testDiffPackageNeg2(Path base) throws Exception {
// test that the compiler rejects a subtype that is not accessible to the sealed class
Path src = base.resolve("src");
Path pkg1 = src.resolve("pkg1");
Path pkg2 = src.resolve("pkg2");
Path sealed = pkg1.resolve("Sealed");
Path sub1 = pkg2.resolve("Sub1");
Path sub2 = pkg2.resolve("Sub2");
tb.writeJavaFiles(sealed,
"package pkg1;\n" +
"import pkg2.*;\n" +
"public sealed class Sealed permits pkg2.Sub1 {\n" +
"}");
tb.writeJavaFiles(sub1,
"package pkg2;\n" +
"import pkg1.*;\n" +
"final class Sub1 extends pkg1.Sealed {\n" +
"}");
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics")
.files(findJavaFiles(pkg1, pkg2))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Sealed.java:3:40: compiler.err.not.def.public.cant.access: pkg2.Sub1, pkg2",
"Sub1.java:3:7: compiler.err.cant.inherit.from.sealed: pkg1.Sealed",
"2 errors");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Expected: " + expected);
}
}
@Test
public void testDiffPackageNeg3(Path base) throws Exception {
Path src = base.resolve("src");
Path pkg1 = src.resolve("pkg1");
Path pkg2 = src.resolve("pkg2");
Path sealed = pkg1.resolve("Sealed");
Path sub1 = pkg2.resolve("Sub1");
Path sub2 = pkg2.resolve("Sub2");
tb.writeJavaFiles(sealed,
"package pkg1;\n" +
"import pkg2.*;\n" +
"public sealed class Sealed permits pkg2.Sub1 {\n" +
"}");
tb.writeJavaFiles(sub1,
"package pkg2;\n" +
"import pkg1.*;\n" +
"public final class Sub1 extends pkg1.Sealed {\n" +
"}");
Path out = base.resolve("out");
Files.createDirectories(out);
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics")
.files(findJavaFiles(pkg1, pkg2))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Sealed.java:3:40: compiler.err.class.in.unnamed.module.cant.extend.sealed.in.diff.package: pkg1.Sealed",
"1 error");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Found: " + error);
}
}
@Test
public void testSameModuleSamePkgPos(Path base) throws Exception {
Path src = base.resolve("src");
Path src_m1 = src.resolve("mSealed");
tb.writeJavaFiles(src_m1,
"module mSealed {}",
"package pkg; public sealed class Sealed permits pkg.Sub{}",
"package pkg; public final class Sub extends pkg.Sealed{}");
Path classes = base.resolve("classes");
tb.createDirectories(classes);
new JavacTask(tb)
.options("--module-source-path", src.toString())
.outdir(classes)
.files(findJavaFiles(src))
.run()
.writeAll();
}
@Test
public void testSameModuleDiffPkgPos(Path base) throws Exception {
Path src = base.resolve("src");
Path src_m1 = src.resolve("mSealed");
tb.writeJavaFiles(src_m1,
"module mSealed {}",
"package pkg1; public sealed class Sealed permits pkg2.Sub{}",
"package pkg2; public final class Sub extends pkg1.Sealed{}");
Path classes = base.resolve("classes");
tb.createDirectories(classes);
new JavacTask(tb)
.options("--module-source-path", src.toString())
.outdir(classes)
.files(findJavaFiles(src))
.run()
.writeAll();
}
@Test
public void testSameModuleSamePkgNeg1(Path base) throws Exception {
Path src = base.resolve("src");
Path src_m1 = src.resolve("mSealed");
// subclass doesn't extend super class
tb.writeJavaFiles(src_m1,
"module mSealed {}",
"package pkg; public sealed class Sealed permits pkg.Sub {}",
"package pkg; public final class Sub {}");
Path classes = base.resolve("classes");
tb.createDirectories(classes);
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics", "--module-source-path",
src.toString())
.outdir(classes)
.files(findJavaFiles(src))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Sealed.java:1:52: compiler.err.invalid.permits.clause: (compiler.misc.doesnt.extend.sealed: pkg.Sub)",
"1 error");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Found: " + error);
}
}
@Test
public void testSameModuleSamePkgNeg2(Path base) throws Exception {
Path src = base.resolve("src");
Path src_m1 = src.resolve("mSealed");
// subclass doesn't extend super class
tb.writeJavaFiles(src_m1,
"module mSealed {}",
"package pkg; public sealed interface Sealed permits pkg.Sub1, pkg.Sub2 {}",
"package pkg; public sealed class Sub1 implements Sealed permits Sub2 {}",
"package pkg; public final class Sub2 extends Sub1 {}");
Path classes = base.resolve("classes");
tb.createDirectories(classes);
List<String> error = new JavacTask(tb)
.options("-XDrawDiagnostics", "--module-source-path",
src.toString())
.outdir(classes)
.files(findJavaFiles(src))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Sealed.java:1:66: compiler.err.invalid.permits.clause: (compiler.misc.doesnt.extend.sealed: pkg.Sub2)",
"1 error");
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Found: " + error);
}
}
@Test
public void testDifferentModuleNeg(Path base) throws Exception {
// check that a subclass in one module can't extend a sealed class in another module
Path src = base.resolve("src");
Path src_m1 = src.resolve("mSealed");
tb.writeJavaFiles(src_m1,
"module mSealed { exports a; }",
"package a; public sealed class Base permits b.Impl {}"
);
Path src_m2 = src.resolve("mSub");
tb.writeJavaFiles(src_m2,
"module mSub { exports b; requires mSealed; }",
"package b; public final class Impl extends a.Base {}"
);
Path classes = base.resolve("classes");
tb.createDirectories(classes);
List<String> error =
new JavacTask(tb)
.options("-XDrawDiagnostics",
"--module-source-path", src.toString(),
"--add-reads", "mSealed=mSub")
.outdir(classes)
.files(findJavaFiles(src))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = List.of(
"Base.java:1:46: compiler.err.class.in.module.cant.extend.sealed.in.diff.module: a.Base, mSealed",
"1 error"
);
if (!error.containsAll(expected)) {
throw new AssertionError("Expected output not found. Found: " + error);
}
}
@Test
public void testSeparateCompilation(Path base) throws Exception {
Path src = base.resolve("src");
Path src_m = src.resolve("m");
tb.writeJavaFiles(src_m,
"module m {}",
"package pkg.a; public sealed interface Sealed permits pkg.b.Sub {}",
"package pkg.b; public final class Sub implements pkg.a.Sealed {}");
Path classes = base.resolve("classes");
tb.createDirectories(classes);
new JavacTask(tb)
.options("-XDrawDiagnostics", "--module-source-path",
src.toString())
.outdir(classes)
.files(findJavaFiles(src_m))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
new JavacTask(tb)
.options("-XDrawDiagnostics", "--module-source-path",
src.toString(), "-doe")
.outdir(classes)
.files(findJavaFiles(src_m.resolve("pkg").resolve("a")))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
new JavacTask(tb)
.options("-XDrawDiagnostics", "--module-source-path",
src.toString(), "-doe")
.outdir(classes)
.files(findJavaFiles(src_m.resolve("pkg").resolve("b")))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
tb.cleanDirectory(classes);
//implicit compilations:
new JavacTask(tb)
.options("-XDrawDiagnostics", "--module-source-path",
src.toString(), "-doe")
.outdir(classes)
.files(findJavaFiles(src_m.resolve("pkg").resolve("a")))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
tb.cleanDirectory(classes);
new JavacTask(tb)
.options("-XDrawDiagnostics", "--module-source-path",
src.toString(), "-doe")
.outdir(classes)
.files(findJavaFiles(src_m.resolve("pkg").resolve("b")))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
}
@Test //JDK-8293348
public void testSupertypePermitsLoop(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"class Main implements T2 {}",
"non-sealed interface T2 extends T {}",
"sealed interface T permits T2 {}");
Path out = base.resolve("out");
Files.createDirectories(out);
new JavacTask(tb)
.outdir(out)
.files(findJavaFiles(src))
.run()
.writeAll();
Files.delete(out.resolve("Main.class"));
Files.delete(out.resolve("T.class"));
new JavacTask(tb)
.outdir(out)
.options("-cp", out.toString(),
"-sourcepath", src.toString())
.files(src.resolve("Main.java"))
.run()
.writeAll();
}
@Test
public void testClientSwapsPermittedSubclassesOrder(Path base) throws Exception {
Path src = base.resolve("src");
Path foo = src.resolve("Foo.java");
Path fooUser = src.resolve("FooUser.java");
tb.writeFile(foo,
"""
public sealed interface Foo {
record R1() implements Foo {}
record R2() implements Foo {}
}
""");
tb.writeFile(fooUser,
"""
public class FooUser {
// see that the order of arguments differ from the order of subclasses of Foo in the source above
// we need to check that the order of permitted subclasses of Foo in the class file corresponds to the
// original order in the source code
public void blah(Foo.R2 a, Foo.R1 b) {}
}
""");
Path out = base.resolve("out");
Files.createDirectories(out);
new JavacTask(tb)
.outdir(out)
.files(fooUser, foo)
.run();
checkSealedClassFile(out, "Foo.class", List.of("Foo$R1", "Foo$R2"));
}
}