diff --git a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java index 154810e20f2..a88eabe01b2 100644 --- a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java +++ b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2024, 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 @@ -970,7 +970,7 @@ public class CreateSymbols { } if (header.moduleMainClass != null) { int attrIdx = addString(cp, Attribute.ModuleMainClass); - int targetIdx = addString(cp, header.moduleMainClass); + int targetIdx = addClassName(cp, header.moduleMainClass); attributes.put(Attribute.ModuleMainClass, new ModuleMainClass_attribute(attrIdx, targetIdx)); } diff --git a/test/langtools/tools/javac/platform/VerifyCTSymClassFiles.java b/test/langtools/tools/javac/platform/VerifyCTSymClassFiles.java new file mode 100644 index 00000000000..c97b15a2b15 --- /dev/null +++ b/test/langtools/tools/javac/platform/VerifyCTSymClassFiles.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, 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 8331027 + * @summary Verify classfile inside ct.sym + * @enablePreview + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.platform + * jdk.compiler/com.sun.tools.javac.util:+open + * @build toolbox.ToolBox VerifyCTSymClassFiles + * @run main VerifyCTSymClassFiles + */ + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.classfile.ClassFile; +import java.lang.classfile.attribute.ModuleMainClassAttribute; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class VerifyCTSymClassFiles { + + public static void main(String... args) throws IOException, URISyntaxException { + VerifyCTSymClassFiles t = new VerifyCTSymClassFiles(); + + t.checkClassFiles(); + } + + void checkClassFiles() throws IOException { + Path ctSym = Paths.get(System.getProperty("java.home"), "lib", "ct.sym"); + + if (!Files.exists(ctSym)) { + //no ct.sym, nothing to check: + return ; + } + try (FileSystem fs = FileSystems.newFileSystem(ctSym)) { + Files.walk(fs.getRootDirectories().iterator().next()) + .filter(p -> Files.isRegularFile(p)) + .forEach(p -> checkClassFile(p)); + } + } + + void checkClassFile(Path p) { + if (!"module-info.sig".equals(p.getFileName().toString())) { + return ; + } + try { + ClassFile.of().parse(p).attributes().forEach(attr -> { + if (attr instanceof ModuleMainClassAttribute mmca) { + mmca.mainClass(); + } + }); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + +} diff --git a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTest.java b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTest.java index c54c21bfc0d..7f7400a65b4 100644 --- a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTest.java +++ b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -23,8 +23,9 @@ /** * @test - * @bug 8072480 8277106 + * @bug 8072480 8277106 8331027 * @summary Unit test for CreateSymbols + * @enablePreview * @modules java.compiler * jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.jvm @@ -101,6 +102,9 @@ public class CreateSymbolsTest { null, List.of("-d", compileDir.toAbsolutePath().toString(), + "--enable-preview", + "--source", + "" + System.getProperty("java.specification.version"), "-g", "--add-modules", "jdk.jdeps", "--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", diff --git a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java index be9137226b4..3a4fd85e54e 100644 --- a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java +++ b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java @@ -26,6 +26,11 @@ import java.io.InputStream; import java.io.Writer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.attribute.ModuleAttribute; +import java.lang.classfile.attribute.ModulePackagesAttribute; +import java.lang.constant.PackageDesc; import java.lang.reflect.Method; import java.util.Arrays; import java.util.ArrayList; @@ -58,14 +63,11 @@ import build.tools.symbolgenerator.CreateSymbols.ClassDescription; import build.tools.symbolgenerator.CreateSymbols.ClassList; import build.tools.symbolgenerator.CreateSymbols.ExcludeIncludeList; import build.tools.symbolgenerator.CreateSymbols.VersionDescription; -import com.sun.tools.classfile.Attribute; -import com.sun.tools.classfile.Attributes; -import com.sun.tools.classfile.ClassFile; -import com.sun.tools.classfile.ClassWriter; -import com.sun.tools.classfile.ConstantPool; -import com.sun.tools.classfile.ConstantPool.CPInfo; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info; -import com.sun.tools.classfile.ModulePackages_attribute; +import java.io.UncheckedIOException; +import java.lang.classfile.attribute.ModuleMainClassAttribute; +import java.lang.constant.ClassDesc; +import java.util.Objects; +import java.util.function.Consumer; public class CreateSymbolsTestImpl { @@ -981,14 +983,19 @@ public class CreateSymbolsTestImpl { } Path prepareVersionedCTSym(String[] code7, String[] code8) throws Exception { + return prepareVersionedCTSym(code7, code8, _ -> {}); + } + + Path prepareVersionedCTSym(String[] code7, String[] code8, + Consumer adjustClassFiles) throws Exception { String testClasses = System.getProperty("test.classes"); Path output = Paths.get(testClasses, "test-data" + i++); deleteRecursively(output); Files.createDirectories(output); Path ver7Jar = output.resolve("7.jar"); - compileAndPack(output, ver7Jar, code7); + compileAndPack(output, ver7Jar, adjustClassFiles, code7); Path ver8Jar = output.resolve("8.jar"); - compileAndPack(output, ver8Jar, code8); + compileAndPack(output, ver8Jar, adjustClassFiles, code8); Path classes = output.resolve("classes.zip"); @@ -1048,7 +1055,112 @@ public class CreateSymbolsTestImpl { new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classDest, 0, "8", "", modules.toString(), modulesList.toString()); } + @Test + void testModuleMainClass() throws Exception { + ClassFile cf = ClassFile.of(); + ToolBox tb = new ToolBox(); + String testClasses = System.getProperty("test.classes"); + Path output = Paths.get(testClasses, "test-data" + i++); + deleteRecursively(output); + Files.createDirectories(output); + Path ver9Jar = output.resolve("9.jar"); + compileAndPack(output, + ver9Jar, + classesDir -> { + try { + Path moduleInfo = classesDir.resolve("module-info.class"); + byte[] newClassData = + cf.transform(cf.parse(moduleInfo), + (builder, element) -> { + builder.with(element); + if (element instanceof ModuleAttribute) { + builder.with(ModuleMainClassAttribute.of(ClassDesc.of("main.Main"))); + } + }); + try (OutputStream out = Files.newOutputStream(moduleInfo)) { + out.write(newClassData); + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }, + """ + module m { + } + """, + """ + package main; + public class Main {} + """); + + + Path ctSym = output.resolve("ct.sym"); + + deleteRecursively(ctSym); + + CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true; + CreateSymbols.EXTENSION = ".class"; + + List versions = + Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null)); + + ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) { + @Override public boolean accepts(String className, boolean includePrivateClasses) { + return true; + } + }; + new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]); + Path symbolsDesc = ctSym.resolve("symbols"); + Path modules = ctSym.resolve("modules"); + Path modulesList = ctSym.resolve("modules-list"); + + Files.createDirectories(modules); + try (Writer w = Files.newBufferedWriter(modulesList)) {} + + Path classesZip = output.resolve("classes.zip"); + Path classesDir = output.resolve("classes"); + + new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classesZip.toAbsolutePath().toString(), 0, "9", "", modules.toString(), modulesList.toString()); + + try (JarFile jf = new JarFile(classesZip.toFile())) { + Enumeration en = jf.entries(); + + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + if (je.isDirectory()) continue; + Path target = classesDir.resolve(je.getName()); + Files.createDirectories(target.getParent()); + Files.copy(jf.getInputStream(je), target); + } + } + + Path moduleInfo = classesDir.resolve("9") + .resolve("m") + .resolve("module-info.class"); + + cf.parse(moduleInfo) + .attributes() + .stream() + .filter(attr -> attr instanceof ModuleMainClassAttribute) + .forEach(attr -> { + String expectedMain = "Lmain/Main;"; + String mainClass = + ((ModuleMainClassAttribute) attr).mainClass() + .asSymbol() + .descriptorString(); + if (!Objects.equals(expectedMain, mainClass)) { + throw new AssertionError("Expected " + expectedMain + " as a main class, " + + "but got: " + mainClass); + } + }); + } + void compileAndPack(Path output, Path outputFile, String... code) throws Exception { + compileAndPack(output, outputFile, _ -> {}, code); + } + + void compileAndPack(Path output, Path outputFile, + Consumer adjustClassFiles, String... code) throws Exception { ToolBox tb = new ToolBox(); Path scratch = output.resolve("temp"); deleteRecursively(scratch); @@ -1065,27 +1177,21 @@ public class CreateSymbolsTestImpl { packages.add(cf.substring(0, sep)); } } - ClassFile cf = ClassFile.read(moduleInfo); - List cp = new ArrayList<>(); - cp.add(null); - cf.constant_pool.entries().forEach(cp::add); - Map attrs = new HashMap<>(cf.attributes.map); - int[] encodedPackages = new int[packages.size()]; - int i = 0; - for (String p : packages) { - int nameIndex = cp.size(); - cp.add(new CONSTANT_Utf8_info(p)); - encodedPackages[i++] = cp.size(); - cp.add(new ConstantPool.CONSTANT_Package_info(null, nameIndex)); - } - int attrName = cp.size(); - cp.add(new CONSTANT_Utf8_info(Attribute.ModulePackages)); - attrs.put(Attribute.ModulePackages, new ModulePackages_attribute(attrName, encodedPackages)); - ClassFile newFile = new ClassFile(cf.magic, cf.minor_version, cf.major_version, new ConstantPool(cp.toArray(new CPInfo[0])), cf.access_flags, cf.this_class, cf.super_class, cf.interfaces, cf.fields, cf.methods, new Attributes(attrs)); + ClassFile cf = ClassFile.of(); + ClassModel cm = cf.parse(moduleInfo); + byte[] newData = cf.transform(cm, (builder, element) -> { + builder.with(element); + if (element instanceof ModuleAttribute) { + builder.with(ModulePackagesAttribute.ofNames(packages.stream() + .map(pack -> PackageDesc.of(pack)) + .toList())); + } + }); try (OutputStream out = Files.newOutputStream(moduleInfo)) { - new ClassWriter().write(newFile, out); + out.write(newData); } } + adjustClassFiles.accept(scratch); try (Writer out = Files.newBufferedWriter(outputFile)) { for (String classFile : classFiles) { try (InputStream in = Files.newInputStream(scratch.resolve(classFile))) {