diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java index 689c8b24743e9..d53589dc388e3 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java @@ -51,6 +51,7 @@ import jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin; import jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin; import jdk.tools.jlink.internal.plugins.PluginsResourceBundle; +import jdk.tools.jlink.internal.plugins.StripJavaDebugAttributesPlugin; import jdk.tools.jlink.plugin.Plugin; import jdk.tools.jlink.plugin.Plugin.Category; @@ -418,6 +419,9 @@ private PluginsConfiguration getPluginsConfig(Path output, Map l List pluginsList = new ArrayList<>(); Set seenPlugins = new HashSet<>(); + // reference to the enabled DefaultStripDebugPlugin + DefaultStripDebugPlugin defaultStripDebugPlugin = null; + for (Entry>> entry : pluginToMaps.entrySet()) { Plugin plugin = entry.getKey(); List> argsMaps = entry.getValue(); @@ -438,6 +442,10 @@ private PluginsConfiguration getPluginsConfig(Path output, Map l } if (!Utils.isDisabled(plugin)) { + if (plugin instanceof DefaultStripDebugPlugin p) { + defaultStripDebugPlugin = p; + } + // make sure that --strip-debug and --strip-native-debug-symbols // aren't being used at the same time. --strip-debug invokes --strip-native-debug-symbols on // platforms that support it, so it makes little sense to allow both at the same time. @@ -452,6 +460,11 @@ private PluginsConfiguration getPluginsConfig(Path output, Map l } } + // disable StripJavaDebugAttributesPlugin within DefaultStripDebug plugin if both enabled + if (seenPlugins.contains(StripJavaDebugAttributesPlugin.NAME) && defaultStripDebugPlugin != null) { + defaultStripDebugPlugin.enableJavaStripPlugin(false); + } + // recreate or postprocessing don't require an output directory. ImageBuilder builder = null; if (output != null) { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/DefaultStripDebugPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/DefaultStripDebugPlugin.java index e497083cc949a..b3644bdde857f 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/DefaultStripDebugPlugin.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/DefaultStripDebugPlugin.java @@ -47,6 +47,8 @@ public final class DefaultStripDebugPlugin extends AbstractPlugin { private final Plugin javaStripPlugin; private final NativePluginFactory stripNativePluginFactory; + private boolean isJavaStripPluginEnabled = true; + public DefaultStripDebugPlugin() { this(new StripJavaDebugAttributesPlugin(), new DefaultNativePluginFactory()); @@ -59,6 +61,10 @@ public DefaultStripDebugPlugin(Plugin javaStripPlugin, this.stripNativePluginFactory = nativeStripPluginFact; } + public void enableJavaStripPlugin(boolean enableJavaStripPlugin) { + isJavaStripPluginEnabled = enableJavaStripPlugin; + } + @Override public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { Plugin stripNativePlugin = stripNativePluginFactory.create(); @@ -66,14 +72,21 @@ public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { Map stripNativeConfig = Map.of( STRIP_NATIVE_DEBUG_PLUGIN, EXCLUDE_DEBUGINFO); stripNativePlugin.configure(stripNativeConfig); + + if (!isJavaStripPluginEnabled) { + return stripNativePlugin.transform(in, out); + } + ResourcePoolManager outRes = new ResourcePoolManager(in.byteOrder(), ((ResourcePoolImpl)in).getStringTable()); ResourcePool strippedJava = javaStripPlugin.transform(in, outRes.resourcePoolBuilder()); return stripNativePlugin.transform(strippedJava, out); - } else { + } else if (isJavaStripPluginEnabled) { return javaStripPlugin.transform(in, out); + } else { + return in; } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/StripJavaDebugAttributesPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/StripJavaDebugAttributesPlugin.java index fff585bb2dd31..4a6b54a3b6c83 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/StripJavaDebugAttributesPlugin.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/StripJavaDebugAttributesPlugin.java @@ -43,13 +43,14 @@ */ public final class StripJavaDebugAttributesPlugin extends AbstractPlugin { private final Predicate predicate; + public static final String NAME = "strip-java-debug-attributes"; public StripJavaDebugAttributesPlugin() { this((path) -> false); } StripJavaDebugAttributesPlugin(Predicate predicate) { - super("strip-java-debug-attributes"); + super(NAME); this.predicate = predicate; } @@ -69,11 +70,9 @@ public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { ClassFile.LineNumbersOption.DROP_LINE_NUMBERS); byte[] content = ClassFile.of().transformClass(clm, ClassTransform .dropping(cle -> cle instanceof SourceFileAttribute - || cle instanceof SourceDebugExtensionAttribute) - .andThen(ClassTransform.transformingMethods(MethodTransform - .dropping(me -> me instanceof MethodParametersAttribute) - .andThen(MethodTransform - .transformingCode(CodeTransform.ACCEPT_ALL))))); + || cle instanceof SourceDebugExtensionAttribute) + .andThen(ClassTransform.transformingMethods(MethodTransform + .transformingCode(CodeTransform.ACCEPT_ALL)))); res = resource.copyWithContent(content); } } diff --git a/test/jdk/tools/jlink/plugins/DefaultStripDebugPluginTest.java b/test/jdk/tools/jlink/plugins/DefaultStripDebugPluginTest.java index 10d534348fab8..4b131484d9d77 100644 --- a/test/jdk/tools/jlink/plugins/DefaultStripDebugPluginTest.java +++ b/test/jdk/tools/jlink/plugins/DefaultStripDebugPluginTest.java @@ -73,6 +73,40 @@ public void testNoNativeStripPluginPresent() { } } + public void testOnlyNativePlugin() { + MockStripPlugin javaPlugin = new MockStripPlugin(false); + MockStripPlugin nativePlugin = new MockStripPlugin(true); + TestNativeStripPluginFactory nativeFactory = + new TestNativeStripPluginFactory(nativePlugin); + DefaultStripDebugPlugin plugin = new DefaultStripDebugPlugin(javaPlugin, + nativeFactory); + plugin.enableJavaStripPlugin(false); + + ResourcePoolManager inManager = new ResourcePoolManager(); + ResourcePool pool = plugin.transform(inManager.resourcePool(), + inManager.resourcePoolBuilder()); + if (pool.findEntry(MockStripPlugin.JAVA_PATH).isPresent() || + !pool.findEntry(MockStripPlugin.NATIVE_PATH).isPresent()) { + throw new AssertionError("Expected only native to get called"); + } + } + + public void testNoOperation() { + MockStripPlugin javaPlugin = new MockStripPlugin(false); + TestNativeStripPluginFactory nativeFactory = + new TestNativeStripPluginFactory(null); + DefaultStripDebugPlugin plugin = new DefaultStripDebugPlugin(javaPlugin, + nativeFactory); + plugin.enableJavaStripPlugin(false); + ResourcePoolManager inManager = new ResourcePoolManager(); + ResourcePool pool = plugin.transform(inManager.resourcePool(), + inManager.resourcePoolBuilder()); + if (pool.findEntry(MockStripPlugin.JAVA_PATH).isPresent() || + pool.findEntry(MockStripPlugin.NATIVE_PATH).isPresent()) { + throw new AssertionError("Expected both native and java not called"); + } + } + public static void main(String[] args) { DefaultStripDebugPluginTest test = new DefaultStripDebugPluginTest(); test.testNoNativeStripPluginPresent(); diff --git a/test/jdk/tools/jlink/plugins/StripParameterNamesTest.java b/test/jdk/tools/jlink/plugins/StripParameterNamesTest.java new file mode 100644 index 0000000000000..10e58f25997b6 --- /dev/null +++ b/test/jdk/tools/jlink/plugins/StripParameterNamesTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2025, 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. + */ + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.FieldSource; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; +import java.util.spi.ToolProvider; + +import tests.JImageGenerator; +import tests.JImageGenerator.InMemorySourceFile; +import tests.Result; + +/* + * @test + * @summary Test jlink strip debug plugins handle method parameter names. + * @bug 8347007 + * @library ../../lib + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jmod + * jdk.jlink/jdk.tools.jimage + * jdk.compiler + * @build tests.* + * @run junit/othervm StripParameterNamesTest + */ +public class StripParameterNamesTest { + private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac") + .orElseThrow(() -> new RuntimeException("javac tool not found")); + + private static Path src = Paths.get("src").toAbsolutePath(); + private static List testJmods = new ArrayList<>(); + + record Jmod(Path moduleDir, boolean withDebugInfo, boolean withParameterNames) {} + + @BeforeAll + public static void setup() throws IOException { + Files.createDirectory(src); + var mainClassSource = new InMemorySourceFile("test", "InspectParameterNames", """ + package test; + + public class InspectParameterNames { + int add(int a, int b) { + return a + b; + } + + public static boolean hasParameterNames() throws NoSuchMethodException { + // Get add method in the class + var method = InspectParameterNames.class.getDeclaredMethod("add", int.class, int.class); + + // Get method parameters + var parameters = method.getParameters(); + + // validate parameter names + return parameters[0].getName().equals("a") && parameters[1].getName().equals("b"); + } + + public static void main(String[] args) throws NoSuchMethodException { + System.out.println(hasParameterNames()); + } + } + """); + var moduleDir = JImageGenerator.generateSources(src, "bug8347007x", List.of(mainClassSource)); + JImageGenerator.generateModuleInfo(moduleDir, List.of("test")); + testJmods.add(buildJmod(true, true)); + testJmods.add(buildJmod(true, false)); + testJmods.add(buildJmod(false, true)); + testJmods.add(buildJmod(false, false)); + } + + @AfterEach + public void cleanup() throws IOException { + rmdir(Paths.get("img")); + } + + static void report(String command, List args) { + System.out.println(command + " " + String.join(" ", args)); + } + + static void javac(List args) { + report("javac", args); + JAVAC_TOOL.run(System.out, System.err, args.toArray(new String[0])); + } + /** + * Recursively remove a Directory + * + * @param dir Directory to delete + * @throws IOException If an error occurs + */ + static void rmdir(Path dir) throws IOException { + // Nothing to do if the file does not exist + if (!Files.exists(dir)) { + return; + } + try (Stream walk = Files.walk(dir)) { + walk.sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + /** + * Build jmods from the module source path + */ + static Jmod buildJmod(boolean withDebugInfo, boolean withParameterNames) { + String dirName = "jmods"; + List options = new ArrayList<>(); + + if (withDebugInfo) { + options.add("-g"); + dirName += "g"; + } + + if (withParameterNames) { + options.add("-parameters"); + dirName += "p"; + } + + Path moduleDir = Paths.get(dirName).toAbsolutePath(); + + options.add("-d"); + options.add(moduleDir.toString()); + options.add("--module-source-path"); + options.add(src.toString()); + options.add("--module"); + options.add("bug8347007x"); + + javac(options); + return new Jmod(moduleDir, withDebugInfo, withParameterNames); + } + + Result buildImage(Path modulePath, Path imageDir, String... options) { + var jlinkTask = JImageGenerator.getJLinkTask() + .modulePath(modulePath.toString()) + .output(imageDir); + + for (var option: options) { + jlinkTask.option(option); + } + + return jlinkTask.addMods("bug8347007x") + .call(); + } + + void assertHasParameterNames(Path imageDir, boolean expected) throws IOException, InterruptedException { + Path binDir = imageDir.resolve("bin").toAbsolutePath(); + Path bin = binDir.resolve("java"); + + ProcessBuilder processBuilder = new ProcessBuilder(bin.toString(), + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+BytecodeVerificationLocal", + "-m", "bug8347007x/test.InspectParameterNames"); + processBuilder.directory(binDir.toFile()); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + var output = process.inputReader().readLine(); + System.out.println(output); + assertEquals(expected, Boolean.parseBoolean(output)); + } + + Stream provideTestJmods() { + return testJmods.stream(); + } + + @ParameterizedTest + @FieldSource("testJmods") + public void testDefaultBehavior(Jmod jmod) throws Exception { + var imageDir = Paths.get("img"); + buildImage(jmod.moduleDir(), imageDir) + .assertSuccess(); + var hasParameter = jmod.withParameterNames(); + assertHasParameterNames(imageDir, hasParameter); + } + + @ParameterizedTest + @FieldSource("testJmods") + public void testStripDebug(Jmod jmod) throws Exception { + var imageDir = Paths.get("img"); + buildImage(jmod.moduleDir(), imageDir, + "--strip-debug") + .assertSuccess(); + var hasParameter = jmod.withParameterNames(); + assertHasParameterNames(imageDir, hasParameter); + } + + @Test + public void testWithoutStripParameterName() throws Exception { + var imageDir = Paths.get("img"); + var jmod = testJmods.get(0); + buildImage(jmod.moduleDir(), imageDir, + "--strip-debug", "--strip-java-debug-attributes") + .assertSuccess(); + assertHasParameterNames(imageDir, jmod.withParameterNames()); + } +} \ No newline at end of file