From 456ac14c027b1b61308af8fa1cbbe0af383c3b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Bj=C3=B8rsn=C3=B8s?= Date: Sat, 9 Nov 2024 05:53:16 +0000 Subject: [PATCH 01/23] 8343819: Link Float.NaN and Double.NaN to equivalence discussion in Double Reviewed-by: darcy --- src/java.base/share/classes/java/lang/Double.java | 6 +++--- src/java.base/share/classes/java/lang/Float.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Double.java b/src/java.base/share/classes/java/lang/Double.java index ed23f7d39c9..8f7b6c6aa42 100644 --- a/src/java.base/share/classes/java/lang/Double.java +++ b/src/java.base/share/classes/java/lang/Double.java @@ -375,9 +375,9 @@ public final class Double extends Number public static final double NEGATIVE_INFINITY = -1.0 / 0.0; /** - * A constant holding a Not-a-Number (NaN) value of type - * {@code double}. It is equivalent to the value returned by - * {@code Double.longBitsToDouble(0x7ff8000000000000L)}. + * A constant holding a Not-a-Number (NaN) value of type {@code double}. + * It is {@linkplain Double##equivalenceRelation equivalent} to the + * value returned by {@code Double.longBitsToDouble(0x7ff8000000000000L)}. */ public static final double NaN = 0.0d / 0.0; diff --git a/src/java.base/share/classes/java/lang/Float.java b/src/java.base/share/classes/java/lang/Float.java index 821a05fa00a..85b20e6a2e1 100644 --- a/src/java.base/share/classes/java/lang/Float.java +++ b/src/java.base/share/classes/java/lang/Float.java @@ -93,9 +93,9 @@ public final class Float extends Number public static final float NEGATIVE_INFINITY = -1.0f / 0.0f; /** - * A constant holding a Not-a-Number (NaN) value of type - * {@code float}. It is equivalent to the value returned by - * {@code Float.intBitsToFloat(0x7fc00000)}. + * A constant holding a Not-a-Number (NaN) value of type {@code float}. + * It is {@linkplain Double##equivalenceRelation equivalent} + * to the value returned by{@code Float.intBitsToFloat(0x7fc00000)}. */ public static final float NaN = 0.0f / 0.0f; From f785013db9c96fa7854ad58409902306b1fefbd9 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Sat, 9 Nov 2024 09:39:23 +0000 Subject: [PATCH 02/23] 8343793: Test java/foreign/TestMemorySession.java is timing out Reviewed-by: mcimadamore --- test/jdk/java/foreign/TestMemorySession.java | 32 +++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/test/jdk/java/foreign/TestMemorySession.java b/test/jdk/java/foreign/TestMemorySession.java index e5b4ca74027..b06e2707c39 100644 --- a/test/jdk/java/foreign/TestMemorySession.java +++ b/test/jdk/java/foreign/TestMemorySession.java @@ -326,52 +326,56 @@ public void testAcquireCloseRace() throws InterruptedException { int iteration = 1000; AtomicInteger lock = new AtomicInteger(); boolean[] result = new boolean[1]; - lock.set(-2); MemorySessionImpl[] scopes = new MemorySessionImpl[iteration]; for (int i = 0; i < iteration; i++) { scopes[i] = MemorySessionImpl.toMemorySession(Arena.ofShared()); } + // These two threads proceed the scopes array in a lock-step manner, the first thread wait + // for the second thread on the lock variable, while the second thread wait for the first + // thread on the closing of the current scope + // This thread tries to close the scopes Thread t1 = new Thread(() -> { - for (int i = 0; i < iteration; i++) { + for (int i = 0; i < iteration;) { MemorySessionImpl scope = scopes[i]; while (true) { try { scope.close(); + // Continue to the next iteration after a successful close break; - } catch (IllegalStateException e) {} + } catch (IllegalStateException e) { + // Wait for the release and try again + } } - // Keep the 2 threads operating on the same scope - int k = lock.getAndAdd(1) + 1; - while (k != i * 2) { + // Wait for the other thread to complete its iteration + int prev = i; + while (prev == i) { + i = lock.get(); Thread.onSpinWait(); - k = lock.get(); } } }); // This thread tries to acquire the scopes, then check if it is alive after an acquire failure Thread t2 = new Thread(() -> { - for (int i = 0; i < iteration; i++) { + for (int i = 0; i < iteration;) { MemorySessionImpl scope = scopes[i]; while (true) { try { scope.acquire0(); } catch (IllegalStateException e) { + // The scope has been closed, proceed to the next iteration if (scope.isAlive()) { result[0] = true; } break; } + // Release and try again scope.release0(); } - // Keep the 2 threads operating on the same scope - int k = lock.getAndAdd(1) + 1; - while (k != i * 2) { - Thread.onSpinWait(); - k = lock.get(); - } + // Proceed to the next iteration + i = lock.getAndAdd(1) + 1; } }); From 3187c6a334b7669d680afe183d9231794897f9e9 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Sat, 9 Nov 2024 18:57:21 +0000 Subject: [PATCH 03/23] 8343848: Fix typo of property name in TestOAEPPadding after 8341927 Reviewed-by: lucy, mullan --- .../jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java b/test/jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java index 2a82f2214d4..3bacff7617d 100644 --- a/test/jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java +++ b/test/jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java @@ -57,7 +57,7 @@ public static void main(String args[]) throws Exception { System.getProperty("test.provider.name", "SunJCE")); System.out.println("Testing provider " + cp.getName() + "..."); Provider kfp = Security.getProvider( - System.getProperty("test.providername", "SunRsaSign")); + System.getProperty("test.provider.name", "SunRsaSign")); String kpgAlgorithm = "RSA"; KeyPairGenerator kpg = KeyPairGenerator.getInstance(kpgAlgorithm, kfp); kpg.initialize(SecurityUtils.getTestKeySize(kpgAlgorithm)); From e872a442f32e3789b01eb1593b3a993d371e02b1 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Sat, 9 Nov 2024 23:11:33 +0000 Subject: [PATCH 04/23] 8342442: Static ACVP sample tests Reviewed-by: mullan, bperez --- .../sun/security/provider/acvp/Launcher.java | 162 ++++++++++++++++++ .../security/provider/acvp/ML_DSA_Test.java | 126 ++++++++++++++ .../security/provider/acvp/ML_KEM_Test.java | 112 ++++++++++++ .../sun/security/provider/acvp/SHA_Test.java | 139 +++++++++++++++ .../sun/security/provider/acvp/data/acvp.md | 9 + .../lib/security/FixedSecureRandomTest.java | 49 ++++++ .../test/lib/security/FixedSecureRandom.java | 72 ++++++++ 7 files changed, 669 insertions(+) create mode 100644 test/jdk/sun/security/provider/acvp/Launcher.java create mode 100644 test/jdk/sun/security/provider/acvp/ML_DSA_Test.java create mode 100644 test/jdk/sun/security/provider/acvp/ML_KEM_Test.java create mode 100644 test/jdk/sun/security/provider/acvp/SHA_Test.java create mode 100644 test/jdk/sun/security/provider/acvp/data/acvp.md create mode 100644 test/lib-test/jdk/test/lib/security/FixedSecureRandomTest.java create mode 100644 test/lib/jdk/test/lib/security/FixedSecureRandom.java diff --git a/test/jdk/sun/security/provider/acvp/Launcher.java b/test/jdk/sun/security/provider/acvp/Launcher.java new file mode 100644 index 00000000000..5a2ef1233f2 --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/Launcher.java @@ -0,0 +1,162 @@ +/* + * 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. + */ +import jdk.test.lib.json.JSONValue; +import jtreg.SkippedException; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Provider; +import java.security.Security; + +/* + * @test + * @bug 8342442 + * @library /test/lib + */ + +/// This test runs on `internalProjection.json`-style files generated +/// by NIST's ACVP Server. See [https://github.com/usnistgov/ACVP-Server]. +/// +/// The files are either put into the `data` directory or another +/// directory specified by the `test.acvp.data` test property. +/// The test walks through the directory recursively and looks for +/// file names equal to or ending with `internalProjection.json` and +/// runs tests on them. +/// +/// Set the `test.acvp.alg` test property to only test the specified algorithm. +/// +/// Sample files can be downloaded from +/// [https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files]. +/// +/// By default, the test uses system-preferred implementations. +/// If you want to test a specific provider, set the +/// `test.acvp.provider` test property. The provider must be +/// registered. +/// +/// Tests for each algorithm must be compliant to its specification linked from +/// [https://github.com/usnistgov/ACVP?tab=readme-ov-file#supported-algorithms]. +/// +/// Example: +/// ``` +/// jtreg -Dtest.acvp.provider=SunJCE \ +/// -Dtest.acvp.alg=ML-KEM \ +/// -Dtest.acvp.data=/path/to/json-files/ \ +/// -jdk:/path/to/jdk Launcher.java +/// ``` +public class Launcher { + + private static final String ONLY_ALG + = System.getProperty("test.acvp.alg"); + + private static final Provider PROVIDER; + + private static int count = 0; + private static int invalidTest = 0; + private static int unsupportedTest = 0; + + static { + var provProp = System.getProperty("test.acvp.provider"); + if (provProp != null) { + var p = Security.getProvider(provProp); + if (p == null) { + System.err.println(provProp + " is not a registered provider name"); + throw new RuntimeException(provProp + " is not a registered provider name"); + } + PROVIDER = p; + } else { + PROVIDER = null; + } + } + + public static void main(String[] args) throws Exception { + + var testDataProp = System.getProperty("test.acvp.data"); + Path dataPath = testDataProp != null + ? Path.of(testDataProp) + : Path.of(System.getProperty("test.src"), "data"); + System.out.println("Data path: " + dataPath); + + if (PROVIDER != null) { + System.out.println("Provider: " + PROVIDER.getName()); + } + if (ONLY_ALG != null) { + System.out.println("Algorithm: " + ONLY_ALG); + } + + try (var stream = Files.walk(dataPath)) { + stream.filter(Files::isRegularFile) + .filter(p -> p.getFileName().toString() + .endsWith("internalProjection.json")) + .forEach(Launcher::run); + } + + if (count > 0) { + System.out.println(); + System.out.println("Test completed: " + count); + System.out.println("Invalid tests: " + invalidTest); + System.out.println("Unsupported tests: " + unsupportedTest); + } else { + throw new SkippedException("No supported test found"); + } + } + + static void run(Path test) { + try { + JSONValue kat; + try { + kat = JSONValue.parse(Files.readString(test)); + } catch (Exception e) { + System.out.println("Warning: cannot parse " + test + ". Skipped"); + invalidTest++; + return; + } + var alg = kat.get("algorithm").asString(); + if (ONLY_ALG != null && !alg.equals(ONLY_ALG)) { + return; + } + System.out.println(">>> Testing " + test + "..."); + switch (alg) { + case "ML-DSA" -> { + ML_DSA_Test.run(kat, PROVIDER); + count++; + } + case "ML-KEM" -> { + ML_KEM_Test.run(kat, PROVIDER); + count++; + } + case "SHA2-256", "SHA2-224", "SHA3-256", "SHA3-224" -> { + SHA_Test.run(kat, PROVIDER); + count++; + } + default -> { + System.out.println("Skipped unsupported algorithm: " + alg); + unsupportedTest++; + } + } + } catch (RuntimeException re) { + throw re; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java new file mode 100644 index 00000000000..6d402516410 --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java @@ -0,0 +1,126 @@ +/* + * 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. + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.json.JSONValue; +import jdk.test.lib.security.FixedSecureRandom; + +import java.security.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.NamedParameterSpec; + +import static jdk.test.lib.Utils.toByteArray; + +// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html +public class ML_DSA_Test { + + public static void run(JSONValue kat, Provider provider) throws Exception { + var mode = kat.get("mode").asString(); + switch (mode) { + case "keyGen" -> keyGenTest(kat, provider); + case "sigGen" -> sigGenTest(kat, provider); + case "sigVer" -> sigVerTest(kat, provider); + default -> throw new UnsupportedOperationException("Unknown mode: " + mode); + } + } + + static void keyGenTest(JSONValue kat, Provider p) throws Exception { + var g = p == null + ? KeyPairGenerator.getInstance("ML-DSA") + : KeyPairGenerator.getInstance("ML-DSA", p); + var f = p == null + ? KeyFactory.getInstance("ML-DSA") + : KeyFactory.getInstance("ML-DSA", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var np = new NamedParameterSpec(pname); + System.out.println(">> " + pname); + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + g.initialize(np, new FixedSecureRandom(toByteArray(c.get("seed").asString()))); + var kp = g.generateKeyPair(); + var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded(); + var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded(); + Asserts.assertEqualsByteArray(pk, toByteArray(c.get("pk").asString())); + Asserts.assertEqualsByteArray(sk, toByteArray(c.get("sk").asString())); + } + System.out.println(); + } + } + + static void sigGenTest(JSONValue kat, Provider p) throws Exception { + var s = p == null + ? Signature.getInstance("ML-DSA") + : Signature.getInstance("ML-DSA", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var det = Boolean.parseBoolean(t.get("deterministic").asString()); + System.out.println(">> " + pname + " sign"); + for (var c : t.get("tests").asArray()) { + System.out.print(Integer.parseInt(c.get("tcId").asString()) + " "); + var sk = new PrivateKey() { + public String getAlgorithm() { return pname; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return toByteArray(c.get("sk").asString()); } + }; + var sr = new FixedSecureRandom( + det ? new byte[32] : toByteArray(c.get("rnd").asString())); + s.initSign(sk, sr); + s.update(toByteArray(c.get("message").asString())); + var sig = s.sign(); + Asserts.assertEqualsByteArray( + sig, toByteArray(c.get("signature").asString())); + } + System.out.println(); + } + } + + static void sigVerTest(JSONValue kat, Provider p) throws Exception { + var s = p == null + ? Signature.getInstance("ML-DSA") + : Signature.getInstance("ML-DSA", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var pk = new PublicKey() { + public String getAlgorithm() { return pname; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return toByteArray(t.get("pk").asString()); } + }; + System.out.println(">> " + pname + " verify"); + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + // Only ML-DSA sigVer has negative tests + var expected = Boolean.parseBoolean(c.get("testPassed").asString()); + var actual = true; + try { + s.initVerify(pk); + s.update(toByteArray(c.get("message").asString())); + actual = s.verify(toByteArray(c.get("signature").asString())); + } catch (InvalidKeyException | SignatureException e) { + actual = false; + } + Asserts.assertEQ(expected, actual); + } + System.out.println(); + } + } +} diff --git a/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java b/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java new file mode 100644 index 00000000000..5b707bee6d8 --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java @@ -0,0 +1,112 @@ +/* + * 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. + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.json.JSONValue; +import jdk.test.lib.security.FixedSecureRandom; + +import javax.crypto.KEM; +import java.security.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.NamedParameterSpec; + +import static jdk.test.lib.Utils.toByteArray; + +// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-ml-kem.html +public class ML_KEM_Test { + + public static void run(JSONValue kat, Provider provider) throws Exception { + var mode = kat.get("mode").asString(); + switch (mode) { + case "keyGen" -> keyGenTest(kat, provider); + case "encapDecap" -> encapDecapTest(kat, provider); + default -> throw new UnsupportedOperationException("Unknown mode: " + mode); + } + } + + static void keyGenTest(JSONValue kat, Provider p) throws Exception { + var g = p == null + ? KeyPairGenerator.getInstance("ML-KEM") + : KeyPairGenerator.getInstance("ML-KEM", p); + var f = p == null + ? KeyFactory.getInstance("ML-KEM") + : KeyFactory.getInstance("ML-KEM", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var np = new NamedParameterSpec(pname); + System.out.println(">> " + pname); + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + g.initialize(np, new FixedSecureRandom( + toByteArray(c.get("d").asString()), toByteArray(c.get("z").asString()))); + var kp = g.generateKeyPair(); + var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded(); + var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded(); + Asserts.assertEqualsByteArray(pk, toByteArray(c.get("ek").asString())); + Asserts.assertEqualsByteArray(sk, toByteArray(c.get("dk").asString())); + } + System.out.println(); + } + } + + static void encapDecapTest(JSONValue kat, Provider p) throws Exception { + var g = p == null + ? KEM.getInstance("ML-KEM") + : KEM.getInstance("ML-KEM", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var function = t.get("function").asString(); + System.out.println(">> " + pname + " " + function); + if (function.equals("encapsulation")) { + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var ek = new PublicKey() { + public String getAlgorithm() { return pname; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return toByteArray(c.get("ek").asString()); } + }; + var e = g.newEncapsulator( + ek, new FixedSecureRandom(toByteArray(c.get("m").asString()))); + var enc = e.encapsulate(); + Asserts.assertEqualsByteArray( + enc.encapsulation(), toByteArray(c.get("c").asString())); + Asserts.assertEqualsByteArray( + enc.key().getEncoded(), toByteArray(c.get("k").asString())); + } + System.out.println(); + } else if (function.equals("decapsulation")) { + var dk = new PrivateKey() { + public String getAlgorithm() { return pname; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return toByteArray(t.get("dk").asString()); } + }; + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var d = g.newDecapsulator(dk); + var k = d.decapsulate(toByteArray(c.get("c").asString())); + Asserts.assertEqualsByteArray(k.getEncoded(), toByteArray(c.get("k").asString())); + } + System.out.println(); + } + } + } +} diff --git a/test/jdk/sun/security/provider/acvp/SHA_Test.java b/test/jdk/sun/security/provider/acvp/SHA_Test.java new file mode 100644 index 00000000000..3e5d4bb2521 --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/SHA_Test.java @@ -0,0 +1,139 @@ +/* + * 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. + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.json.JSONValue; + +import java.security.*; +import java.util.Arrays; + +import static jdk.test.lib.Utils.toByteArray; + +// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html +// and https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html +public class SHA_Test { + + public static void run(JSONValue kat, Provider provider) throws Exception { + var alg = kat.get("algorithm").asString(); + if (alg.startsWith("SHA2-")) alg = "SHA-" + alg.substring(5); + var md = provider == null ? MessageDigest.getInstance(alg) + : MessageDigest.getInstance(alg, provider); + for (var t : kat.get("testGroups").asArray()) { + var testType = t.get("testType").asString(); + switch (testType) { + case "AFT" -> { + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var msg = toByteArray(c.get("msg").asString()); + var len = Integer.parseInt(c.get("len").asString()); + if (msg.length * 8 == len) { + Asserts.assertEqualsByteArray(md.digest(msg), + toByteArray(c.get("md").asString())); + } else { + System.out.print("bits "); + } + } + } + case "MCT" -> { + var mctVersion = t.get("mctVersion").asString(); + var trunc = mctVersion.equals("alternate"); + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var SEED = toByteArray(c.get("msg").asString()); + var INITIAL_SEED_LENGTH = Integer.parseInt(c.get("len").asString()); + if (SEED.length * 8 == INITIAL_SEED_LENGTH) { + for (var r : c.get("resultsArray").asArray()) { + if (alg.startsWith("SHA3-")) { + var MD = SEED; + for (var i = 0; i < 1000; i++) { + if (trunc) { + MD = Arrays.copyOf(MD, INITIAL_SEED_LENGTH / 8); + } + MD = md.digest(MD); + } + Asserts.assertEqualsByteArray(MD, + toByteArray(r.get("md").asString())); + SEED = MD; + } else { + var A = SEED; + var B = SEED; + var C = SEED; + byte[] MD = null; + for (var i = 0; i < 1000; i++) { + var MSG = concat(A, B, C); + if (trunc) { + MSG = Arrays.copyOf(MSG, INITIAL_SEED_LENGTH / 8); + } + MD = md.digest(MSG); + A = B; + B = C; + C = MD; + } + Asserts.assertEqualsByteArray(MD, + toByteArray(r.get("md").asString())); + SEED = MD; + } + } + } else { + System.out.print("bits "); + } + } + } + case "LDT" -> { + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var lm = c.get("largeMsg"); + var ct = toByteArray(lm.get("content").asString()); + var flen = Long.parseLong(lm.get("fullLength").asString()); + var clen = Long.parseLong(lm.get("contentLength").asString()); + var cc = 0L; + while (cc < flen) { + md.update(ct); + cc += clen; + } + Asserts.assertEqualsByteArray(md.digest(), + toByteArray(c.get("md").asString())); + } + } + default -> throw new UnsupportedOperationException( + "Unknown testType: " + testType); + } + System.out.println(); + } + } + + ///////////// + + static byte[] concat(byte[]... input) { + var sum = 0; + for (var i : input) { + sum += i.length; + } + var out = new byte[sum]; + sum = 0; + for (var i : input) { + System.arraycopy(i, 0, out, sum, i.length); + sum += i.length; + } + return out; + } +} diff --git a/test/jdk/sun/security/provider/acvp/data/acvp.md b/test/jdk/sun/security/provider/acvp/data/acvp.md new file mode 100644 index 00000000000..68e073b038f --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/data/acvp.md @@ -0,0 +1,9 @@ +# Automated Cryptographic Validation Test System Sample JSON files v1.1.0.36 + +## License + +NIST-developed software is provided by NIST as a public service. You may use, copy, and distribute copies of the software in any medium, provided that you keep intact this entire notice. You may improve, modify, and create derivative works of the software or any portion of the software, and you may copy and distribute such modifications or works. Modified works should carry a notice stating that you changed the software and should note the date and nature of any such change. Please explicitly acknowledge the National Institute of Standards and Technology as the source of the software. + +NIST-developed software is expressly provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT, OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE. + +You are solely responsible for determining the appropriateness of using and distributing the software and you assume all risks associated with its use, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of operation. This software is not intended to be used in any situation where a failure could cause risk of injury or damage to property. The software developed by NIST employees is not subject to copyright protection within the United States. diff --git a/test/lib-test/jdk/test/lib/security/FixedSecureRandomTest.java b/test/lib-test/jdk/test/lib/security/FixedSecureRandomTest.java new file mode 100644 index 00000000000..bdebc9e0a8c --- /dev/null +++ b/test/lib-test/jdk/test/lib/security/FixedSecureRandomTest.java @@ -0,0 +1,49 @@ +/* + * 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. + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; +import jdk.test.lib.security.FixedSecureRandom; + +/* + * @test + * @library /test/lib + * @summary ensure FixedSecureRandom works as expected + */ +public class FixedSecureRandomTest { + public static void main(String[] args) throws Exception { + var fsr = new FixedSecureRandom(new byte[] {1, 2, 3}, + new byte[] {4, 5, 6}); + var b1 = new byte[2]; + fsr.nextBytes(b1); + Asserts.assertEqualsByteArray(b1, new byte[] {1, 2}); + Asserts.assertTrue(fsr.hasRemaining()); + fsr.nextBytes(b1); + Asserts.assertEqualsByteArray(b1, new byte[] {3, 4}); + Asserts.assertTrue(fsr.hasRemaining()); + fsr.nextBytes(b1); + Asserts.assertEqualsByteArray(b1, new byte[] {5, 6}); + Asserts.assertFalse(fsr.hasRemaining()); + Utils.runAndCheckException(() -> fsr.nextBytes(b1), + IllegalStateException.class); + } +} diff --git a/test/lib/jdk/test/lib/security/FixedSecureRandom.java b/test/lib/jdk/test/lib/security/FixedSecureRandom.java new file mode 100644 index 00000000000..cfe8ccc212f --- /dev/null +++ b/test/lib/jdk/test/lib/security/FixedSecureRandom.java @@ -0,0 +1,72 @@ +/* + * 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. + */ +package jdk.test.lib.security; + +import java.io.ByteArrayOutputStream; +import java.security.SecureRandom; + +/// A custom implementation of `SecureRandom` that outputs a +/// predefined sequence of bytes. +/// +/// The `FixedSecureRandom` class is designed for testing and +/// controlled environments where predictable output is required. +/// Upon creation, the class is initialized with a fixed byte array. +/// Each call to `nextBytes()` will return these bytes in sequence, +/// ensuring that the output matches the provided input exactly. +/// An `IllegalStateException` will be thrown when the predefined +/// bytes are exhausted. +public class FixedSecureRandom extends SecureRandom { + + private byte[] buffer; + private int offset; + + // Multiple segments of ordered predefined bytes can be + // provided for convenience. For example, ML-KEM.KeyGen + // requires 2 blocks of 32-byte random data. + public FixedSecureRandom(byte[]... data) { + var os = new ByteArrayOutputStream(); + for (byte[] b : data) { + os.writeBytes(b); + } + buffer = os.toByteArray(); + offset = 0; + } + + @Override + public void nextBytes(byte[] bytes) { + if (bytes.length > buffer.length - offset) { + throw new IllegalStateException("Not enough bytes"); + } + System.arraycopy(buffer, offset, bytes, 0, bytes.length); + offset += bytes.length; + } + + /// {@return whether there are remaining used bytes} + /// + /// This method is useful to detect whether an algorithm + /// implementation has indeed consumed the required number + /// of bytes correctly. + public boolean hasRemaining() { + return offset != buffer.length; + } +} From 50190b760cf8d2baaebb99a6d5eece844425f8d7 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 11 Nov 2024 00:40:26 +0000 Subject: [PATCH 05/23] 8343650: Reuse StringLatin1::putCharsAt and StringUTF16::putCharsAt Reviewed-by: liach --- .../classes/java/lang/StringConcatHelper.java | 44 ++++--------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index d0c558ed93a..b635d0dee0f 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -236,17 +236,10 @@ static long prepend(long indexCoder, byte[] buf, boolean value, String prefix) { if (indexCoder < UTF16) { if (value) { index -= 4; - buf[index] = 't'; - buf[index + 1] = 'r'; - buf[index + 2] = 'u'; - buf[index + 3] = 'e'; + StringLatin1.putCharsAt(buf, index, 't', 'r', 'u', 'e'); } else { index -= 5; - buf[index] = 'f'; - buf[index + 1] = 'a'; - buf[index + 2] = 'l'; - buf[index + 3] = 's'; - buf[index + 4] = 'e'; + StringLatin1.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e'); } index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); @@ -254,17 +247,10 @@ static long prepend(long indexCoder, byte[] buf, boolean value, String prefix) { } else { if (value) { index -= 4; - StringUTF16.putChar(buf, index, 't'); - StringUTF16.putChar(buf, index + 1, 'r'); - StringUTF16.putChar(buf, index + 2, 'u'); - StringUTF16.putChar(buf, index + 3, 'e'); + StringUTF16.putCharsAt(buf, index, 't', 'r', 'u', 'e'); } else { index -= 5; - StringUTF16.putChar(buf, index, 'f'); - StringUTF16.putChar(buf, index + 1, 'a'); - StringUTF16.putChar(buf, index + 2, 'l'); - StringUTF16.putChar(buf, index + 3, 's'); - StringUTF16.putChar(buf, index + 4, 'e'); + StringUTF16.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e'); } index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); @@ -638,34 +624,20 @@ static int prepend(int index, byte coder, byte[] buf, boolean value, String pref if (coder == String.LATIN1) { if (value) { index -= 4; - buf[index] = 't'; - buf[index + 1] = 'r'; - buf[index + 2] = 'u'; - buf[index + 3] = 'e'; + StringLatin1.putCharsAt(buf, index, 't', 'r', 'u', 'e'); } else { index -= 5; - buf[index] = 'f'; - buf[index + 1] = 'a'; - buf[index + 2] = 'l'; - buf[index + 3] = 's'; - buf[index + 4] = 'e'; + StringLatin1.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e'); } index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { if (value) { index -= 4; - StringUTF16.putChar(buf, index, 't'); - StringUTF16.putChar(buf, index + 1, 'r'); - StringUTF16.putChar(buf, index + 2, 'u'); - StringUTF16.putChar(buf, index + 3, 'e'); + StringUTF16.putCharsAt(buf, index, 't', 'r', 'u', 'e'); } else { index -= 5; - StringUTF16.putChar(buf, index, 'f'); - StringUTF16.putChar(buf, index + 1, 'a'); - StringUTF16.putChar(buf, index + 2, 'l'); - StringUTF16.putChar(buf, index + 3, 's'); - StringUTF16.putChar(buf, index + 4, 'e'); + StringUTF16.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e'); } index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); From b3ed34bc9667191c49609af55c67b40240ae4f7d Mon Sep 17 00:00:00 2001 From: David Holmes Date: Mon, 11 Nov 2024 01:40:10 +0000 Subject: [PATCH 06/23] 8343894: ProblemList javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java Reviewed-by: jpai --- test/jdk/ProblemList.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 1ae9623ef14..4b483a4b076 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -563,6 +563,8 @@ javax/management/monitor/DerivedGaugeMonitorTest.java 8042211 generic- javax/management/remote/mandatory/connection/BrokenConnectionTest.java 8262312 linux-all +javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java 8343838 generic-all + ############################################################################ # jdk_net From a96d8b2037a93ca7acf69f0dd20fa3ee3022d8eb Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Mon, 11 Nov 2024 04:31:56 +0000 Subject: [PATCH 07/23] 8211033: Clean up the processing -classpath argument not to set LM_CLASS Reviewed-by: alanb --- src/java.base/share/native/libjli/java.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 0bb1daed28a..4f9a6426dff 100644 --- a/src/java.base/share/native/libjli/java.c +++ b/src/java.base/share/native/libjli/java.c @@ -1198,9 +1198,6 @@ ParseArguments(int *pargc, char ***pargv, JLI_StrCmp(arg, "-cp") == 0) { REPORT_ERROR (has_arg_any_len, ARG_ERROR1, arg); SetClassPath(value); - if (mode != LM_SOURCE) { - mode = LM_CLASS; - } } else if (JLI_StrCmp(arg, "--list-modules") == 0) { listModules = JNI_TRUE; } else if (JLI_StrCmp(arg, "--show-resolved-modules") == 0) { @@ -1355,11 +1352,12 @@ ParseArguments(int *pargc, char ***pargv, *pret = 1; } } else if (mode == LM_UNKNOWN) { - /* default to LM_CLASS if -m, -jar and -cp options are - * not specified */ if (!_have_classpath) { SetClassPath("."); } + /* If neither of -m, -jar, --source option is set, then the + * launcher mode is LM_UNKNOWN. In such cases, we determine the + * mode as LM_CLASS or LM_SOURCE per the input file. */ mode = IsSourceFile(arg) ? LM_SOURCE : LM_CLASS; } else if (mode == LM_CLASS && IsSourceFile(arg)) { /* override LM_CLASS mode if given a source file */ From 477ad904b15c71af17adf2113521c26978f40ea0 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 11 Nov 2024 05:06:56 +0000 Subject: [PATCH 08/23] 8342650: Move getChars to DecimalDigits Reviewed-by: liach --- .../java/lang/AbstractStringBuilder.java | 8 +- .../share/classes/java/lang/Integer.java | 4 +- .../share/classes/java/lang/Long.java | 4 +- .../classes/java/lang/StringConcatHelper.java | 16 +- .../share/classes/java/lang/StringLatin1.java | 115 ------- .../share/classes/java/lang/StringUTF16.java | 118 ------- .../share/classes/java/lang/System.java | 8 - .../share/classes/java/math/BigDecimal.java | 127 ++----- .../jdk/internal/access/JavaLangAccess.java | 4 - .../jdk/internal/util/DecimalDigits.java | 315 +++++++++++++++++- .../patches/java.base/java/lang/Helper.java | 12 +- .../bench/java/lang/StringBuilders.java | 50 +++ 12 files changed, 405 insertions(+), 376 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index b40f6274412..fd9dcf60e54 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -830,9 +830,9 @@ public AbstractStringBuilder append(int i) { int spaceNeeded = count + DecimalDigits.stringSize(i); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - StringLatin1.getChars(i, spaceNeeded, value); + DecimalDigits.getCharsLatin1(i, spaceNeeded, value); } else { - StringUTF16.getChars(i, count, spaceNeeded, value); + DecimalDigits.getCharsUTF16(i, spaceNeeded, value); } this.count = spaceNeeded; return this; @@ -855,9 +855,9 @@ public AbstractStringBuilder append(long l) { int spaceNeeded = count + DecimalDigits.stringSize(l); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - StringLatin1.getChars(l, spaceNeeded, value); + DecimalDigits.getCharsLatin1(l, spaceNeeded, value); } else { - StringUTF16.getChars(l, count, spaceNeeded, value); + DecimalDigits.getCharsUTF16(l, spaceNeeded, value); } this.count = spaceNeeded; return this; diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index e666e977c61..5f73d61e5d4 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -432,11 +432,11 @@ public static String toString(int i) { int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - StringLatin1.getChars(i, size, buf); + DecimalDigits.getCharsLatin1(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; - StringUTF16.getChars(i, size, buf); + DecimalDigits.getCharsUTF16(i, size, buf); return new String(buf, UTF16); } } diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 8c083b3ec84..7df9ddfb270 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -462,11 +462,11 @@ public static String toString(long i) { int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - StringLatin1.getChars(i, size, buf); + DecimalDigits.getCharsLatin1(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; - StringUTF16.getChars(i, size, buf); + DecimalDigits.getCharsUTF16(i, size, buf); return new String(buf, UTF16); } } diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index b635d0dee0f..632fe0f58b5 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -298,12 +298,12 @@ static long prepend(long indexCoder, byte[] buf, char value, String prefix) { static long prepend(long indexCoder, byte[] buf, int value, String prefix) { int index = (int)indexCoder; if (indexCoder < UTF16) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); return index; } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); return index | UTF16; @@ -324,12 +324,12 @@ static long prepend(long indexCoder, byte[] buf, int value, String prefix) { static long prepend(long indexCoder, byte[] buf, long value, String prefix) { int index = (int)indexCoder; if (indexCoder < UTF16) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); return index; } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); return index | UTF16; @@ -682,11 +682,11 @@ static int prepend(int index, byte coder, byte[] buf, char value, String prefix) */ static int prepend(int index, byte coder, byte[] buf, int value, String prefix) { if (coder == String.LATIN1) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); } @@ -706,11 +706,11 @@ static int prepend(int index, byte coder, byte[] buf, int value, String prefix) */ static int prepend(int index, byte coder, byte[] buf, long value, String prefix) { if (coder == String.LATIN1) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); } diff --git a/src/java.base/share/classes/java/lang/StringLatin1.java b/src/java.base/share/classes/java/lang/StringLatin1.java index c12b8afc21f..abe42c2c7c7 100644 --- a/src/java.base/share/classes/java/lang/StringLatin1.java +++ b/src/java.base/share/classes/java/lang/StringLatin1.java @@ -34,7 +34,6 @@ import java.util.stream.StreamSupport; import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; -import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.IntrinsicCandidate; import static java.lang.String.LATIN1; @@ -86,120 +85,6 @@ public static byte[] inflate(byte[] value, int off, int len) { return ret; } - /** - * Places characters representing the integer i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @implNote This method converts positive inputs into negative - * values, to cover the Integer.MIN_VALUE case. Converting otherwise - * (negative to positive) will expose -Integer.MIN_VALUE that overflows - * integer. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(int i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - int q; - int charPos = index; - - boolean negative = i < 0; - if (!negative) { - i = -i; - } - - // Generate two digits per iteration - while (i <= -100) { - q = i / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (q * 100) - i); - i = q; - } - - // We know there are at most two digits left at this point. - if (i < -9) { - charPos -= 2; - writeDigitPair(buf, charPos, -i); - } else { - buf[--charPos] = (byte)('0' - i); - } - - if (negative) { - buf[--charPos] = (byte)'-'; - } - return charPos; - } - - /** - * Places characters representing the long i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @implNote This method converts positive inputs into negative - * values, to cover the Long.MIN_VALUE case. Converting otherwise - * (negative to positive) will expose -Long.MIN_VALUE that overflows - * long. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(long i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - writeDigitPair(buf, charPos, -i2); - } else { - buf[--charPos] = (byte)('0' - i2); - } - - if (negative) { - buf[--charPos] = (byte)'-'; - } - return charPos; - } - - private static void writeDigitPair(byte[] buf, int charPos, int value) { - short pair = DecimalDigits.digitPair(value); - buf[charPos] = (byte)(pair); - buf[charPos + 1] = (byte)(pair >> 8); - } - public static void getChars(byte[] value, int srcBegin, int srcEnd, char[] dst, int dstBegin) { inflate(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); } diff --git a/src/java.base/share/classes/java/lang/StringUTF16.java b/src/java.base/share/classes/java/lang/StringUTF16.java index f04b991827f..a1dcca8ffad 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -35,7 +35,6 @@ import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; -import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -1513,20 +1512,6 @@ public static int codePointCountSB(byte[] val, int beginIndex, int endIndex) { return codePointCount(val, beginIndex, endIndex, true /* checked */); } - public static int getChars(int i, int begin, int end, byte[] value) { - checkBoundsBeginEnd(begin, end, value); - int pos = getChars(i, end, value); - assert begin == pos; - return pos; - } - - public static int getChars(long l, int begin, int end, byte[] value) { - checkBoundsBeginEnd(begin, end, value); - int pos = getChars(l, end, value); - assert begin == pos; - return pos; - } - public static boolean contentEquals(byte[] v1, byte[] v2, int len) { checkBoundsOffCount(0, len, v2); for (int i = 0; i < len; i++) { @@ -1662,109 +1647,6 @@ public static int lastIndexOfLatin1(byte[] src, int srcCount, static final int MAX_LENGTH = Integer.MAX_VALUE >> 1; - // Used by trusted callers. Assumes all necessary bounds checks have - // been done by the caller. - - /** - * This is a variant of {@link StringLatin1#getChars(int, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(int i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - int q, r; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using ints - while (i <= -100) { - q = i / 100; - r = (q * 100) - i; - i = q; - charPos -= 2; - putPair(buf, charPos, r); - } - - // We know there are at most two digits left at this point. - if (i < -9) { - charPos -= 2; - putPair(buf, charPos, -i); - } else { - putChar(buf, --charPos, '0' - i); - } - - if (negative) { - putChar(buf, --charPos, '-'); - } - return charPos; - } - - /** - * This is a variant of {@link StringLatin1#getChars(long, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(long i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - putPair(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - putPair(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - putPair(buf, charPos, -i2); - } else { - putChar(buf, --charPos, '0' - i2); - } - - if (negative) { - putChar(buf, --charPos, '-'); - } - return charPos; - } - - private static void putPair(byte[] buf, int charPos, int v) { - int packed = (int) DecimalDigits.digitPair(v); - putChar(buf, charPos, packed & 0xFF); - putChar(buf, charPos + 1, packed >> 8); - } - // End of trusted methods. - public static void checkIndex(int off, byte[] val) { String.checkIndex(off, length(val)); } diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 5b04bca4f44..451ed8e6bfc 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2648,14 +2648,6 @@ public byte stringCoder(String str) { return str.coder(); } - public int getCharsLatin1(long i, int index, byte[] buf) { - return StringLatin1.getChars(i, index, buf); - } - - public int getCharsUTF16(long i, int index, byte[] buf) { - return StringUTF16.getChars(i, index, buf); - } - public String join(String prefix, String suffix, String delimiter, String[] elements, int size) { return String.join(prefix, suffix, delimiter, elements, size); } diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index abd49aa69bc..b00970963b6 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -35,9 +35,15 @@ import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.StreamCorruptedException; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.DecimalDigits; + /** * Immutable, arbitrary-precision signed decimal numbers. A {@code * BigDecimal} consists of an arbitrary precision integer @@ -328,6 +334,8 @@ * @since 1.1 */ public class BigDecimal extends Number implements Comparable { + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + /* * Let l = log_2(10). * Then, L < l < L + ulp(L) / 2, that is, L = roundTiesToEven(l). @@ -4164,103 +4172,6 @@ public BigDecimal ulp() { return BigDecimal.valueOf(1, this.scale(), 1); } - // Private class to build a string representation for BigDecimal object. The - // StringBuilder field acts as a buffer to hold the temporary representation - // of BigDecimal. The cmpCharArray holds all the characters for the compact - // representation of BigDecimal (except for '-' sign' if it is negative) if - // its intCompact field is not INFLATED. - static class StringBuilderHelper { - final StringBuilder sb; // Placeholder for BigDecimal string - final char[] cmpCharArray; // character array to place the intCompact - - StringBuilderHelper() { - sb = new StringBuilder(32); - // All non negative longs can be made to fit into 19 character array. - cmpCharArray = new char[19]; - } - - // Accessors. - StringBuilder getStringBuilder() { - sb.setLength(0); - return sb; - } - - char[] getCompactCharArray() { - return cmpCharArray; - } - - /** - * Places characters representing the intCompact in {@code long} into - * cmpCharArray and returns the offset to the array where the - * representation starts. - * - * @param intCompact the number to put into the cmpCharArray. - * @return offset to the array where the representation starts. - * Note: intCompact must be greater or equal to zero. - */ - int putIntCompact(long intCompact) { - assert intCompact >= 0; - - long q; - int r; - // since we start from the least significant digit, charPos points to - // the last character in cmpCharArray. - int charPos = cmpCharArray.length; - - // Get 2 digits/iteration using longs until quotient fits into an int - while (intCompact > Integer.MAX_VALUE) { - q = intCompact / 100; - r = (int)(intCompact - q * 100); - intCompact = q; - cmpCharArray[--charPos] = DIGIT_ONES[r]; - cmpCharArray[--charPos] = DIGIT_TENS[r]; - } - - // Get 2 digits/iteration using ints when i2 >= 100 - int q2; - int i2 = (int)intCompact; - while (i2 >= 100) { - q2 = i2 / 100; - r = i2 - q2 * 100; - i2 = q2; - cmpCharArray[--charPos] = DIGIT_ONES[r]; - cmpCharArray[--charPos] = DIGIT_TENS[r]; - } - - cmpCharArray[--charPos] = DIGIT_ONES[i2]; - if (i2 >= 10) - cmpCharArray[--charPos] = DIGIT_TENS[i2]; - - return charPos; - } - - static final char[] DIGIT_TENS = { - '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', - '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', - '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', - '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', - '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', - '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', - '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', - '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', - '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', - '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', - }; - - static final char[] DIGIT_ONES = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - }; - } - /** * Lay out this {@code BigDecimal} into a {@code char[]} array. * The Java 1.2 equivalent to this was called {@code getValueString}. @@ -4271,6 +4182,8 @@ int putIntCompact(long intCompact) { * {@code BigDecimal} */ private String layoutChars(boolean sci) { + long intCompact = this.intCompact; + int scale = this.scale; if (scale == 0) // zero scale is trivial return (intCompact != INFLATED) ? Long.toString(intCompact): @@ -4280,18 +4193,24 @@ private String layoutChars(boolean sci) { // currency fast path int lowInt = (int)intCompact % 100; int highInt = (int)intCompact / 100; - return (Integer.toString(highInt) + '.' + - StringBuilderHelper.DIGIT_TENS[lowInt] + - StringBuilderHelper.DIGIT_ONES[lowInt]) ; + int highIntSize = DecimalDigits.stringSize(highInt); + byte[] buf = new byte[highIntSize + 3]; + DecimalDigits.putPairLatin1(buf, highIntSize + 1, lowInt); + buf[highIntSize] = '.'; + DecimalDigits.getCharsLatin1(highInt, highIntSize, buf); + try { + return JLA.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } } - StringBuilderHelper sbHelper = new StringBuilderHelper(); char[] coeff; int offset; // offset is the starting index for coeff array // Get the significand as an absolute value if (intCompact != INFLATED) { - offset = sbHelper.putIntCompact(Math.abs(intCompact)); - coeff = sbHelper.getCompactCharArray(); + coeff = new char[19]; + offset = DecimalDigits.getChars(Math.abs(intCompact), coeff.length, coeff); } else { offset = 0; coeff = intVal.abs().toString().toCharArray(); @@ -4301,7 +4220,7 @@ private String layoutChars(boolean sci) { // If E-notation is needed, length will be: +1 if negative, +1 // if '.' needed, +2 for "E+", + up to 10 for adjusted exponent. // Otherwise it could have +1 if negative, plus leading "0.00000" - StringBuilder buf = sbHelper.getStringBuilder(); + StringBuilder buf = new StringBuilder(32);; if (signum() < 0) // prefix '-' if negative buf.append('-'); int coeffLen = coeff.length - offset; diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 0436cbb314f..ecfdbd28095 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -487,10 +487,6 @@ public interface JavaLangAccess { */ Object classData(Class c); - int getCharsLatin1(long i, int index, byte[] buf); - - int getCharsUTF16(long i, int index, byte[] buf); - /** * Returns the {@link NativeLibraries} object associated with the provided class loader. * This is used by {@link SymbolLookup#loaderLookup()}. diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 83438e59b82..75e67e3f9cc 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -25,14 +25,18 @@ package jdk.internal.util; +import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.Stable; +import static jdk.internal.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; + /** * Digits class for decimal digits. * * @since 21 */ public final class DecimalDigits { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); /** * Each element of the array represents the packaging of two ascii characters based on little endian:

@@ -76,15 +80,6 @@ public final class DecimalDigits { private DecimalDigits() { } - /** - * For values from 0 to 99 return a short encoding a pair of ASCII-encoded digit characters in little-endian - * @param i value to convert - * @return a short encoding a pair of ASCII-encoded digit characters - */ - public static short digitPair(int i) { - return DIGITS[i]; - } - /** * Returns the string representation size for a given int value. * @@ -136,4 +131,306 @@ public static int stringSize(long x) { } return 19 + d; } + + /** + * Places characters representing the integer i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Integer.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Integer.MIN_VALUE that overflows + * integer. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsLatin1(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q; + int charPos = index; + + boolean negative = i < 0; + if (!negative) { + i = -i; + } + + // Generate two digits per iteration + while (i <= -100) { + q = i / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (q * 100) - i); + i = q; + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + putPairLatin1(buf, charPos, -i); + } else { + putCharLatin1(buf, --charPos, '0' - i); + } + + if (negative) { + putCharLatin1(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * Places characters representing the long i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Long.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Long.MIN_VALUE that overflows + * long. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsLatin1(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPairLatin1(buf, charPos, -i2); + } else { + putCharLatin1(buf, --charPos, '0' - i2); + } + + if (negative) { + putCharLatin1(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * This is a variant of {@link DecimalDigits#getCharsLatin1(int, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsUTF16(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q, r; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using ints + while (i <= -100) { + q = i / 100; + r = (q * 100) - i; + i = q; + charPos -= 2; + putPairUTF16(buf, charPos, r); + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + putPairUTF16(buf, charPos, -i); + } else { + putCharUTF16(buf, --charPos, '0' - i); + } + + if (negative) { + putCharUTF16(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * This is a variant of {@link DecimalDigits#getCharsLatin1(long, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsUTF16(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPairUTF16(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPairUTF16(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPairUTF16(buf, charPos, -i2); + } else { + putCharUTF16(buf, --charPos, '0' - i2); + } + + if (negative) { + putCharUTF16(buf, --charPos, '-'); + } + return charPos; + } + + /** + * This is a variant of {@link DecimalDigits#getCharsUTF16(long, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getChars(long i, int index, char[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPair(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPair(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPair(buf, charPos, -i2); + } else { + buf[--charPos] = (char) ('0' - i2); + } + + if (negative) { + buf[--charPos] = '-'; + } + return charPos; + } + + /** + * Insert the 2-chars integer into the buf as 2 decimal digit ASCII chars, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPair(char[] buf, int charPos, int v) { + int packed = DIGITS[v]; + buf[charPos ] = (char) (packed & 0xFF); + buf[charPos + 1] = (char) (packed >> 8); + } + + /** + * Insert the 2-bytes integer into the buf as 2 decimal digit ASCII bytes, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPairLatin1(byte[] buf, int charPos, int v) { + int packed = DIGITS[v]; + putCharLatin1(buf, charPos, packed & 0xFF); + putCharLatin1(buf, charPos + 1, packed >> 8); + } + + /** + * Insert the 2-chars integer into the buf as 2 decimal digit UTF16 bytes, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPairUTF16(byte[] buf, int charPos, int v) { + int packed = DIGITS[v]; + putCharUTF16(buf, charPos, packed & 0xFF); + putCharUTF16(buf, charPos + 1, packed >> 8); + } + + private static void putCharLatin1(byte[] buf, int charPos, int c) { + UNSAFE.putByte(buf, ARRAY_BYTE_BASE_OFFSET + charPos, (byte) c); + } + + private static void putCharUTF16(byte[] buf, int charPos, int c) { + UNSAFE.putChar(buf, ARRAY_BYTE_BASE_OFFSET + (charPos << 1), (char) c); + } } diff --git a/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java b/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java index 5ecc01aa2bc..a60354ec2fc 100644 --- a/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java +++ b/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java @@ -23,6 +23,8 @@ package java.lang; +import jdk.internal.util.DecimalDigits; + /** * A helper class to get access to package-private members */ @@ -117,11 +119,17 @@ public static int codePointCountSB(byte[] val, int beginIndex, int endIndex) { } public static int getChars(int i, int begin, int end, byte[] value) { - return StringUTF16.getChars(i, begin, end, value); + StringUTF16.checkBoundsBeginEnd(begin, end, value); + int pos = DecimalDigits.getCharsUTF16(i, end, value); + assert begin == pos; + return pos; } public static int getChars(long l, int begin, int end, byte[] value) { - return StringUTF16.getChars(l, begin, end, value); + StringUTF16.checkBoundsBeginEnd(begin, end, value); + int pos = DecimalDigits.getCharsUTF16(l, end, value); + assert begin == pos; + return pos; } public static boolean contentEquals(byte[] v1, byte[] v2, int len) { diff --git a/test/micro/org/openjdk/bench/java/lang/StringBuilders.java b/test/micro/org/openjdk/bench/java/lang/StringBuilders.java index ed5c0d30db8..e41bd361ff5 100644 --- a/test/micro/org/openjdk/bench/java/lang/StringBuilders.java +++ b/test/micro/org/openjdk/bench/java/lang/StringBuilders.java @@ -54,6 +54,8 @@ public class StringBuilders { private StringBuilder sbLatin2; private StringBuilder sbUtf16; private StringBuilder sbUtf17; + private int[] intsArray; + private long[] longArray; @Setup public void setup() { @@ -69,6 +71,13 @@ public void setup() { sbLatin2 = new StringBuilder("Latin1 string"); sbUtf16 = new StringBuilder("UTF-\uFF11\uFF16 string"); sbUtf17 = new StringBuilder("UTF-\uFF11\uFF16 string"); + int size = 16; + intsArray = new int[size]; + longArray = new long[size]; + for (int i = 0; i < longArray.length; i++) { + intsArray[i] = ((100 * i + i) << 24) + 4543 + i * 4; + longArray[i] = ((100L * i + i) << 32) + 4543 + i * 4L; + } } @Benchmark @@ -224,6 +233,47 @@ public String toStringCharWithInt8() { return result.toString(); } + @Benchmark + public int appendWithIntLatin1() { + StringBuilder buf = sbLatin1; + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithIntUtf16() { + StringBuilder buf = sbUtf16; + buf.setLength(0); + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithLongLatin1() { + StringBuilder buf = sbLatin1; + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithLongUtf16() { + StringBuilder buf = sbUtf16; + buf.setLength(0); + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } @Benchmark public int appendWithBool8Latin1() { From 3b9b1ba5ddbeb1ecb7e2b4a9053e7cd3cac4a013 Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Mon, 11 Nov 2024 09:24:44 +0000 Subject: [PATCH 09/23] 8343118: [TESTBUG] java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java fails with rror. Can't find HTML file PrintCheckboxManualTest.html Reviewed-by: abhiscxk, dnguyen --- .../PrintCheckboxManualTest.java | 315 ++++-------------- 1 file changed, 62 insertions(+), 253 deletions(-) diff --git a/test/jdk/java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java b/test/jdk/java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java index 4fd3ee8ef16..72ad2a4589e 100644 --- a/test/jdk/java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java +++ b/test/jdk/java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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 @@ -22,84 +22,52 @@ */ /* - @test - @bug 5045936 5055171 - @summary Tests that there is no ClassCastException thrown in printing - checkbox and scrollbar with XAWT - @key printer - @run applet/manual=yesno PrintCheckboxManualTest.html -*/ - -// Note there is no @ in front of test above. This is so that the -// harness will not mistake this file as a test file. It should -// only see the html file as a test file. (the harness runs all -// valid test files, so it would run this test twice if this file -// were valid as well as the html file.) -// Also, note the area= after Your Name in the author tag. Here, you -// should put which functional area the test falls in. See the -// AWT-core home page -> test areas and/or -> AWT team for a list of -// areas. - - - -import java.awt.*; -import java.awt.event.*; - - -//Manual tests should run as applet tests if possible because they -// get their environments cleaned up, including AWT threads, any -// test created threads, and any system resources used by the test -// such as file descriptors. (This is normally not a problem as -// main tests usually run in a separate VM, however on some platforms -// such as the Mac, separate VMs are not possible and non-applet -// tests will cause problems). Also, you don't have to worry about -// synchronisation stuff in Applet tests the way you do in main -// tests... - - -public class PrintCheckboxManualTest extends Panel -{ - //Declare things used in the test, like buttons and labels here - Frame f; - - public static void main(String[] args) { - PrintCheckboxManualTest a = new PrintCheckboxManualTest(); + * @test + * @bug 5045936 5055171 + * @summary Tests that there is no ClassCastException thrown in printing + * checkbox and scrollbar with XAWT + * @key printer + * @requires (os.family == "linux") + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual PrintCheckboxManualTest + */ - a.init(); - a.start(); +import java.awt.Button; +import java.awt.Checkbox; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.GridLayout; +import java.awt.Panel; +import java.awt.PrintJob; +import java.awt.Scrollbar; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class PrintCheckboxManualTest extends Panel { + + private static final String INSTRUCTIONS = """ + This test is for Linux with XToolkit ONLY!, + 1. Click the 'Print' button on the frame + 2. Select a printer in the print dialog and proceed + 3. If the frame with checkbox and button on it + is printed without any exception test PASSED else FAILED. + """; + + public static void main(String[] args) throws Exception { + PassFailJFrame.builder() + .title("Instructions") + .instructions(INSTRUCTIONS) + .columns(40) + .testUI(PrintCheckboxManualTest::createTestUI) + .build() + .awaitAndCheck(); } - public void init() - { - //Create instructions for the user here, as well as set up - // the environment -- set the layout manager, add buttons, - // etc. - this.setLayout (new BorderLayout ()); - - String[] instructions = - { - "Linux or Solaris with XToolkit ONLY!", - "1. Click the 'Print' button on the frame", - "2. Select a printer in the print dialog and proceed", - "3. If the frame with checkbox and button on it is printed successfully test PASSED else FAILED" - }; - Sysout.createDialogWithInstructions( instructions ); - - }//End init() + private static Frame createTestUI() { - public void start () - { - //Get things going. Request focus, set size, et cetera - setSize (200,200); - setVisible(true); - validate(); - - //What would normally go into main() will probably go here. - //Use System.out.println for diagnostic messages that you want - // to read after the test is done. - //Use Sysout.println for messages you want the tester to read. - - f = new Frame("Print checkbox"); + Frame f = new Frame("Print checkbox"); f.setLayout(new GridLayout(2, 2)); f.setSize(200, 100); @@ -111,185 +79,26 @@ public void start () f.add(sb); Button b = new Button("Print"); - b.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent ev) - { - PrintJob pj = Toolkit.getDefaultToolkit().getPrintJob(f, "PrintCheckboxManualTest", null); - if (pj != null) - { - try - { - Graphics g = pj.getGraphics(); - f.printAll(g); - g.dispose(); - pj.end(); - Sysout.println("Test PASSED"); - } - catch (ClassCastException cce) - { - Sysout.println("Test FAILED: ClassCastException"); -// throw new RuntimeException("Test FAILED: ClassCastException", cce); - } - catch (Exception e) - { - Sysout.println("Test FAILED: unknown Exception"); -// throw new Error("Test FAILED: unknown exception", e); - } + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + PrintJob pj = Toolkit.getDefaultToolkit(). + getPrintJob(f, "PrintCheckboxManualTest", + null); + if (pj != null) { + try { + Graphics g = pj.getGraphics(); + f.printAll(g); + g.dispose(); + pj.end(); + } catch (ClassCastException cce) { + throw new RuntimeException("Test FAILED: ClassCastException", cce); + } catch (Exception e) { + throw new Error("Test FAILED: unknown exception", e); + } } - } + } }); f.add(b); - - f.setVisible(true); - }// start() - - //The rest of this class is the actions which perform the test... - - //Use Sysout.println to communicate with the user NOT System.out!! - //Sysout.println ("Something Happened!"); - -} - -/* Place other classes related to the test after this line */ - - - - - -/**************************************************** - Standard Test Machinery - DO NOT modify anything below -- it's a standard - chunk of code whose purpose is to make user - interaction uniform, and thereby make it simpler - to read and understand someone else's test. - ****************************************************/ - -/** - This is part of the standard test machinery. - It creates a dialog (with the instructions), and is the interface - for sending text messages to the user. - To print the instructions, send an array of strings to Sysout.createDialog - WithInstructions method. Put one line of instructions per array entry. - To display a message for the tester to see, simply call Sysout.println - with the string to be displayed. - This mimics System.out.println but works within the test harness as well - as standalone. - */ - -class Sysout -{ - private static TestDialog dialog; - - public static void createDialogWithInstructions( String[] instructions ) - { - dialog = new TestDialog( new Frame(), "Instructions" ); - dialog.printInstructions( instructions ); - dialog.setVisible(true); - println( "Any messages for the tester will display here." ); + return f; } - - public static void createDialog( ) - { - dialog = new TestDialog( new Frame(), "Instructions" ); - String[] defInstr = { "Instructions will appear here. ", "" } ; - dialog.printInstructions( defInstr ); - dialog.setVisible(true); - println( "Any messages for the tester will display here." ); - } - - - public static void printInstructions( String[] instructions ) - { - dialog.printInstructions( instructions ); - } - - - public static void println( String messageIn ) - { - dialog.displayMessage( messageIn ); - } - -}// Sysout class - -/** - This is part of the standard test machinery. It provides a place for the - test instructions to be displayed, and a place for interactive messages - to the user to be displayed. - To have the test instructions displayed, see Sysout. - To have a message to the user be displayed, see Sysout. - Do not call anything in this dialog directly. - */ -class TestDialog extends Dialog -{ - - TextArea instructionsText; - TextArea messageText; - int maxStringLength = 80; - - //DO NOT call this directly, go through Sysout - public TestDialog( Frame frame, String name ) - { - super( frame, name ); - int scrollBoth = TextArea.SCROLLBARS_BOTH; - instructionsText = new TextArea( "", 15, maxStringLength, scrollBoth ); - add( "North", instructionsText ); - - messageText = new TextArea( "", 5, maxStringLength, scrollBoth ); - add("Center", messageText); - - pack(); - - setVisible(true); - }// TestDialog() - - //DO NOT call this directly, go through Sysout - public void printInstructions( String[] instructions ) - { - //Clear out any current instructions - instructionsText.setText( "" ); - - //Go down array of instruction strings - - String printStr, remainingStr; - for( int i=0; i < instructions.length; i++ ) - { - //chop up each into pieces maxSringLength long - remainingStr = instructions[ i ]; - while( remainingStr.length() > 0 ) - { - //if longer than max then chop off first max chars to print - if( remainingStr.length() >= maxStringLength ) - { - //Try to chop on a word boundary - int posOfSpace = remainingStr. - lastIndexOf( ' ', maxStringLength - 1 ); - - if( posOfSpace <= 0 ) posOfSpace = maxStringLength - 1; - - printStr = remainingStr.substring( 0, posOfSpace + 1 ); - remainingStr = remainingStr.substring( posOfSpace + 1 ); - } - //else just print - else - { - printStr = remainingStr; - remainingStr = ""; - } - - instructionsText.append( printStr + "\n" ); - - }// while - - }// for - - }//printInstructions() - - //DO NOT call this directly, go through Sysout - public void displayMessage( String messageIn ) - { - messageText.append( messageIn + "\n" ); - System.out.println(messageIn); - } - -}// TestDialog class +} From 41a6aaf533cfd211b0604203a47303d94480c674 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Mon, 11 Nov 2024 09:34:43 +0000 Subject: [PATCH 10/23] 8341176: Permit access to diagnostics for transient snippets Reviewed-by: vromero --- .../share/classes/jdk/jshell/Eval.java | 11 +++- .../share/classes/jdk/jshell/JShell.java | 24 +++++++- .../jdk/jshell/SourceCodeAnalysis.java | 3 +- .../jdk/jshell/AnalyzeSnippetTest.java | 60 ++++++++++++++++++- 4 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/jdk.jshell/share/classes/jdk/jshell/Eval.java b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java index 0ee6a926b41..bc6f6d30236 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/Eval.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java @@ -176,7 +176,16 @@ List sourceToSnippetsWithWrappers(String userSource) { List toScratchSnippets(String userSource) { try { preserveState = true; - return sourceToSnippets(userSource); + List result = sourceToSnippetsWithWrappers(userSource); + result.forEach(snippet -> { + if (snippet.diagnostics() == null || snippet.diagnostics().isEmpty()) { + //if no better diagnostics set yet, do trial compilation, and + //set diagnostic found: + DiagList fullDiagnostics = state.taskFactory.analyze(snippet.outerWrap(), AnalyzeTask::getDiagnostics); + snippet.setDiagnostics(fullDiagnostics); + } + }); + return result; } finally { preserveState = false; } diff --git a/src/jdk.jshell/share/classes/jdk/jshell/JShell.java b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java index ffa46cf8be4..4ca8e3830c8 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/JShell.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, 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 @@ -678,6 +678,12 @@ public Status status(Snippet snippet) { * Return the diagnostics of the most recent evaluation of the snippet. * The evaluation can either because of an explicit {@code eval()} call or * an automatic update triggered by a dependency. + * + *

This method will return best-effort diagnostics for snippets returned + * from {@link SourceCodeAnalysis#sourceToSnippets(java.lang.String) }. The + * diagnostics returned for such snippets may differ from diagnostics provided + * after the snippet is {@link #eval(java.lang.String) }-ed. + * * @param snippet the {@code Snippet} to look up * @return the diagnostics corresponding to this snippet. This does not * include unresolvedDependencies references reported in {@code unresolvedDependencies()}. @@ -686,7 +692,7 @@ public Status status(Snippet snippet) { * this {@code JShell} instance. */ public Stream diagnostics(Snippet snippet) { - return checkValidSnippet(snippet).diagnostics().stream(); + return checkValidSnippet(snippet, true).diagnostics().stream(); } /** @@ -901,10 +907,22 @@ void checkIfAlive() throws IllegalStateException { * @return the input Snippet (for chained calls) */ private Snippet checkValidSnippet(Snippet sn) { + return checkValidSnippet(sn, false); + } + + /** + * Check a Snippet parameter coming from the API user + * @param sn the Snippet to check + * @param acceptUnassociated accept snippets that are unassociated + * @throws NullPointerException if Snippet parameter is null + * @throws IllegalArgumentException if Snippet is not from this JShell + * @return the input Snippet (for chained calls) + */ + private Snippet checkValidSnippet(Snippet sn, boolean acceptUnassociated) { if (sn == null) { throw new NullPointerException(messageFormat("jshell.exc.null")); } else { - if (sn.key().state() != this || sn.id() == Snippet.UNASSOCIATED_ID) { + if (sn.key().state() != this || (!acceptUnassociated && sn.id() == Snippet.UNASSOCIATED_ID)) { throw new IllegalArgumentException(messageFormat("jshell.exc.alien", sn.toString())); } return sn; diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java index 99bfd870f37..0375a2ead65 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java @@ -145,7 +145,8 @@ public abstract class SourceCodeAnalysis { * will be {@code "*UNASSOCIATED*"}. * The returned snippets are not associated with the * {@link JShell} instance, so attempts to pass them to {@code JShell} - * methods will throw an {@code IllegalArgumentException}. + * methods will throw an {@code IllegalArgumentException}, unless otherwise + * noted. * They will not appear in queries for snippets -- * for example, {@link JShell#snippets() }. *

diff --git a/test/langtools/jdk/jshell/AnalyzeSnippetTest.java b/test/langtools/jdk/jshell/AnalyzeSnippetTest.java index 3e2e1a839e2..a9b0315e960 100644 --- a/test/langtools/jdk/jshell/AnalyzeSnippetTest.java +++ b/test/langtools/jdk/jshell/AnalyzeSnippetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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,7 +23,7 @@ /* * @test - * @bug 8182270 + * @bug 8182270 8341176 * @summary test non-eval Snippet analysis * @build KullaTesting TestingInputStream * @run testng AnalyzeSnippetTest @@ -32,8 +32,10 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.List; +import java.util.stream.Stream; import jdk.jshell.Snippet; import jdk.jshell.DeclarationSnippet; +import jdk.jshell.Diag; import org.testng.annotations.Test; import jdk.jshell.JShell; @@ -52,6 +54,7 @@ import jdk.jshell.TypeDeclSnippet; import jdk.jshell.VarSnippet; import static jdk.jshell.Snippet.SubKind.*; +import jdk.jshell.SourceCodeAnalysis.SnippetWrapper; @Test public class AnalyzeSnippetTest { @@ -141,6 +144,49 @@ public void testErroneous() { SubKind.UNKNOWN_SUBKIND); } + public void testDiagnosticsForSourceSnippet() { + Snippet sn; + sn = assertSnippet("unknown()", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "0-7:compiler.err.cant.resolve.location.args"); + sn = assertSnippet("new String(null, )", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "17-17:compiler.err.illegal.start.of.expr"); + sn = assertSnippet("1 + ", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "3-3:compiler.err.premature.eof"); + sn = assertSnippet("class C {", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "9-9:compiler.err.premature.eof"); + sn = assertSnippet("class C {}", CLASS_SUBKIND); + assertDiagnostics(sn); + sn = assertSnippet("void t() { throw new java.io.IOException(); }", METHOD_SUBKIND); + assertDiagnostics(sn, "11-43:compiler.err.unreported.exception.need.to.catch.or.throw"); + sn = assertSnippet("void t() { unknown(); }", METHOD_SUBKIND); + assertDiagnostics(sn, "11-18:compiler.err.cant.resolve.location.args"); + sn = assertSnippet("import unknown.unknown;", SINGLE_TYPE_IMPORT_SUBKIND); + assertDiagnostics(sn, "7-22:compiler.err.doesnt.exist"); + } + + public void testSnippetWrapper() { + SourceCodeAnalysis analysis = state.sourceCodeAnalysis(); + Snippet sn; + String code = "unknown()"; + sn = assertSnippet(code, UNKNOWN_SUBKIND); + SnippetWrapper wrapper = analysis.wrapper(sn); + String wrapped = wrapper.wrapped(); + assertEquals(wrapped, """ + package REPL; + + class $JShell$DOESNOTMATTER { + public static java.lang.Object do_it$() throws java.lang.Throwable { + return unknown(); + } + } + """); + for (int pos = 0; pos < code.length(); pos++) { + int wrappedPos = wrapper.sourceToWrappedPosition(pos); + assertEquals(wrapped.charAt(wrappedPos), code.charAt(pos)); + assertEquals(wrapper.wrappedToSourcePosition(wrappedPos), pos); + } + } + public void testNoStateChange() { assertSnippet("int a = 5;", SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND); assertSnippet("a", SubKind.UNKNOWN_SUBKIND); @@ -159,4 +205,14 @@ private Snippet assertSnippet(String input, SubKind sk) { assertEquals(sn.subKind(), sk); return sn; } + + private String diagToString(Diag d) { + return d.getStartPosition() + "-" + d.getEndPosition() + ":" + d.getCode(); + } + + private void assertDiagnostics(Snippet s, String... expectedDiags) { + List actual = state.diagnostics(s).map(this::diagToString).toList(); + List expected = List.of(expectedDiags); + assertEquals(actual, expected); + } } From d7d7c0f495bdeadc921b267c40fb122318a6d0f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Mon, 11 Nov 2024 10:05:15 +0000 Subject: [PATCH 11/23] 8343067: C2: revisit constant-offset AddP chains after successful input idealizations Reviewed-by: kvn, vlivanov --- .../TestCombineAddPWithConstantOffsets.java | 56 +++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 6 ++ 2 files changed, 62 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/c2/irTests/igvn/TestCombineAddPWithConstantOffsets.java diff --git a/test/hotspot/jtreg/compiler/c2/irTests/igvn/TestCombineAddPWithConstantOffsets.java b/test/hotspot/jtreg/compiler/c2/irTests/igvn/TestCombineAddPWithConstantOffsets.java new file mode 100644 index 00000000000..69963316fa9 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/igvn/TestCombineAddPWithConstantOffsets.java @@ -0,0 +1,56 @@ +/* + * 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. + */ +package compiler.c2.irTests.igvn; + +import compiler.lib.ir_framework.*; + +/* + * @test + * @bug 8343067 + * @requires os.simpleArch == "x64" | os.simpleArch == "aarch64" + * @requires vm.compiler2.enabled + * @summary Test that chains of AddP nodes with constant offsets are idealized + * when their offset input changes. + * @library /test/lib / + * @run driver compiler.c2.irTests.igvn.TestCombineAddPWithConstantOffsets + */ +public class TestCombineAddPWithConstantOffsets { + + public static void main(String[] args) { + TestFramework.run(); + } + + @Test + @IR(applyIfPlatform = {"x64", "true"}, failOn = {IRNode.ADD_P_OF, ".*"}) + @IR(applyIfPlatform = {"aarch64", "true"}, failOn = {IRNode.ADD_P_OF, "reg_imm"}) + static void testCombineAddPWithConstantOffsets(int[] arr) { + for (long i = 6; i < 14; i++) { + arr[(int)i] = 1; + } + } + + @Run(test = {"testCombineAddPWithConstantOffsets"}) + public void runTests() { + testCombineAddPWithConstantOffsets(new int[14]); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index fc55662d801..1d586e972be 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -279,6 +279,12 @@ public class IRNode { superWordNodes(ADD_REDUCTION_VL, "AddReductionVL"); } + public static final String ADD_P_OF = COMPOSITE_PREFIX + "ADD_P_OF" + POSTFIX; + static { + String regex = START + "addP_" + IS_REPLACED + MID + ".*" + END; + machOnly(ADD_P_OF, regex); + } + public static final String ALLOC = PREFIX + "ALLOC" + POSTFIX; static { String optoRegex = "(.*precise .*\\R((.*(?i:mov|mv|xorl|nop|spill).*|\\s*)\\R)*.*(?i:call,static).*wrapper for: C2 Runtime new_instance" + END; From bcc68daef423f59dbd4e5b28a67e05abae652f15 Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Mon, 11 Nov 2024 10:15:02 +0000 Subject: [PATCH 12/23] 8268895: Do not filter out man pages from build Reviewed-by: dholmes, jwaters --- make/autoconf/jdk-options.m4 | 7 ++----- make/autoconf/spec.gmk.template | 2 -- make/common/modules/LauncherCommon.gmk | 13 +++++-------- make/conf/jib-profiles.js | 1 - 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4 index fec0a93161b..c5c2290019b 100644 --- a/make/autoconf/jdk-options.m4 +++ b/make/autoconf/jdk-options.m4 @@ -638,14 +638,11 @@ AC_DEFUN([JDKOPT_EXCLUDE_TRANSLATIONS], ################################################################################ # -# Optionally disable man pages +# Optionally disable man pages (deprecated) # AC_DEFUN([JDKOPT_ENABLE_DISABLE_MANPAGES], [ - UTIL_ARG_ENABLE(NAME: manpages, DEFAULT: true, RESULT: BUILD_MANPAGES, - DESC: [enable copying of static man pages], - CHECKING_MSG: [if static man pages should be copied]) - AC_SUBST(BUILD_MANPAGES) + UTIL_DEPRECATED_ARG_ENABLE(manpages) ]) ################################################################################ diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template index 62afd6577ab..231355043d5 100644 --- a/make/autoconf/spec.gmk.template +++ b/make/autoconf/spec.gmk.template @@ -367,8 +367,6 @@ ENABLE_GENERATE_CLASSLIST := @ENABLE_GENERATE_CLASSLIST@ EXCLUDE_TRANSLATIONS := @EXCLUDE_TRANSLATIONS@ -BUILD_MANPAGES := @BUILD_MANPAGES@ - BUILD_CDS_ARCHIVE := @BUILD_CDS_ARCHIVE@ BUILD_CDS_ARCHIVE_COH := @BUILD_CDS_ARCHIVE_COH@ diff --git a/make/common/modules/LauncherCommon.gmk b/make/common/modules/LauncherCommon.gmk index 7c2cef58835..aa721a683d7 100644 --- a/make/common/modules/LauncherCommon.gmk +++ b/make/common/modules/LauncherCommon.gmk @@ -228,14 +228,11 @@ ifeq ($(call isTargetOsType, unix)+$(MAKEFILE_PREFIX), true+Launcher) endif else # No markdown man pages present - ifeq ($(BUILD_MANPAGES), true) - # BUILD_MANPAGES is a mis-nomer. It really means "copy the pre-generated man pages". - $(eval $(call SetupCopyFiles, COPY_MAN_PAGES, \ - DEST := $(SUPPORT_OUTPUTDIR)/modules_man/$(MODULE)/man1, \ - FILES := $(MAN_FILES_TROFF), \ - )) + $(eval $(call SetupCopyFiles, COPY_MAN_PAGES, \ + DEST := $(SUPPORT_OUTPUTDIR)/modules_man/$(MODULE)/man1, \ + FILES := $(MAN_FILES_TROFF), \ + )) - TARGETS += $(COPY_MAN_PAGES) - endif + TARGETS += $(COPY_MAN_PAGES) endif endif diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index 0785d340f48..a28d85c146f 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -252,7 +252,6 @@ var getJibProfilesCommon = function (input, data) { default_make_targets: ["product-bundles", "test-bundles", "static-libs-bundles"], configure_args: concat( "--with-exclude-translations=es,fr,it,ko,pt_BR,sv,ca,tr,cs,sk,ja_JP_A,ja_JP_HA,ja_JP_HI,ja_JP_I,zh_TW,zh_HK", - "--disable-manpages", "--disable-jvm-feature-shenandoahgc", versionArgs(input, common)) }; From 6aff8b16818aa30b53d569c3f2496f0fa04f9575 Mon Sep 17 00:00:00 2001 From: Kevin Walls Date: Mon, 11 Nov 2024 12:26:38 +0000 Subject: [PATCH 13/23] 8343838: Test EmptyDomainNotificationTest.java fails with ListenerNotFoundException Reviewed-by: dholmes --- .../jmx/remote/internal/ServerNotifForwarder.java | 14 +++++++++++++- test/jdk/ProblemList.txt | 2 -- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java b/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java index f15c416f296..fb24055cc8f 100644 --- a/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java +++ b/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java @@ -162,10 +162,22 @@ public void removeNotificationListener(ObjectName name, connectionId, name, getSubject()); } + // 6238731: set the default domain if no domain is set. + ObjectName nn = name; + if (name.getDomain() == null || name.getDomain().isEmpty()) { + try { + nn = ObjectName.getInstance(mbeanServer.getDefaultDomain(), + name.getKeyPropertyList()); + } catch (MalformedObjectNameException mfoe) { + // impossible, but... + throw new IOException(mfoe.getMessage(), mfoe); + } + } + Exception re = null; for (int i = 0 ; i < listenerIDs.length ; i++) { try { - removeNotificationListener(name, listenerIDs[i]); + removeNotificationListener(nn, listenerIDs[i]); } catch (Exception e) { // Give back the first exception // diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 4b483a4b076..1ae9623ef14 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -563,8 +563,6 @@ javax/management/monitor/DerivedGaugeMonitorTest.java 8042211 generic- javax/management/remote/mandatory/connection/BrokenConnectionTest.java 8262312 linux-all -javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java 8343838 generic-all - ############################################################################ # jdk_net From 5a061bdc7996441063a9b498112da4ef9053eb58 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Mon, 11 Nov 2024 13:25:42 +0000 Subject: [PATCH 14/23] 8343535: IGV: Colorize nodes on demand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roberto Castañeda Lozano Reviewed-by: chagedorn, rcastanedalo --- .../sun/hotspot/igv/view/DiagramScene.java | 13 +- .../sun/hotspot/igv/view/DiagramViewer.java | 9 +- .../hotspot/igv/view/EditorTopComponent.java | 5 + .../hotspot/igv/view/actions/ColorAction.java | 170 ++++++++++++++++++ .../igv/view/actions/ExtractAction.java | 4 +- .../igv/view/widgets/FigureWidget.java | 37 ++-- .../com/sun/hotspot/igv/view/images/color.gif | Bin 0 -> 207 bytes 7 files changed, 216 insertions(+), 22 deletions(-) create mode 100644 src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java create mode 100644 src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/color.gif diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index e47518de4a7..f9c2e991d46 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -222,6 +222,17 @@ public void filteredChanged(SelectionCoordinator coordinator) { } }; + public void colorSelectedFigures(Color color) { + for (Figure figure : model.getSelectedFigures()) { + figure.setColor(color); + FigureWidget figureWidget = getWidget(figure); + if (figureWidget != null) { + figureWidget.refreshColor(); + } + } + validateAll(); + } + private Point getScrollPosition() { return scrollPane.getViewport().getViewPosition(); } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewer.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewer.java index 591bbe371a3..e8cfd2968dc 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewer.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -26,10 +26,7 @@ import com.sun.hotspot.igv.data.ChangedEvent; import com.sun.hotspot.igv.data.InputNode; -import java.awt.Component; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; +import java.awt.*; import java.util.Collection; import javax.swing.JComponent; import org.openide.awt.UndoRedo; @@ -89,4 +86,6 @@ enum InteractionMode { Rectangle getBounds(); JComponent getView(); + + void colorSelectedFigures(Color color); } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java index 617f59a591d..e24ffa476a2 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java @@ -99,6 +99,7 @@ public EditorTopComponent(DiagramViewModel diagramViewModel) { }; Action[] actionsWithSelection = new Action[]{ + ColorAction.get(ColorAction.class), ExtractAction.get(ExtractAction.class), HideAction.get(HideAction.class), null, @@ -349,6 +350,10 @@ public void addSelectedNodes(Collection nodes, boolean showIfHidden) scene.addSelectedNodes(nodes, showIfHidden); } + public void colorSelectedFigures(Color color) { + scene.colorSelectedFigures(color); + } + public void centerSelectedNodes() { scene.centerSelectedFigures(); } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java new file mode 100644 index 00000000000..7a38587eb38 --- /dev/null +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java @@ -0,0 +1,170 @@ +/* + * 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. + * + */ +package com.sun.hotspot.igv.view.actions; + +import com.sun.hotspot.igv.view.DiagramViewModel; +import com.sun.hotspot.igv.view.EditorTopComponent; +import com.sun.hotspot.igv.view.widgets.FigureWidget; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import javax.swing.*; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; + + +@ActionID(category = "View", id = "com.sun.hotspot.igv.view.actions.ColorAction") +@ActionRegistration(displayName = "#CTL_ColorAction") +@ActionReferences({ + @ActionReference(path = "Menu/View", position = 360), + @ActionReference(path = "Shortcuts", name = "D-C") +}) +@Messages({ + "CTL_ColorAction=Color", + "HINT_ColorAction=Color current set of selected nodes" +}) +public final class ColorAction extends ModelAwareAction { + + @Override + protected String iconResource() { + return "com/sun/hotspot/igv/view/images/color.gif"; // NOI18N + } + + @Override + protected String getDescription() { + return NbBundle.getMessage(ColorAction.class, "HINT_ColorAction"); + } + + @Override + public String getName() { + return NbBundle.getMessage(ColorAction.class, "CTL_ColorAction"); + } + + private static final ArrayList colors = new ArrayList<>(Arrays.asList( + Color.RED, + Color.ORANGE, + Color.YELLOW, + Color.GREEN, + Color.CYAN, + Color.BLUE, + Color.MAGENTA, + Color.PINK, + Color.DARK_GRAY, + Color.GRAY, + Color.LIGHT_GRAY, + Color.WHITE + )); + + private static final JLabel selectedColorLabel = new JLabel("Preview"); + private static final JColorChooser colorChooser = new JColorChooser(Color.WHITE); + + public ColorAction() { + initializeComponents(); + } + + private void initializeComponents() { + selectedColorLabel.setPreferredSize(new Dimension(3 * 32, 32)); + selectedColorLabel.setOpaque(true); + selectedColorLabel.setBackground(Color.WHITE); + selectedColorLabel.setForeground(Color.BLACK); // Set text color + selectedColorLabel.setHorizontalAlignment(SwingConstants.CENTER); // Center the text + + + // Add a ChangeListener to react to color selection changes + colorChooser.getSelectionModel().addChangeListener(e -> { + Color selectedColor = colorChooser.getColor(); + if (selectedColor != null) { + selectedColorLabel.setBackground(selectedColor); + selectedColorLabel.setForeground(FigureWidget.getTextColor(selectedColor)); + } + }); + + // Create a panel to display recent colors + JPanel colorsPanel = new JPanel(); + colorsPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + for (Color color : colors) { + JButton colorButton = new JButton(); + colorButton.setBackground(color); + colorButton.setOpaque(true); + colorButton.setBorderPainted(false); + colorButton.setRolloverEnabled(false); + colorButton.setRequestFocusEnabled(false); + + colorButton.setPreferredSize(new Dimension(16, 16)); + colorButton.addActionListener(e -> { + selectedColorLabel.setBackground(color); + selectedColorLabel.setForeground(FigureWidget.getTextColor(color)); + }); + colorsPanel.add(colorButton); + } + colorsPanel.add(selectedColorLabel, 0); + colorsPanel.revalidate(); + colorsPanel.repaint(); + + // Add recent colors panel below the color chooser + colorChooser.setPreviewPanel(colorsPanel); + } + + // Variables to store the dialog position + private Point dialogLoc = null; + + public void performAction(DiagramViewModel model) { + EditorTopComponent editor = EditorTopComponent.getActive(); + if (editor != null) { + // Create the dialog with an OK button to select the color + final JDialog[] dialogHolder = new JDialog[1]; + dialogHolder[0] = JColorChooser.createDialog( + null, + "Choose a Color", + true, + colorChooser, + e -> { + // Save the current location + dialogLoc = dialogHolder[0].getLocation(); + // OK button action + Color selectedColor = selectedColorLabel.getBackground(); + if (selectedColor != null) { + editor.colorSelectedFigures(selectedColor); + } + }, + null // Cancel button action + ); + + // Set the dialog's position if previously saved + if (dialogLoc != null) { + dialogHolder[0].setLocation(dialogLoc); + } + dialogHolder[0].setVisible(true); + } + } + + @Override + public boolean isEnabled(DiagramViewModel model) { + return model != null && !model.getSelectedNodes().isEmpty(); + } +} diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ExtractAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ExtractAction.java index 24815527a0e..24547b19b6a 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ExtractAction.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ExtractAction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -42,7 +42,7 @@ @ActionReference(path = "Shortcuts", name = "D-X") }) @Messages({ - "CTL_ExtractAction=Extract action", + "CTL_ExtractAction=Extract", "HINT_ExtractAction=Extract current set of selected nodes" }) public final class ExtractAction extends ModelAwareAction { diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index 7ac76fdefba..bbdf08dc8b8 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -90,7 +90,20 @@ private void formatExtraLabel(boolean selected) { if (getFigure().getProperties().get("extra_label") != null) { LabelWidget extraLabelWidget = labelWidgets.get(labelWidgets.size() - 1); extraLabelWidget.setFont(Diagram.FONT.deriveFont(Font.ITALIC)); - extraLabelWidget.setForeground(selected ? getTextColor() : Color.DARK_GRAY); + extraLabelWidget.setForeground(getTextColorHelper(figure.getColor(), !selected)); + } + } + + public static Color getTextColor(Color color) { + return getTextColorHelper(color, false); + } + + private static Color getTextColorHelper(Color bg, boolean useGrey) { + double brightness = bg.getRed() * 0.21 + bg.getGreen() * 0.72 + bg.getBlue() * 0.07; + if (brightness < 150) { + return useGrey ? Color.LIGHT_GRAY : Color.WHITE; + } else { + return useGrey ? Color.DARK_GRAY : Color.BLACK; } } @@ -113,7 +126,6 @@ public FigureWidget(final Figure f, DiagramScene scene) { LayoutFactory.SerialAlignment.LEFT_TOP : LayoutFactory.SerialAlignment.CENTER; middleWidget.setLayout(LayoutFactory.createVerticalFlowLayout(textAlign, 0)); - middleWidget.setBackground(f.getColor()); middleWidget.setOpaque(true); middleWidget.getActions().addAction(new DoubleClickAction(this)); middleWidget.setCheckClipping(false); @@ -143,13 +155,13 @@ public FigureWidget(final Figure f, DiagramScene scene) { textWidget.addChild(lw); lw.setLabel(displayString); lw.setFont(Diagram.FONT); - lw.setForeground(getTextColor()); lw.setAlignment(LabelWidget.Alignment.CENTER); lw.setVerticalAlignment(LabelWidget.VerticalAlignment.CENTER); lw.setBorder(BorderFactory.createEmptyBorder()); lw.setCheckClipping(false); } formatExtraLabel(false); + refreshColor(); if (getFigure().getWarning() != null) { ImageWidget warningWidget = new ImageWidget(scene, warningSign); @@ -184,6 +196,13 @@ protected Sheet createSheet() { this.setToolTipText(PropertiesConverter.convertToHTML(f.getProperties())); } + public void refreshColor() { + middleWidget.setBackground(figure.getColor()); + for (LabelWidget lw : labelWidgets) { + lw.setForeground(getTextColor(figure.getColor())); + } + } + @Override protected void notifyStateChanged(ObjectState previousState, ObjectState state) { super.notifyStateChanged(previousState, state); @@ -222,16 +241,6 @@ public Figure getFigure() { return figure; } - private Color getTextColor() { - Color bg = figure.getColor(); - double brightness = bg.getRed() * 0.21 + bg.getGreen() * 0.72 + bg.getBlue() * 0.07; - if (brightness < 150) { - return Color.WHITE; - } else { - return Color.BLACK; - } - } - @Override protected void paintChildren() { Composite oldComposite = null; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/color.gif b/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/color.gif new file mode 100644 index 0000000000000000000000000000000000000000..fb95a6f29589d10b649edb2204bc016b0cf30b5e GIT binary patch literal 207 zcmZ?wbh9u|6krfwIKlt|Oj6TAm0JI)F?`o%{H@KPnLV>o!+pW!FN%3lose>435{~v4&c0%zd3nv$YID-yI8e}H} ztIq?4K4(VX46BKYS1wx#_W`pb{G%QzUU0YRNdMF0Q* literal 0 HcmV?d00001 From 4c1b0e03d35fb85dc7b8d372af5c1cc0edc55f36 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Mon, 11 Nov 2024 13:35:25 +0000 Subject: [PATCH 15/23] 8311302: Implement JEP 493: Linking Run-Time Images without JMODs Co-authored-by: Mandy Chung Reviewed-by: mchung, alanb, erikj, ihse --- make/Images.gmk | 4 + make/autoconf/jdk-options.m4 | 33 +- make/autoconf/spec.gmk.template | 1 + .../jlink/internal/ImageFileCreator.java | 376 +++++++++- .../jdk/tools/jlink/internal/JRTArchive.java | 525 +++++++++++++ .../jdk/tools/jlink/internal/Jlink.java | 26 +- .../jdk/tools/jlink/internal/JlinkTask.java | 356 +++++---- .../jlink/internal/LinkableRuntimeImage.java | 88 +++ .../jdk/tools/jlink/internal/TaskHelper.java | 36 +- .../runtimelink/JimageDiffGenerator.java | 113 +++ .../internal/runtimelink/ResourceDiff.java | 286 +++++++ .../runtimelink/ResourcePoolReader.java | 57 ++ .../RuntimeImageLinkException.java | 62 ++ .../tools/jlink/resources/jlink.properties | 19 +- test/hotspot/jtreg/TEST.ROOT | 4 +- test/jdk/TEST.ROOT | 4 +- .../jdk/modules/etc/JmodExcludedFiles.java | 1 + .../jdk/tools/jlink/ImageFileCreatorTest.java | 7 +- test/jdk/tools/jlink/IntegrationTest.java | 23 +- .../jlink/JLinkDedupTestBatchSizeOne.java | 31 +- .../tools/jlink/JLinkHelpCapabilityTest.java | 78 ++ .../JLinkMRJavaBaseVersionTest.java | 3 +- .../plugins/GenerateJLIClassesPluginTest.java | 29 +- .../plugins/IncludeLocalesPluginTest.java | 35 +- .../AbstractLinkableRuntimeTest.java | 705 ++++++++++++++++++ .../jlink/runtimeImage/AddOptionsTest.java | 86 +++ .../BasicJlinkMissingJavaBase.java | 72 ++ .../jlink/runtimeImage/BasicJlinkTest.java | 69 ++ .../jlink/runtimeImage/CapturingHandler.java | 42 ++ .../runtimeImage/CustomModuleJlinkTest.java | 84 +++ .../runtimeImage/GenerateJLIClassesTest.java | 89 +++ .../jlink/runtimeImage/JImageHelper.java | 65 ++ .../runtimeImage/JavaSEReproducibleTest.java | 75 ++ .../KeepPackagedModulesFailTest.java | 92 +++ .../runtimeImage/ModifiedFilesExitTest.java | 85 +++ .../jlink/runtimeImage/ModifiedFilesTest.java | 72 ++ .../ModifiedFilesWarningTest.java | 75 ++ .../jlink/runtimeImage/MultiHopTest.java | 93 +++ ...PackagedModulesVsRuntimeImageLinkTest.java | 175 +++++ .../PatchedJDKModuleJlinkTest.java | 124 +++ .../jlink/runtimeImage/SystemModulesTest.java | 81 ++ .../runtimeImage/SystemModulesTest2.java | 76 ++ test/jdk/tools/lib/tests/Helper.java | 27 +- test/jdk/tools/lib/tests/JImageGenerator.java | 15 +- test/jtreg-ext/requires/VMProps.java | 16 + .../tools/javac/plugin/AutostartPlugins.java | 2 +- .../tools/javac/plugin/InternalAPI.java | 2 +- 47 files changed, 4169 insertions(+), 250 deletions(-) create mode 100644 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java create mode 100644 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/LinkableRuntimeImage.java create mode 100644 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JimageDiffGenerator.java create mode 100644 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java create mode 100644 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourcePoolReader.java create mode 100644 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java create mode 100644 test/jdk/tools/jlink/JLinkHelpCapabilityTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/AbstractLinkableRuntimeTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/AddOptionsTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/BasicJlinkMissingJavaBase.java create mode 100644 test/jdk/tools/jlink/runtimeImage/BasicJlinkTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/CapturingHandler.java create mode 100644 test/jdk/tools/jlink/runtimeImage/CustomModuleJlinkTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/GenerateJLIClassesTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/JImageHelper.java create mode 100644 test/jdk/tools/jlink/runtimeImage/JavaSEReproducibleTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/KeepPackagedModulesFailTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/ModifiedFilesExitTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/ModifiedFilesTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/ModifiedFilesWarningTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/MultiHopTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/PatchedJDKModuleJlinkTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/SystemModulesTest.java create mode 100644 test/jdk/tools/jlink/runtimeImage/SystemModulesTest2.java diff --git a/make/Images.gmk b/make/Images.gmk index 10fc8041325..acdc594b009 100644 --- a/make/Images.gmk +++ b/make/Images.gmk @@ -96,6 +96,10 @@ JLINK_DISABLE_WARNINGS := | ( $(GREP) -v -e "WARNING: Using incubator module" || JDK_IMAGE_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/images/jdk JRE_IMAGE_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/images/jre +ifeq ($(JLINK_PRODUCE_LINKABLE_RUNTIME), true) + JLINK_JDK_EXTRA_OPTS += --generate-linkable-runtime +endif + $(eval $(call SetupExecute, jlink_jdk, \ WARN := Creating jdk image, \ DEPS := $(JDK_JMODS) $(BASE_RELEASE_FILE) \ diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4 index c5c2290019b..cf8a856de96 100644 --- a/make/autoconf/jdk-options.m4 +++ b/make/autoconf/jdk-options.m4 @@ -586,13 +586,42 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_JMOD_OPTIONS], ################################################################################ # # jlink options. -# We always keep packaged modules in JDK image. # AC_DEFUN_ONCE([JDKOPT_SETUP_JLINK_OPTIONS], [ - UTIL_ARG_ENABLE(NAME: keep-packaged-modules, DEFAULT: true, + + ################################################################################ + # + # Configure option for building a JDK that is suitable for linking from the + # run-time image without JMODs. + # + # Determines whether or not a suitable run-time image is being produced from + # packaged modules. If set to 'true, changes the *default* of packaged + # modules to 'false'. + # + UTIL_ARG_ENABLE(NAME: linkable-runtime, DEFAULT: false, + RESULT: JLINK_PRODUCE_LINKABLE_RUNTIME, + DESC: [enable a JDK build suitable for linking from the run-time image], + CHECKING_MSG: [whether or not a JDK suitable for linking from the run-time image should be produced]) + AC_SUBST(JLINK_PRODUCE_LINKABLE_RUNTIME) + + if test "x$JLINK_PRODUCE_LINKABLE_RUNTIME" = xtrue; then + DEFAULT_PACKAGED_MODULES=false + else + DEFAULT_PACKAGED_MODULES=true + fi + + ################################################################################ + # + # Configure option for packaged modules + # + # We keep packaged modules in the JDK image unless --enable-linkable-runtime is + # requested. + # + UTIL_ARG_ENABLE(NAME: keep-packaged-modules, DEFAULT: $DEFAULT_PACKAGED_MODULES, RESULT: JLINK_KEEP_PACKAGED_MODULES, DESC: [enable keeping of packaged modules in jdk image], + DEFAULT_DESC: [enabled by default unless --enable-linkable-runtime is set], CHECKING_MSG: [if packaged modules are kept]) AC_SUBST(JLINK_KEEP_PACKAGED_MODULES) ]) diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template index 231355043d5..bcd54058c28 100644 --- a/make/autoconf/spec.gmk.template +++ b/make/autoconf/spec.gmk.template @@ -706,6 +706,7 @@ NEW_JAVADOC = $(INTERIM_LANGTOOLS_ARGS) $(JAVADOC_MAIN_CLASS) JMOD_COMPRESS := @JMOD_COMPRESS@ JLINK_KEEP_PACKAGED_MODULES := @JLINK_KEEP_PACKAGED_MODULES@ +JLINK_PRODUCE_LINKABLE_RUNTIME := @JLINK_PRODUCE_LINKABLE_RUNTIME@ RCFLAGS := @RCFLAGS@ diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java index 5664c195003..466c5d0a14c 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -24,29 +24,44 @@ */ package jdk.tools.jlink.internal; +import static jdk.tools.jlink.internal.LinkableRuntimeImage.DIFF_PATTERN; +import static jdk.tools.jlink.internal.LinkableRuntimeImage.RESPATH_PATTERN; + import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.tools.jlink.internal.Archive.Entry; import jdk.tools.jlink.internal.Archive.Entry.EntryType; +import jdk.tools.jlink.internal.JRTArchive.ResourceFileEntry; import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData; +import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator; +import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator.ImageResource; +import jdk.tools.jlink.internal.runtimelink.ResourceDiff; +import jdk.tools.jlink.internal.runtimelink.ResourcePoolReader; +import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException; import jdk.tools.jlink.plugin.PluginException; import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolBuilder; import jdk.tools.jlink.plugin.ResourcePoolEntry; +import jdk.tools.jlink.plugin.ResourcePoolModule; /** * An image (native endian.) @@ -68,38 +83,61 @@ * } */ public final class ImageFileCreator { + private static final byte[] EMPTY_RESOURCE_BYTES = new byte[] {}; + + private static final String JLINK_MOD_NAME = "jdk.jlink"; + private static final String RESPATH = "/" + JLINK_MOD_NAME + "/" + RESPATH_PATTERN; + private static final String DIFF_PATH = "/" + JLINK_MOD_NAME + "/" + DIFF_PATTERN; private final Map> entriesForModule = new HashMap<>(); private final ImagePluginStack plugins; - private ImageFileCreator(ImagePluginStack plugins) { - this.plugins = Objects.requireNonNull(plugins); - } - - public static ExecutableImage create(Set archives, - ImagePluginStack plugins) - throws IOException { - return ImageFileCreator.create(archives, ByteOrder.nativeOrder(), - plugins); - } + private final boolean generateRuntimeImage; + private final TaskHelper helper; - public static ExecutableImage create(Set archives, - ByteOrder byteOrder) - throws IOException { - return ImageFileCreator.create(archives, byteOrder, - new ImagePluginStack()); + private ImageFileCreator(ImagePluginStack plugins, + boolean generateRuntimeImage, + TaskHelper taskHelper) { + this.plugins = Objects.requireNonNull(plugins); + this.generateRuntimeImage = generateRuntimeImage; + this.helper = taskHelper; } + /** + * Create an executable image based on a set of input archives and a given + * plugin stack for a given byte order. It optionally generates a runtime + * that can be used for linking from the run-time image if + * {@code generateRuntimeImage} is set to {@code true}. + * + * @param archives The set of input archives + * @param byteOrder The desired byte order of the result + * @param plugins The plugin stack to apply to the input + * @param generateRuntimeImage if a runtime suitable for linking from the + * run-time image should get created. + * @return The executable image. + * @throws IOException + */ public static ExecutableImage create(Set archives, ByteOrder byteOrder, - ImagePluginStack plugins) + ImagePluginStack plugins, + boolean generateRuntimeImage, + TaskHelper taskHelper) throws IOException { - ImageFileCreator image = new ImageFileCreator(plugins); + ImageFileCreator image = new ImageFileCreator(plugins, + generateRuntimeImage, + taskHelper); try { image.readAllEntries(archives); // write to modular image image.writeImage(archives, byteOrder); + } catch (RuntimeImageLinkException e) { + // readAllEntries() might throw this exception. + // Propagate as IOException with appropriate message for + // jlink runs from the run-time image. This handles better + // error messages for the case of modified files in the run-time + // image. + throw image.newIOException(e); } finally { - //Close all archives + // Close all archives for (Archive a : archives) { a.close(); } @@ -125,7 +163,8 @@ private void readAllEntries(Set archives) { public static void recreateJimage(Path jimageFile, Set archives, - ImagePluginStack pluginSupport) + ImagePluginStack pluginSupport, + boolean generateRuntimeImage) throws IOException { try { Map> entriesForModule @@ -142,7 +181,7 @@ public static void recreateJimage(Path jimageFile, try (OutputStream fos = Files.newOutputStream(jimageFile); BufferedOutputStream bos = new BufferedOutputStream(fos); DataOutputStream out = new DataOutputStream(bos)) { - generateJImage(pool, writer, pluginSupport, out); + generateJImage(pool, writer, pluginSupport, out, generateRuntimeImage); } } finally { //Close all archives @@ -158,9 +197,14 @@ private void writeImage(Set archives, BasicImageWriter writer = new BasicImageWriter(byteOrder); ResourcePoolManager allContent = createPoolManager(archives, entriesForModule, byteOrder, writer); - ResourcePool result; + ResourcePool result = null; try (DataOutputStream out = plugins.getJImageFileOutputStream()) { - result = generateJImage(allContent, writer, plugins, out); + result = generateJImage(allContent, writer, plugins, out, generateRuntimeImage); + } catch (RuntimeImageLinkException e) { + // Propagate as IOException with appropriate message for + // jlink runs from the run-time image. This handles better + // error messages for the case of --patch-module. + throw newIOException(e); } //Handle files. @@ -174,14 +218,53 @@ private void writeImage(Set archives, } } + private IOException newIOException(RuntimeImageLinkException e) throws IOException { + if (JlinkTask.DEBUG) { + e.printStackTrace(); + } + String message = switch (e.getReason()) { + case PATCH_MODULE -> helper.getMessage("err.runtime.link.patched.module", e.getFile()); + case MODIFIED_FILE -> helper.getMessage("err.runtime.link.modified.file", e.getFile()); + default -> throw new AssertionError("Unexpected value: " + e.getReason()); + }; + throw new IOException(message); + } + + /** + * Create a jimage based on content of the given ResourcePoolManager, + * optionally creating a runtime that can be used for linking from the + * run-time image + * + * @param allContent The content that needs to get added to the resulting + * lib/modules (jimage) file. + * @param writer The writer for the jimage file. + * @param pluginSupport The stack of all plugins to apply. + * @param out The output stream to write the jimage to. + * @param generateRuntimeImage if a runtime suitable for linking from the + * run-time image should get created. + * @return A pool of the actual result resources. + * @throws IOException + */ private static ResourcePool generateJImage(ResourcePoolManager allContent, BasicImageWriter writer, ImagePluginStack pluginSupport, - DataOutputStream out + DataOutputStream out, + boolean generateRuntimeImage ) throws IOException { ResourcePool resultResources; try { resultResources = pluginSupport.visitResources(allContent); + if (generateRuntimeImage) { + // Keep track of non-modules resources for linking from a run-time image + resultResources = addNonClassResourcesTrackFiles(resultResources, + writer); + // Generate the diff between the input resources from packaged + // modules in 'allContent' to the plugin- or otherwise + // generated-content in 'resultResources' + resultResources = addResourceDiffFiles(allContent.resourcePool(), + resultResources, + writer); + } } catch (PluginException pe) { if (JlinkTask.DEBUG) { pe.printStackTrace(); @@ -198,7 +281,7 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, List content = new ArrayList<>(); List paths = new ArrayList<>(); - // the order of traversing the resources and the order of + // the order of traversing the resources and the order of // the module content being written must be the same resultResources.entries().forEach(res -> { if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) { @@ -248,11 +331,225 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, return resultResources; } + /** + * Support for creating a runtime suitable for linking from the run-time + * image. + * + * Generates differences between the packaged modules "view" in + * {@code jmodContent} to the optimized image in {@code resultContent} and + * adds the result to the returned resource pool. + * + * @param jmodContent The resource pool view of packaged modules + * @param resultContent The optimized result generated from the jmodContent + * input by applying the plugin stack. + * @param writer The image writer. + * @return The resource pool with the difference file resources added to + * the {@code resultContent} + */ + @SuppressWarnings("try") + private static ResourcePool addResourceDiffFiles(ResourcePool jmodContent, + ResourcePool resultContent, + BasicImageWriter writer) { + JimageDiffGenerator generator = new JimageDiffGenerator(); + List diff; + try (ImageResource jmods = new ResourcePoolReader(jmodContent); + ImageResource jimage = new ResourcePoolReader(resultContent)) { + diff = generator.generateDiff(jmods, jimage); + } catch (Exception e) { + throw new AssertionError("Failed to generate the runtime image diff", e); + } + Set modules = resultContent.moduleView().modules() + .map(a -> a.name()) + .collect(Collectors.toSet()); + // Add resource diffs for the resource files we are about to add + modules.stream().forEach(m -> { + String resourceName = String.format(DIFF_PATH, m); + ResourceDiff.Builder builder = new ResourceDiff.Builder(); + ResourceDiff d = builder.setKind(ResourceDiff.Kind.ADDED) + .setName(resourceName) + .build(); + diff.add(d); + }); + Map> perModDiffs = preparePerModuleDiffs(diff, + modules); + return addDiffResourcesFiles(modules, perModDiffs, resultContent, writer); + } + + private static Map> preparePerModuleDiffs(List resDiffs, + Set modules) { + Map> modToDiff = new HashMap<>(); + resDiffs.forEach(d -> { + int secondSlash = d.getName().indexOf("/", 1); + if (secondSlash == -1) { + throw new AssertionError("Module name not present"); + } + String module = d.getName().substring(1, secondSlash); + List perModDiff = modToDiff.computeIfAbsent(module, + a -> new ArrayList<>()); + perModDiff.add(d); + }); + Map> allModsToDiff = new HashMap<>(); + modules.stream().forEach(m -> { + List d = modToDiff.get(m); + if (d == null) { + // Not all modules will have a diff + allModsToDiff.put(m, Collections.emptyList()); + } else { + allModsToDiff.put(m, d); + } + }); + return allModsToDiff; + } + + private static ResourcePool addDiffResourcesFiles(Set modules, + Map> perModDiffs, + ResourcePool resultResources, + BasicImageWriter writer) { + ResourcePoolManager mgr = createPoolManager(resultResources, writer); + ResourcePoolBuilder out = mgr.resourcePoolBuilder(); + modules.stream().sorted().forEach(module -> { + String mResource = String.format(DIFF_PATH, module); + List diff = perModDiffs.get(module); + // Note that for modules without diff to the packaged modules view + // we create resource diff files with just the header and no content. + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try { + ResourceDiff.write(diff, bout); + } catch (IOException e) { + throw new AssertionError("Failed to write resource diff file" + + " for module " + module, e); + } + out.add(ResourcePoolEntry.create(mResource, bout.toByteArray())); + }); + return out.build(); + } + + /** + * Support for creating runtimes that can be used for linking from the + * run-time image. Adds meta-data files for resources not in the lib/modules + * file of the JDK. That is, mapping files for which on-disk files belong to + * which module. + * + * @param resultResources + * The original resources which serve as the basis for generating + * the meta-data files. + * @param writer + * The image writer. + * + * @return An amended resource pool which includes meta-data files. + */ + private static ResourcePool addNonClassResourcesTrackFiles(ResourcePool resultResources, + BasicImageWriter writer) { + // Only add resources if jdk.jlink module is present in the target image + Optional jdkJlink = resultResources.moduleView() + .findModule(JLINK_MOD_NAME); + if (jdkJlink.isPresent()) { + Map> nonClassResources = recordAndFilterEntries(resultResources); + return addModuleResourceEntries(resultResources, nonClassResources, writer); + } else { + return resultResources; // No-op + } + } + + /** + * Support for creating runtimes that can be used for linking from the + * run-time image. Adds the given mapping of files as a meta-data file to + * the given resource pool. + * + * @param resultResources + * The resource pool to add files to. + * @param nonClassResEntries + * The per module mapping for which to create the meta-data files + * for. + * @param writer + * The image writer. + * + * @return A resource pool with meta-data files added. + */ + private static ResourcePool addModuleResourceEntries(ResourcePool resultResources, + Map> nonClassResEntries, + BasicImageWriter writer) { + Set inputModules = resultResources.moduleView().modules() + .map(rm -> rm.name()) + .collect(Collectors.toSet()); + ResourcePoolManager mgr = createPoolManager(resultResources, writer); + ResourcePoolBuilder out = mgr.resourcePoolBuilder(); + inputModules.stream().sorted().forEach(module -> { + String mResource = String.format(RESPATH, module); + List mResources = nonClassResEntries.get(module); + if (mResources == null) { + // We create empty resource files for modules in the resource + // pool view that don't themselves contain native resources + // or config files. + out.add(ResourcePoolEntry.create(mResource, EMPTY_RESOURCE_BYTES)); + } else { + String mResContent = mResources.stream().sorted() + .collect(Collectors.joining("\n")); + out.add(ResourcePoolEntry.create(mResource, + mResContent.getBytes(StandardCharsets.UTF_8))); + } + }); + return out.build(); + } + + /** + * Support for creating runtimes that can be used for linking from the + * run-time image. Generates a per module mapping of files not part of the + * modules image (jimage). This mapping is needed so as to know which files + * of the installed JDK belong to which module. + * + * @param resultResources + * The resources from which the mapping gets generated + * @return A mapping with the module names as keys and the list of files not + * part of the modules image (jimage) as values. + */ + private static Map> recordAndFilterEntries(ResourcePool resultResources) { + Map> nonClassResEntries = new HashMap<>(); + Platform platform = getTargetPlatform(resultResources); + resultResources.entries().forEach(entry -> { + // Note that the fs_$module_files file is a resource file itself, so + // we cannot add fs_$module_files themselves due to the + // not(class_or_resources) condition. However, we also don't want + // to track 'release' file entries (not(top) condition) as those are + // handled by the release info plugin. + if (entry.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE && + entry.type() != ResourcePoolEntry.Type.TOP) { + List mRes = nonClassResEntries.computeIfAbsent(entry.moduleName(), + a -> new ArrayList<>()); + ResourceFileEntry rfEntry = ResourceFileEntry.toResourceFileEntry(entry, + platform); + mRes.add(rfEntry.encodeToString()); + } + }); + return nonClassResEntries; + } + + private static Platform getTargetPlatform(ResourcePool in) { + String platform = in.moduleView().findModule("java.base") + .map(ResourcePoolModule::targetPlatform) + .orElseThrow(() -> new AssertionError("java.base not found")); + return Platform.parsePlatform(platform); + } + private static ResourcePoolManager createPoolManager(Set archives, Map> entriesForModule, ByteOrder byteOrder, BasicImageWriter writer) throws IOException { - ResourcePoolManager resources = new ResourcePoolManager(byteOrder, new StringTable() { + ResourcePoolManager resources = createBasicResourcePoolManager(byteOrder, writer); + archives.stream() + .map(Archive::moduleName) + .sorted() + .flatMap(mn -> + entriesForModule.get(mn).stream() + .map(e -> new ArchiveEntryResourcePoolEntry(mn, + e.getResourcePoolEntryName(), e))) + .forEach(resources::add); + return resources; + } + + private static ResourcePoolManager createBasicResourcePoolManager(ByteOrder byteOrder, + BasicImageWriter writer) { + return new ResourcePoolManager(byteOrder, new StringTable() { @Override public int addString(String str) { @@ -264,14 +561,25 @@ public String getString(int id) { return writer.getString(id); } }); - archives.stream() - .map(Archive::moduleName) - .sorted() - .flatMap(mn -> - entriesForModule.get(mn).stream() - .map(e -> new ArchiveEntryResourcePoolEntry(mn, - e.getResourcePoolEntryName(), e))) - .forEach(resources::add); + } + + /** + * Creates a ResourcePoolManager from existing resources so that more + * resources can be appended. + * + * @param resultResources The existing resources to initially add. + * @param writer The basic image writer. + * @return An appendable ResourcePoolManager. + */ + private static ResourcePoolManager createPoolManager(ResourcePool resultResources, + BasicImageWriter writer) { + ResourcePoolManager resources = createBasicResourcePoolManager(resultResources.byteOrder(), + writer); + // Note that resources are already sorted in the correct order. + // The underlying ResourcePoolManager keeps track of entries via + // LinkedHashMap, which keeps values in insertion order. Therefore + // adding resources here, preserving that same order is OK. + resultResources.entries().forEach(resources::add); return resources; } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java new file mode 100644 index 00000000000..755afea8c60 --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.tools.jlink.internal; + +import static jdk.tools.jlink.internal.LinkableRuntimeImage.RESPATH_PATTERN; +import static jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException.Reason.MODIFIED_FILE; +import static jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException.Reason.PATCH_MODULE; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jdk.internal.util.OperatingSystem; +import jdk.tools.jlink.internal.Archive.Entry.EntryType; +import jdk.tools.jlink.internal.runtimelink.ResourceDiff; +import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException; +import jdk.tools.jlink.plugin.ResourcePoolEntry; +import jdk.tools.jlink.plugin.ResourcePoolEntry.Type; + +/** + * An archive implementation based on the JDK's run-time image. That is, classes + * and resources from the modules image (lib/modules, or jimage) and other + * associated files from the filesystem of the JDK installation. + */ +public class JRTArchive implements Archive { + + private final String module; + private final Path path; + private final ModuleReference ref; + // The collection of files of this module + private final List files = new ArrayList<>(); + // Files not part of the lib/modules image of the JDK install. + // Thus, native libraries, binaries, legal files, etc. + private final List otherRes; + // Maps a module resource path to the corresponding diff to packaged + // modules for that resource (if any) + private final Map resDiff; + private final boolean errorOnModifiedFile; + private final TaskHelper taskHelper; + + /** + * JRTArchive constructor + * + * @param module The module name this archive refers to + * @param path The JRT filesystem path. + * @param errorOnModifiedFile Whether or not modified files of the JDK + * install aborts the link. + * @param perModDiff The lib/modules (a.k.a jimage) diff for this module, + * possibly an empty list if there are no differences. + */ + JRTArchive(String module, + Path path, + boolean errorOnModifiedFile, + List perModDiff, + TaskHelper taskHelper) { + this.module = module; + this.path = path; + this.ref = ModuleFinder.ofSystem() + .find(module) + .orElseThrow(() -> + new IllegalArgumentException( + "Module " + module + + " not part of the JDK install")); + this.errorOnModifiedFile = errorOnModifiedFile; + this.otherRes = readModuleResourceFile(module); + this.resDiff = Objects.requireNonNull(perModDiff).stream() + .collect(Collectors.toMap(ResourceDiff::getName, Function.identity())); + this.taskHelper = taskHelper; + } + + @Override + public String moduleName() { + return module; + } + + @Override + public Path getPath() { + return path; + } + + @Override + public Stream entries() { + try { + collectFiles(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return files.stream().map(JRTFile::toEntry); + } + + @Override + public void open() throws IOException { + if (files.isEmpty()) { + collectFiles(); + } + } + + @Override + public void close() throws IOException { + if (!files.isEmpty()) { + files.clear(); + } + } + + @Override + public int hashCode() { + return Objects.hash(module, path); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof JRTArchive other && + Objects.equals(module, other.module) && + Objects.equals(path, other.path)); + } + + private void collectFiles() throws IOException { + if (files.isEmpty()) { + addNonClassResources(); + // Add classes/resources from the run-time image, + // patched with the run-time image diff + files.addAll(ref.open().list() + .filter(i -> { + String lookupKey = String.format("/%s/%s", module, i); + ResourceDiff rd = resDiff.get(lookupKey); + // Filter all resources with a resource diff + // that are of kind MODIFIED. + // Note that REMOVED won't happen since in + // that case the module listing won't have + // the resource anyway. + // Note as well that filter removes files + // of kind ADDED. Those files are not in + // the packaged modules, so ought not to + // get returned from the pipeline. + return (rd == null || + rd.getKind() == ResourceDiff.Kind.MODIFIED); + }) + .map(s -> { + String lookupKey = String.format("/%s/%s", module, s); + return new JRTArchiveFile(JRTArchive.this, s, + EntryType.CLASS_OR_RESOURCE, + null /* hashOrTarget */, + false /* symlink */, + resDiff.get(lookupKey)); + }) + .toList()); + // Finally add all files only present in the resource diff + // That is, removed items in the run-time image. + files.addAll(resDiff.values().stream() + .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED) + .map(s -> { + int secondSlash = s.getName().indexOf("/", 1); + assert secondSlash != -1; + String pathWithoutModule = s.getName().substring(secondSlash + 1); + return new JRTArchiveFile(JRTArchive.this, + pathWithoutModule, + EntryType.CLASS_OR_RESOURCE, + null /* hashOrTarget */, + false /* symlink */, + s); + }) + .toList()); + } + } + + /* + * no need to keep track of the warning produced since this is eagerly + * checked once. + */ + private void addNonClassResources() { + // Not all modules will have other resources like bin, lib, legal etc. + // files. In that case the list will be empty. + if (!otherRes.isEmpty()) { + files.addAll(otherRes.stream() + .filter(Predicate.not(String::isEmpty)) + .map(s -> { + ResourceFileEntry m = ResourceFileEntry.decodeFromString(s); + + // Read from the base JDK image. + Path path = BASE.resolve(m.resPath); + if (shaSumMismatch(path, m.hashOrTarget, m.symlink)) { + if (errorOnModifiedFile) { + throw new RuntimeImageLinkException(path.toString(), MODIFIED_FILE); + } else { + taskHelper.warning("err.runtime.link.modified.file", path.toString()); + } + } + + return new JRTArchiveFile(JRTArchive.this, + m.resPath, + toEntryType(m.resType), + m.hashOrTarget, + m.symlink, + /* diff only for resources */ + null); + }) + .toList()); + } + } + + static boolean shaSumMismatch(Path res, String expectedSha, boolean isSymlink) { + if (isSymlink) { + return false; + } + // handle non-symlink resources + try { + HexFormat format = HexFormat.of(); + byte[] expected = format.parseHex(expectedSha); + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + try (InputStream is = Files.newInputStream(res)) { + byte[] buf = new byte[1024]; + int readBytes = -1; + while ((readBytes = is.read(buf)) != -1) { + digest.update(buf, 0, readBytes); + } + } + byte[] actual = digest.digest(); + return !MessageDigest.isEqual(expected, actual); + } catch (Exception e) { + throw new AssertionError("SHA-512 sum check failed!", e); + } + } + + private static EntryType toEntryType(Type input) { + return switch(input) { + case CLASS_OR_RESOURCE -> EntryType.CLASS_OR_RESOURCE; + case CONFIG -> EntryType.CONFIG; + case HEADER_FILE -> EntryType.HEADER_FILE; + case LEGAL_NOTICE -> EntryType.LEGAL_NOTICE; + case MAN_PAGE -> EntryType.MAN_PAGE; + case NATIVE_CMD -> EntryType.NATIVE_CMD; + case NATIVE_LIB -> EntryType.NATIVE_LIB; + case TOP -> throw new IllegalArgumentException( + "TOP files should be handled by ReleaseInfoPlugin!"); + default -> throw new IllegalArgumentException("Unknown type: " + input); + }; + } + + public record ResourceFileEntry(Type resType, + boolean symlink, + String hashOrTarget, + String resPath) { + // Type file format: + // '|{0,1}||' + // (1) (2) (3) (4) + // + // Where fields are: + // + // (1) The resource type as specified by ResourcePoolEntry.type() + // (2) Symlink designator. 0 => regular resource, 1 => symlinked resource + // (3) The SHA-512 sum of the resources' content. The link to the target + // for symlinked resources. + // (4) The relative file path of the resource + private static final String TYPE_FILE_FORMAT = "%d|%d|%s|%s"; + + private static final Map typeMap = Arrays.stream(Type.values()) + .collect(Collectors.toMap(Type::ordinal, Function.identity())); + + public String encodeToString() { + return String.format(TYPE_FILE_FORMAT, + resType.ordinal(), + symlink ? 1 : 0, + hashOrTarget, + resPath); + } + + /** + * line: ||| + * + * Take the integer before '|' convert it to a Type. The second + * token is an integer representing symlinks (or not). The third token is + * a hash sum (sha512) of the file denoted by the fourth token (path). + */ + static ResourceFileEntry decodeFromString(String line) { + assert !line.isEmpty(); + + String[] tokens = line.split("\\|", 4); + Type type = null; + int symlinkNum = -1; + try { + Integer typeInt = Integer.valueOf(tokens[0]); + type = typeMap.get(typeInt); + if (type == null) { + throw new AssertionError("Illegal type ordinal: " + typeInt); + } + symlinkNum = Integer.valueOf(tokens[1]); + } catch (NumberFormatException e) { + throw new AssertionError(e); // must not happen + } + if (symlinkNum < 0 || symlinkNum > 1) { + throw new AssertionError( + "Symlink designator out of range [0,1] got: " + + symlinkNum); + } + return new ResourceFileEntry(type, + symlinkNum == 1, + tokens[2] /* hash or target */, + tokens[3] /* resource path */); + } + + public static ResourceFileEntry toResourceFileEntry(ResourcePoolEntry entry, + Platform platform) { + String resPathWithoutMod = dropModuleFromPath(entry, platform); + // Symlinks don't have a hash sum, but a link to the target instead + String hashOrTarget = entry.linkedTarget() == null + ? computeSha512(entry) + : dropModuleFromPath(entry.linkedTarget(), + platform); + return new ResourceFileEntry(entry.type(), + entry.linkedTarget() != null, + hashOrTarget, + resPathWithoutMod); + } + + private static String computeSha512(ResourcePoolEntry entry) { + try { + assert entry.linkedTarget() == null; + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + try (InputStream is = entry.content()) { + byte[] buf = new byte[1024]; + int bytesRead = -1; + while ((bytesRead = is.read(buf)) != -1) { + digest.update(buf, 0, bytesRead); + } + } + byte[] db = digest.digest(); + HexFormat format = HexFormat.of(); + return format.formatHex(db); + } catch (Exception e) { + throw new AssertionError("Failed to generate hash sum for " + + entry.path()); + } + } + + private static String dropModuleFromPath(ResourcePoolEntry entry, + Platform platform) { + String resPath = entry.path() + .substring( + // + 2 => prefixed and suffixed '/' + // For example: '/java.base/' + entry.moduleName().length() + 2); + if (!isWindows(platform)) { + return resPath; + } + // For Windows the libraries live in the 'bin' folder rather than + // the 'lib' folder in the final image. Note that going by the + // NATIVE_LIB type only is insufficient since only files with suffix + // .dll/diz/map/pdb are transplanted to 'bin'. + // See: DefaultImageBuilder.nativeDir() + return nativeDir(entry, resPath); + } + + private static boolean isWindows(Platform platform) { + return platform.os() == OperatingSystem.WINDOWS; + } + + private static String nativeDir(ResourcePoolEntry entry, String resPath) { + if (entry.type() != ResourcePoolEntry.Type.NATIVE_LIB) { + return resPath; + } + // precondition: Native lib, windows platform + if (resPath.endsWith(".dll") || resPath.endsWith(".diz") + || resPath.endsWith(".pdb") || resPath.endsWith(".map")) { + if (resPath.startsWith(LIB_DIRNAME + "/")) { + return BIN_DIRNAME + "/" + + resPath.substring((LIB_DIRNAME + "/").length()); + } + } + return resPath; + } + private static final String BIN_DIRNAME = "bin"; + private static final String LIB_DIRNAME = "lib"; + } + + private static final Path BASE = Paths.get(System.getProperty("java.home")); + + interface JRTFile { + Entry toEntry(); + } + + record JRTArchiveFile(Archive archive, + String resPath, + EntryType resType, + String sha, + boolean symlink, + ResourceDiff diff) implements JRTFile { + public Entry toEntry() { + return new Entry(archive, + String.format("/%s/%s", + archive.moduleName(), + resPath), + resPath, + resType) { + @Override + public long size() { + try { + if (resType != EntryType.CLASS_OR_RESOURCE) { + // Read from the base JDK image, special casing + // symlinks, which have the link target in the + // hashOrTarget field + if (symlink) { + return Files.size(BASE.resolve(sha)); + } + return Files.size(BASE.resolve(resPath)); + } else { + if (diff != null) { + // If the resource has a diff to the + // packaged modules, use the diff. Diffs of kind + // ADDED have been filtered out in collectFiles(); + assert diff.getKind() != ResourceDiff.Kind.ADDED; + assert diff.getName().equals(String.format("/%s/%s", + archive.moduleName(), + resPath)); + return diff.getResourceBytes().length; + } + // Read from the module image. This works, because + // the underlying base path is a JrtPath with the + // JrtFileSystem underneath which is able to handle + // this size query. + try { + return Files.size(archive.getPath().resolve(resPath)); + } catch (NoSuchFileException file) { + // This indicates that we don't find the class in the + // modules image using the JRT FS provider. Yet, we find + // the class using the system module finder. Therefore, + // we have a patched module. Mention that module patching + // is not supported. + throw new RuntimeImageLinkException(file.getFile(), PATCH_MODULE); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public InputStream stream() throws IOException { + if (resType != EntryType.CLASS_OR_RESOURCE) { + // Read from the base JDK image. + Path path = symlink ? BASE.resolve(sha) : BASE.resolve(resPath); + return Files.newInputStream(path); + } else { + // Read from the module image. Use the diff to the + // packaged modules if we have one. Diffs of kind + // ADDED have been filtered out in collectFiles(); + if (diff != null) { + assert diff.getKind() != ResourceDiff.Kind.ADDED; + assert diff.getName().equals(String.format("/%s/%s", + archive.moduleName(), + resPath)); + return new ByteArrayInputStream(diff.getResourceBytes()); + } + String module = archive.moduleName(); + ModuleReference mRef = ModuleFinder.ofSystem() + .find(module).orElseThrow(); + return mRef.open().open(resPath).orElseThrow(); + } + } + + }; + } + } + + private static List readModuleResourceFile(String modName) { + String resName = String.format(RESPATH_PATTERN, modName); + try { + try (InputStream inStream = JRTArchive.class.getModule() + .getResourceAsStream(resName)) { + String input = new String(inStream.readAllBytes(), StandardCharsets.UTF_8); + if (input.isEmpty()) { + // Not all modules have non-class resources + return Collections.emptyList(); + } else { + return Arrays.asList(input.split("\n")); + } + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to process resources from the " + + "run-time image for module " + modName, e); + } + } +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java index 1c5b0a3cd57..465a1cae8d9 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -26,7 +26,6 @@ import java.lang.module.Configuration; import java.lang.module.ModuleFinder; -import java.nio.ByteOrder; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -148,6 +147,9 @@ public static final class JlinkConfiguration { private final Path output; private final Set modules; private final ModuleFinder finder; + private final boolean linkFromRuntimeImage; + private final boolean ignoreModifiedRuntime; + private final boolean generateRuntimeImage; /** * jlink configuration, @@ -158,10 +160,16 @@ public static final class JlinkConfiguration { */ public JlinkConfiguration(Path output, Set modules, - ModuleFinder finder) { + ModuleFinder finder, + boolean linkFromRuntimeImage, + boolean ignoreModifiedRuntime, + boolean generateRuntimeImage) { this.output = output; this.modules = Objects.requireNonNull(modules); this.finder = finder; + this.linkFromRuntimeImage = linkFromRuntimeImage; + this.ignoreModifiedRuntime = ignoreModifiedRuntime; + this.generateRuntimeImage = generateRuntimeImage; } /** @@ -186,6 +194,18 @@ public ModuleFinder finder() { return finder; } + public boolean linkFromRuntimeImage() { + return linkFromRuntimeImage; + } + + public boolean ignoreModifiedRuntime() { + return ignoreModifiedRuntime; + } + + public boolean isGenerateRuntimeImage() { + return generateRuntimeImage; + } + /** * Returns a {@link Configuration} of the given module path, * root modules with full service binding. diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java index d9dbf1d0661..15998d6b929 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -24,6 +24,8 @@ */ package jdk.tools.jlink.internal; +import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE; + import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; @@ -39,14 +41,15 @@ import java.lang.module.ResolvedModule; import java.net.URI; import java.nio.ByteOrder; -import java.nio.file.Files; import java.nio.file.FileVisitResult; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; @@ -60,18 +63,18 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.internal.module.ModulePath; import jdk.internal.module.ModuleReferenceImpl; -import jdk.tools.jlink.internal.TaskHelper.BadArgs; -import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE; +import jdk.internal.module.ModuleResolution; +import jdk.internal.opt.CommandLine; +import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider; import jdk.tools.jlink.internal.Jlink.JlinkConfiguration; import jdk.tools.jlink.internal.Jlink.PluginsConfiguration; +import jdk.tools.jlink.internal.TaskHelper.BadArgs; import jdk.tools.jlink.internal.TaskHelper.Option; import jdk.tools.jlink.internal.TaskHelper.OptionsHelper; -import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider; +import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException; import jdk.tools.jlink.plugin.PluginException; -import jdk.internal.opt.CommandLine; -import jdk.internal.module.ModulePath; -import jdk.internal.module.ModuleResolution; /** * Implementation for the jlink tool. @@ -86,7 +89,6 @@ public class JlinkTask { private static final TaskHelper taskHelper = new TaskHelper(JLINK_BUNDLE); - private static final Option[] recognizedOptions = { new Option(false, (task, opt, arg) -> { task.options.help = true; @@ -182,7 +184,17 @@ public class JlinkTask { }, true, "--full-version"), new Option(false, (task, opt, arg) -> { task.options.ignoreSigning = true; - }, "--ignore-signing-information"),}; + }, "--ignore-signing-information"), + new Option(false, (task, opt, arg) -> { + task.options.ignoreModifiedRuntime = true; + }, true, "--ignore-modified-runtime"), + // option for generating a runtime that can then + // be used for linking from the run-time image. + new Option(false, (task, opt, arg) -> { + task.options.generateLinkableRuntime = true; + }, true, "--generate-linkable-runtime") + }; + private static final String PROGNAME = "jlink"; private final OptionsValues options = new OptionsValues(); @@ -222,6 +234,8 @@ static class OptionsValues { boolean ignoreSigning = false; boolean bindServices = false; boolean suggestProviders = false; + boolean ignoreModifiedRuntime = false; + boolean generateLinkableRuntime = false; } public static final String OPTIONS_RESOURCE = "jdk/tools/jlink/internal/options"; @@ -252,7 +266,7 @@ int run(String[] args) { .showUsage(true); } if (options.help) { - optionsHelper.showHelp(PROGNAME); + optionsHelper.showHelp(PROGNAME, LinkableRuntimeImage.isLinkableRuntime()); return EXIT_OK; } if (optionsHelper.shouldListPlugins()) { @@ -270,11 +284,6 @@ int run(String[] args) { if (jmods != null) { options.modulePath.add(jmods); } - - if (options.modulePath.isEmpty()) { - throw taskHelper.newBadArgs("err.modulepath.must.be.specified") - .showUsage(true); - } } JlinkConfiguration config = initJlinkConfig(); @@ -300,7 +309,7 @@ int run(String[] args) { } cleanupOutput(outputPath); return EXIT_ERROR; - } catch (IllegalArgumentException | ResolutionException e) { + } catch (IllegalArgumentException | ResolutionException | RuntimeImageLinkException e) { log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage()); if (DEBUG) { e.printStackTrace(log); @@ -356,6 +365,7 @@ public static void createImage(JlinkConfiguration config, false, null, false, + new OptionsValues(), null); // Then create the Plugin Stack @@ -370,7 +380,7 @@ public static void createImage(JlinkConfiguration config, private JlinkConfiguration initJlinkConfig() throws BadArgs { Set roots = new HashSet<>(); for (String mod : options.addMods) { - if (mod.equals(ALL_MODULE_PATH)) { + if (mod.equals(ALL_MODULE_PATH) && options.modulePath.size() > 0) { ModuleFinder finder = newModuleFinder(options.modulePath, options.limitMods, Set.of()); // all observable modules are roots finder.findAll() @@ -392,9 +402,75 @@ private JlinkConfiguration initJlinkConfig() throws BadArgs { finder = newModuleFinder(options.modulePath, options.limitMods, roots); } + boolean isLinkFromRuntime = options.modulePath.isEmpty(); + // In case of custom modules outside the JDK we may + // have a non-empty module path, which must not include + // java.base. If it did, we link using packaged modules from that + // module path. If the module path does not include java.base, we have + // the case where we link from the run-time image. In that case, we take + // the JDK modules from the run-time image (ModuleFinder.ofSystem()). + if (finder.find("java.base").isEmpty()) { + isLinkFromRuntime = true; + ModuleFinder runtimeImageFinder = ModuleFinder.ofSystem(); + finder = combinedFinders(runtimeImageFinder, finder, options.limitMods, roots); + } + + // --keep-packaged-modules doesn't make sense as we are not linking + // from packaged modules to begin with. + if (isLinkFromRuntime && options.packagedModulesPath != null) { + throw taskHelper.newBadArgs("err.runtime.link.packaged.mods"); + } + return new JlinkConfiguration(options.output, roots, - finder); + finder, + isLinkFromRuntime, + options.ignoreModifiedRuntime, + options.generateLinkableRuntime); + } + + /** + * Creates a combined module finder of {@code finder} and + * {@code runtimeImageFinder} that first looks-up modules in the + * {@code runtimeImageFinder} and if not present in {@code finder}. + * + * @param runtimeImageFinder A system modules finder. + * @param finder A module finder based on packaged modules. + * @param limitMods The set of limited modules for the resulting + * finder (if any). + * @param roots All module roots. + * + * @return A combined finder, or the input finder, potentially applying + * module limits. + */ + private ModuleFinder combinedFinders(ModuleFinder runtimeImageFinder, + ModuleFinder finder, + Set limitMods, + Set roots) { + ModuleFinder combined = new ModuleFinder() { + + @Override + public Optional find(String name) { + Optional mref = runtimeImageFinder.find(name); + if (mref.isEmpty()) { + return finder.find(name); + } + return mref; + } + + @Override + public Set findAll() { + Set all = new HashSet<>(); + all.addAll(runtimeImageFinder.findAll()); + all.addAll(finder.findAll()); + return Collections.unmodifiableSet(all); + } + }; + // if limitmods is specified then limit the universe + if (limitMods != null && !limitMods.isEmpty()) { + return limitFinder(combined, limitMods, Objects.requireNonNull(roots)); + } + return combined; } private void createImage(JlinkConfiguration config) throws Exception { @@ -413,6 +489,7 @@ private void createImage(JlinkConfiguration config) throws Exception { options.bindServices, options.endian, options.verbose, + options, log); // Then create the Plugin Stack @@ -433,10 +510,10 @@ public static Path getDefaultModulePath() { } /* - * Returns a module finder of the given module path that limits - * the observable modules to those in the transitive closure of - * the modules specified in {@code limitMods} plus other modules - * specified in the {@code roots} set. + * Returns a module finder of the given module path or the system modules + * if the module path is empty that limits the observable modules to those + * in the transitive closure of the modules specified in {@code limitMods} + * plus other modules specified in the {@code roots} set. * * @throws IllegalArgumentException if java.base module is present * but its descriptor has no version @@ -445,14 +522,10 @@ public static ModuleFinder newModuleFinder(List paths, Set limitMods, Set roots) { - if (Objects.requireNonNull(paths).isEmpty()) { - throw new IllegalArgumentException(taskHelper.getMessage("err.empty.module.path")); - } - - Path[] entries = paths.toArray(new Path[0]); Runtime.Version version = Runtime.version(); - ModuleFinder finder = ModulePath.of(version, true, entries); - + Path[] entries = paths.toArray(new Path[0]); + ModuleFinder finder = paths.isEmpty() ? ModuleFinder.ofSystem() + : ModulePath.of(version, true, entries); if (finder.find("java.base").isPresent()) { // use the version of java.base module, if present, as // the release version for multi-release JAR files @@ -505,8 +578,9 @@ public FileVisitResult postVisitDirectory(Path dir, IOException e) private static Path toPathLocation(ResolvedModule m) { Optional ouri = m.reference().location(); - if (ouri.isEmpty()) + if (ouri.isEmpty()) { throw new InternalError(m + " does not have a location"); + } URI uri = ouri.get(); return Paths.get(uri); } @@ -518,6 +592,7 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, boolean bindService, ByteOrder endian, boolean verbose, + OptionsValues opts, PrintWriter log) throws IOException { @@ -534,12 +609,35 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, taskHelper.getMessage("err.automatic.module", mref.descriptor().name(), loc)); }); + // Perform some sanity checks for linking from the run-time image + if (config.linkFromRuntimeImage()) { + if (!LinkableRuntimeImage.isLinkableRuntime()) { + String msg = taskHelper.getMessage("err.runtime.link.not.linkable.runtime"); + throw new IllegalArgumentException(msg); + } + // Do not permit linking from run-time image and also including jdk.jlink module + if (cf.findModule(JlinkTask.class.getModule().getName()).isPresent()) { + String msg = taskHelper.getMessage("err.runtime.link.jdk.jlink.prohibited"); + throw new IllegalArgumentException(msg); + } + + // Print info message indicating linking from the run-time image + if (verbose && log != null) { + log.println(taskHelper.getMessage("runtime.link.info")); + } + } + if (verbose && log != null) { // print modules to be linked in cf.modules().stream() .sorted(Comparator.comparing(ResolvedModule::name)) - .forEach(rm -> log.format("%s %s%n", - rm.name(), rm.reference().location().get())); + .forEach(rm -> log.format("%s %s%s%n", + rm.name(), + rm.reference().location().get(), + // We have a link from run-time image when scheme is 'jrt' + "jrt".equals(rm.reference().location().get().getScheme()) + ? " " + taskHelper.getMessage("runtime.link.jprt.path.extra") + : "")); // print provider info Set references = cf.modules().stream() @@ -559,14 +657,15 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, .map(ModuleDescriptor::name) .collect(Collectors.joining(", ")); - if (!"".equals(im)) + if (!"".equals(im)) { log.println("WARNING: Using incubator modules: " + im); + } } Map mods = cf.modules().stream() .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation)); // determine the target platform of the image being created - Platform targetPlatform = targetPlatform(cf, mods); + Platform targetPlatform = targetPlatform(cf, mods, config.linkFromRuntimeImage()); // if the user specified any --endian, then it must match the target platform's native // endianness if (endian != null && endian != targetPlatform.arch().byteOrder()) { @@ -580,7 +679,92 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, targetPlatform.arch().byteOrder(), targetPlatform); } } - return new ImageHelper(cf, mods, targetPlatform, retainModulesPath, ignoreSigning); + + // use the version of java.base module, if present, as + // the release version for multi-release JAR files + var version = cf.findModule("java.base") + .map(ResolvedModule::reference) + .map(ModuleReference::descriptor) + .flatMap(ModuleDescriptor::version) + .map(ModuleDescriptor.Version::toString) + .map(Runtime.Version::parse) + .orElse(Runtime.version()); + + Set archives = mods.entrySet().stream() + .map(e -> newArchive(e.getKey(), + e.getValue(), + version, + ignoreSigning, + config, + log)) + .collect(Collectors.toSet()); + + return new ImageHelper(archives, + targetPlatform, + retainModulesPath, + config.isGenerateRuntimeImage()); + } + + private static Archive newArchive(String module, + Path path, + Runtime.Version version, + boolean ignoreSigning, + JlinkConfiguration config, + PrintWriter log) { + if (path.toString().endsWith(".jmod")) { + return new JmodArchive(module, path); + } else if (path.toString().endsWith(".jar")) { + ModularJarArchive modularJarArchive = new ModularJarArchive(module, path, version); + try (Stream entries = modularJarArchive.entries()) { + boolean hasSignatures = entries.anyMatch((entry) -> { + String name = entry.name().toUpperCase(Locale.ROOT); + + return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && ( + name.endsWith(".SF") || + name.endsWith(".DSA") || + name.endsWith(".RSA") || + name.endsWith(".EC") || + name.startsWith("META-INF/SIG-") + ); + }); + + if (hasSignatures) { + if (ignoreSigning) { + System.err.println(taskHelper.getMessage("warn.signing", path)); + } else { + throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path)); + } + } + } + return modularJarArchive; + } else if (Files.isDirectory(path) && !"jrt".equals(path.toUri().getScheme())) { + // The jrt URI path scheme conditional is there since we'd otherwise + // enter this branch for linking from the run-time image where the + // path is a jrt path. Note that the specific module would be a + // directory. I.e. Files.isDirectory() would be true. + Path modInfoPath = path.resolve("module-info.class"); + if (Files.isRegularFile(modInfoPath)) { + return new DirArchive(path, findModuleName(modInfoPath)); + } else { + throw new IllegalArgumentException( + taskHelper.getMessage("err.not.a.module.directory", path)); + } + } else if (config.linkFromRuntimeImage()) { + return LinkableRuntimeImage.newArchive(module, path, config.ignoreModifiedRuntime(), taskHelper); + } else { + throw new IllegalArgumentException( + taskHelper.getMessage("err.not.modular.format", module, path)); + } + } + + private static String findModuleName(Path modInfoPath) { + try (BufferedInputStream bis = new BufferedInputStream( + Files.newInputStream(modInfoPath))) { + return ModuleDescriptor.read(bis).name(); + } catch (IOException exp) { + throw new IllegalArgumentException(taskHelper.getMessage( + "err.cannot.read.module.info", modInfoPath), exp); + } } /* @@ -626,10 +810,12 @@ public Set findAll() { }; } - private static Platform targetPlatform(Configuration cf, Map modsPaths) throws IOException { + private static Platform targetPlatform(Configuration cf, + Map modsPaths, + boolean runtimeImageLink) throws IOException { Path javaBasePath = modsPaths.get("java.base"); assert javaBasePath != null : "java.base module path is missing"; - if (isJavaBaseFromDefaultModulePath(javaBasePath)) { + if (runtimeImageLink || isJavaBaseFromDefaultModulePath(javaBasePath)) { // this implies that the java.base module used for the target image // will correspond to the current platform. So this isn't an attempt to // build a cross-platform image. We use the current platform's endianness @@ -720,8 +906,9 @@ private static void printProviders(PrintWriter log, String header, Set modules, Map> serviceToUses) { - if (modules.isEmpty()) + if (modules.isEmpty()) { return; + } // Build a map of a service type to the provider modules Map> providers = new HashMap<>(); @@ -845,95 +1032,14 @@ private String getSaveOpts() { return sb.toString(); } - private static class ImageHelper implements ImageProvider { - final Platform targetPlatform; - final Path packagedModulesPath; - final boolean ignoreSigning; - final Runtime.Version version; - final Set archives; - - ImageHelper(Configuration cf, - Map modsPaths, - Platform targetPlatform, - Path packagedModulesPath, - boolean ignoreSigning) throws IOException { - Objects.requireNonNull(targetPlatform); - this.targetPlatform = targetPlatform; - this.packagedModulesPath = packagedModulesPath; - this.ignoreSigning = ignoreSigning; - - // use the version of java.base module, if present, as - // the release version for multi-release JAR files - this.version = cf.findModule("java.base") - .map(ResolvedModule::reference) - .map(ModuleReference::descriptor) - .flatMap(ModuleDescriptor::version) - .map(ModuleDescriptor.Version::toString) - .map(Runtime.Version::parse) - .orElse(Runtime.version()); - - this.archives = modsPaths.entrySet().stream() - .map(e -> newArchive(e.getKey(), e.getValue())) - .collect(Collectors.toSet()); - } - - private Archive newArchive(String module, Path path) { - if (path.toString().endsWith(".jmod")) { - return new JmodArchive(module, path); - } else if (path.toString().endsWith(".jar")) { - ModularJarArchive modularJarArchive = new ModularJarArchive(module, path, version); - - try (Stream entries = modularJarArchive.entries()) { - boolean hasSignatures = entries.anyMatch((entry) -> { - String name = entry.name().toUpperCase(Locale.ROOT); - - return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && ( - name.endsWith(".SF") || - name.endsWith(".DSA") || - name.endsWith(".RSA") || - name.endsWith(".EC") || - name.startsWith("META-INF/SIG-") - ); - }); - - if (hasSignatures) { - if (ignoreSigning) { - System.err.println(taskHelper.getMessage("warn.signing", path)); - } else { - throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path)); - } - } - } - - return modularJarArchive; - } else if (Files.isDirectory(path)) { - Path modInfoPath = path.resolve("module-info.class"); - if (Files.isRegularFile(modInfoPath)) { - return new DirArchive(path, findModuleName(modInfoPath)); - } else { - throw new IllegalArgumentException( - taskHelper.getMessage("err.not.a.module.directory", path)); - } - } else { - throw new IllegalArgumentException( - taskHelper.getMessage("err.not.modular.format", module, path)); - } - } - - private static String findModuleName(Path modInfoPath) { - try (BufferedInputStream bis = new BufferedInputStream( - Files.newInputStream(modInfoPath))) { - return ModuleDescriptor.read(bis).name(); - } catch (IOException exp) { - throw new IllegalArgumentException(taskHelper.getMessage( - "err.cannot.read.module.info", modInfoPath), exp); - } - } - + private static record ImageHelper(Set archives, + Platform targetPlatform, + Path packagedModulesPath, + boolean generateRuntimeImage) implements ImageProvider { @Override public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { ExecutableImage image = ImageFileCreator.create(archives, - targetPlatform.arch().byteOrder(), stack); + targetPlatform.arch().byteOrder(), stack, generateRuntimeImage, taskHelper); if (packagedModulesPath != null) { // copy the packaged modules to the given path Files.createDirectories(packagedModulesPath); diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/LinkableRuntimeImage.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/LinkableRuntimeImage.java new file mode 100644 index 00000000000..935af4585ad --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/LinkableRuntimeImage.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.tools.jlink.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; + +import jdk.tools.jlink.internal.runtimelink.ResourceDiff; + +/** + * Class that supports the feature of running jlink based on the current + * run-time image. + */ +public class LinkableRuntimeImage { + + // meta-data files per module for supporting linking from the run-time image + public static final String RESPATH_PATTERN = "jdk/tools/jlink/internal/runtimelink/fs_%s_files"; + // The diff files per module for supporting linking from the run-time image + public static final String DIFF_PATTERN = "jdk/tools/jlink/internal/runtimelink/diff_%s"; + + /** + * In order to be able to show whether or not a runtime is capable of + * linking from it in {@code jlink --help} we need to look for the delta + * files in the {@code jdk.jlink} module. If present we have the capability. + * + * @return {@code true} iff this jlink is capable of linking from the + * run-time image. + */ + public static boolean isLinkableRuntime() { + try (InputStream in = getDiffInputStream("java.base")) { + return in != null; + } catch (IOException e) { + // fall-through + } + return false; + } + + private static InputStream getDiffInputStream(String module) throws IOException { + String resourceName = String.format(DIFF_PATTERN, module); + return LinkableRuntimeImage.class.getModule().getResourceAsStream(resourceName); + } + + public static Archive newArchive(String module, + Path path, + boolean ignoreModifiedRuntime, + TaskHelper taskHelper) { + assert isLinkableRuntime(); + // Here we retrieve the per module difference file, which is + // potentially empty, from the modules image and pass that on to + // JRTArchive for further processing. When streaming resources from + // the archive, the diff is being applied. + List perModuleDiff = null; + try (InputStream in = getDiffInputStream(module)){ + perModuleDiff = ResourceDiff.read(in); + } catch (IOException e) { + throw new AssertionError("Failure to retrieve resource diff for " + + "module " + module, e); + } + return new JRTArchive(module, path, !ignoreModifiedRuntime, perModuleDiff, taskHelper); + } + + +} 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 2b4e6ca0a97..23b3dfb7079 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -28,23 +28,21 @@ import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.Map; -import java.util.HashMap; -import java.util.Map.Entry; -import java.util.Set; -import java.util.HashSet; -import java.util.List; import java.util.ArrayList; import java.util.Arrays; -import java.util.stream.Stream; import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Locale; -import java.util.ResourceBundle; +import java.util.Map; +import java.util.Map.Entry; import java.util.MissingResourceException; -import java.util.Comparator; - +import java.util.ResourceBundle; +import java.util.Set; +import java.util.stream.Stream; import jdk.tools.jlink.builder.DefaultImageBuilder; import jdk.tools.jlink.builder.ImageBuilder; @@ -55,7 +53,6 @@ import jdk.tools.jlink.internal.plugins.PluginsResourceBundle; import jdk.tools.jlink.plugin.Plugin; import jdk.tools.jlink.plugin.Plugin.Category; -import jdk.tools.jlink.plugin.PluginException; /** * @@ -584,7 +581,7 @@ private Option getOption(String name) { return null; } - public void showHelp(String progName) { + public void showHelp(String progName, boolean linkableRuntimeEnabled) { log.println(bundleHelper.getMessage("main.usage", progName)); Stream.concat(options.stream(), pluginOptions.mainOptions.stream()) .filter(option -> !option.isHidden()) @@ -594,6 +591,17 @@ public void showHelp(String progName) { }); log.println(bundleHelper.getMessage("main.command.files")); + // If the JDK build has the run-time image capability show it + // in the help output in human readable form. + String qualifier = null; + if (linkableRuntimeEnabled) { + qualifier = bundleHelper.getMessage("main.runtime.image.linking.cap.enabled"); + } else { + qualifier = bundleHelper.getMessage("main.runtime.image.linking.cap.disabled"); + } + log.println(bundleHelper.getMessage("main.runtime.image.linking.cap.sect.header")); + log.println(bundleHelper.getMessage("main.runtime.image.linking.cap.msg", + qualifier)); } public void listPlugins() { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JimageDiffGenerator.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JimageDiffGenerator.java new file mode 100644 index 00000000000..5e540be7ced --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JimageDiffGenerator.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ +package jdk.tools.jlink.internal.runtimelink; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Generates a delta between packaged modules (as an ImageResource) and an + * optimized jimage (lib/modules) as an ImageResource. The result can be + * serialized to a file using {@link ResourceDiff}. + */ +public class JimageDiffGenerator { + + /** + * A resource used for linking. Either packaged modules or + * packaged modules transformed to an optimized run-time image by applying + * the jlink plug-in pipeline. The canonical source, the packaged modules, + * are being used to devise the delta to the transformed run-time image. The + * delta can can then be used for jlink input together *with* a prepared + * run-time image. + */ + @SuppressWarnings("try") + public interface ImageResource extends AutoCloseable { + public List getEntries(); + public byte[] getResourceBytes(String name); + } + + /** + * Produce a difference between packaged modules' resources (base) and the + * result of all plug-ins being applied on those resources (image). + * + * @param base + * The ImageResource view of unmodified resources coming from + * packaged modules. + * @param image + * The ImageResource view of the jlink plug-in pipeline having + * been applied to the resources in base. + * @return The list of resource differences across all modules. + */ + public List generateDiff(ImageResource base, ImageResource image) throws Exception { + List baseResources; + Set resources = new HashSet<>(); + List diffs = new ArrayList<>(); + try (base; image) { + resources.addAll(image.getEntries()); + baseResources = base.getEntries(); + for (String item: baseResources) { + byte[] baseBytes = base.getResourceBytes(item); + // First check that every item in the base image exist in + // the optimized image as well. If it does not, it's a removed + // item in the optimized image. + if (!resources.remove(item)) { + // keep track of original bytes for removed item in the + // optimized image, since we need to restore them for the + // runtime image link + ResourceDiff.Builder builder = new ResourceDiff.Builder(); + ResourceDiff diff = builder.setKind(ResourceDiff.Kind.REMOVED) + .setName(item) + .setResourceBytes(baseBytes) + .build(); + diffs.add(diff); + continue; + } + // Verify resource bytes are equal if present in both images + boolean contentEquals = Arrays.equals(baseBytes, image.getResourceBytes(item)); + if (!contentEquals) { + // keep track of original bytes (non-optimized) + ResourceDiff.Builder builder = new ResourceDiff.Builder(); + ResourceDiff diff = builder.setKind(ResourceDiff.Kind.MODIFIED) + .setName(item) + .setResourceBytes(baseBytes) + .build(); + diffs.add(diff); + } + } + } + // What's now left in the set are the resources only present in the + // optimized image (generated by some plugins; not present in jmods) + for (String e: resources) { + ResourceDiff.Builder builder = new ResourceDiff.Builder(); + ResourceDiff diff = builder.setKind(ResourceDiff.Kind.ADDED) + .setName(e) + .build(); + diffs.add(diff); + } + return diffs; + } + +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java new file mode 100644 index 00000000000..a007350c16f --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.tools.jlink.internal.runtimelink; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Class representing a difference of a jimage resource. For all intents + * and purposes this represents a difference between a resource in an optimized + * jimage (e.g. images/jdk/lib/modules) and the underlying basic resources from + * which the optimized image got derived from (e.g. packaged modules). The + * differences are being used in JRTArchive so as to back-track from an optimized + * jimage to the original (i.e. it restores original resources using the diff). + */ +public class ResourceDiff implements Comparable { + + private static final int MAGIC = 0xabba; + + public static enum Kind { + ADDED((short)1), // Resource added + REMOVED((short)2), // Resource removed + MODIFIED((short)3); // Resource modified + + private short value; + + private Kind(short value) { + this.value = value; + } + + public short value() { + return value; + } + + static Kind fromShort(short v) { + if (v > 3 || v < 1) { + throw new IllegalArgumentException("Must be within range [1-3]"); + } + switch (v) { + case 1: return ADDED; + case 2: return REMOVED; + case 3: return MODIFIED; + } + throw new AssertionError("Must not reach here!"); + } + } + + private final Kind kind; + private final byte[] resourceBytes; + private final String name; + + private ResourceDiff(Kind kind, String name, byte[] resourceBytes) { + this.kind = kind; + this.name = name; + if ((kind == Kind.REMOVED || kind == Kind.MODIFIED) && + resourceBytes == null) { + throw new AssertionError("Resource bytes must be set for REMOVED or MODIFIED"); + } + this.resourceBytes = resourceBytes; + } + + public Kind getKind() { + return kind; + } + + public byte[] getResourceBytes() { + return resourceBytes; + } + + public String getName() { + return name; + } + + @Override + public int compareTo(ResourceDiff o) { + int kindComp = kind.value() - o.kind.value(); + if (kindComp == 0) { + return getName().compareTo(o.getName()); + } else { + return kindComp; + } + } + + public static class Builder { + private Kind kind; + private String name; + private byte[] resourceBytes; + + public Builder setKind(Kind kind) { + this.kind = kind; + return this; + } + public Builder setName(String name) { + this.name = Objects.requireNonNull(name); + return this; + } + public Builder setResourceBytes(byte[] resourceBytes) { + this.resourceBytes = Objects.requireNonNull(resourceBytes); + return this; + } + public ResourceDiff build() { + if (kind == null || name == null) { + throw new AssertionError("kind and name must be set"); + } + switch (kind) { + case ADDED: + { + break; // null bytes for added is OK. + } + case MODIFIED: // fall-through + case REMOVED: + { + if (resourceBytes == null) { + throw new AssertionError("Original bytes needed for MODIFIED, REMOVED!"); + } + break; + } + default: + break; + } + return new ResourceDiff(kind, name, resourceBytes); + } + } + + /** + * Writes a list of resource diffs to an output stream + * + * @param diffs The list of resource diffs to write. + * @param out The stream to write the serialized bytes to. + */ + public static void write(List diffs, OutputStream out) throws IOException { + /* + * Simple binary format: + * + *

| + * + * **************************************** + * HEADER info + * **************************************** + * + * where
is ('|' separation for clarity): + * + * | + * + * The first integer is the MAGIC, 0xabba. The second integer is the + * total number of items. + * + * ***************************************** + * ITEMS info + * ***************************************** + * + * Each consists of ('|' separation for clarity): + * + * |||| + * + * Where the individual items are: + * + * : + * The value of the respective ResourceDiff.Kind. + * : + * The length of the name bytes (in UTF-8). + * : + * The resource name bytes in UTF-8. + * : + * The length of the resource bytes. 0 (zero) if no resource bytes. + * A.k.a 'null'. + * : + * The bytes of the resource as stored in the jmod files. + */ + try (DataOutputStream dataOut = new DataOutputStream(out)) { + dataOut.writeInt(MAGIC); + dataOut.writeInt(diffs.size()); + for (ResourceDiff d: diffs) { + dataOut.writeShort(d.kind.value()); + byte[] buf = d.name.getBytes(StandardCharsets.UTF_8); + dataOut.writeInt(buf.length); + dataOut.write(buf); + buf = d.resourceBytes; + dataOut.writeInt(buf == null ? 0 : buf.length); + if (buf != null) { + dataOut.write(buf); + } + } + } + } + + /** + * Read a list of resource diffs from an input stream. + * + * @param in The input stream to read from + * @return The list of resource diffs. + */ + public static List read(InputStream in) throws IOException { + /* + * See write() for the details how this is being written + */ + List diffs = new ArrayList<>(); + try (DataInputStream din = new DataInputStream(in)) { + int magic = din.readInt(); + if (magic != MAGIC) { + throw new IllegalArgumentException("Not a ResourceDiff data stream!"); + } + int numItems = din.readInt(); + for (int i = 0; i < numItems; i++) { + Kind k = Kind.fromShort(din.readShort()); + int numBytes = din.readInt(); + byte[] buf = readBytesFromStream(din, numBytes); + String name = new String(buf, StandardCharsets.UTF_8); + numBytes = din.readInt(); + byte[] resBytes = null; + if (numBytes != 0) { + resBytes = readBytesFromStream(din, numBytes); + } + Builder builder = new Builder(); + builder.setKind(k) + .setName(name); + if (resBytes != null) { + builder.setResourceBytes(resBytes); + } + diffs.add(builder.build()); + } + } + return Collections.unmodifiableList(diffs); + } + + private static byte[] readBytesFromStream(DataInputStream din, int numBytes) throws IOException { + byte[] b = new byte[numBytes]; + for (int i = 0; i < numBytes; i++) { + int data = din.read(); + if (data == -1) { + throw new IOException("Short read!"); + } + b[i] = (byte)data; + } + return b; + } + + public static void printDiffs(List diffs) { + for (ResourceDiff diff: diffs.stream().sorted().toList()) { + switch (diff.getKind()) { + case ADDED: + System.out.println("Only added in opt: " + diff.getName()); + break; + case MODIFIED: + System.out.println("Modified in opt: " + diff.getName()); + break; + case REMOVED: + System.out.println("Removed in opt: " + diff.getName()); + break; + default: + break; + } + } + } + +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourcePoolReader.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourcePoolReader.java new file mode 100644 index 00000000000..12e8708477c --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourcePoolReader.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +package jdk.tools.jlink.internal.runtimelink; + +import java.util.List; +import java.util.Objects; + +import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator.ImageResource; +import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolEntry; + +@SuppressWarnings("try") +public class ResourcePoolReader implements ImageResource { + + private final ResourcePool pool; + + public ResourcePoolReader(ResourcePool pool) { + this.pool = Objects.requireNonNull(pool); + } + + @Override + public void close() throws Exception { + // nothing + } + + @Override + public List getEntries() { + return pool.entries().map(ResourcePoolEntry::path).toList(); + } + + @Override + public byte[] getResourceBytes(String name) { + return pool.findEntry(name).orElseThrow().contentBytes(); + } + +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java new file mode 100644 index 00000000000..9f54fd63476 --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.tools.jlink.internal.runtimelink; + +import java.util.Objects; + +/** + * Exception thrown when linking from the run-time image + */ +public class RuntimeImageLinkException extends RuntimeException { + + private static final long serialVersionUID = -1848914673073119403L; + + public static enum Reason { + PATCH_MODULE, /* link exception due to patched module */ + MODIFIED_FILE, /* link exception due to modified file */ + } + + private final String file; + private final Reason reason; + + public RuntimeImageLinkException(String file, Reason reason) { + this.file = Objects.requireNonNull(file); + this.reason = Objects.requireNonNull(reason); + } + + public String getFile() { + return file; + } + + public Reason getReason() { + return reason; + } + + @Override + public String getMessage() { + return reason + ", file: " + file; + } +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties index e09eadbb3d0..9e18177d9c8 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2023, 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 @@ -111,10 +111,22 @@ main.extended.help.footer=\ \ used, one pattern per line\n\ \n\ +main.runtime.image.linking.cap.enabled=enabled +main.runtime.image.linking.cap.disabled=disabled +main.runtime.image.linking.cap.sect.header=Capabilities: +main.runtime.image.linking.cap.msg=\ Linking from run-time image {0} error.prefix=Error: warn.prefix=Warning: +err.runtime.link.not.linkable.runtime=This JDK does not support linking from the current run-time image +err.runtime.link.jdk.jlink.prohibited=This JDK does not contain packaged modules\ +\ and cannot be used to create another image with the jdk.jlink module +err.runtime.link.packaged.mods=This JDK has no packaged modules.\ +\ --keep-packaged-modules is not supported +err.runtime.link.modified.file={0} has been modified +err.runtime.link.patched.module=File {0} not found in the modules image.\ +\ --patch-module is not supported when linking from the run-time image err.empty.module.path=empty module path err.jlink.version.mismatch=jlink version {0}.{1} does not match target java.base version {2}.{3} err.automatic.module:automatic module cannot be used with jlink: {0} from {1} @@ -123,7 +135,7 @@ err.launcher.main.class.empty:launcher main class name cannot be empty: {0} err.launcher.module.name.empty:launcher module name cannot be empty: {0} err.launcher.value.format:launcher value should be of form =[/]: {0} err.output.must.be.specified:--output must be specified -err.modulepath.must.be.specified:--module-path is not specified and this runtime image does not contain jmods directory. +err.modulepath.must.be.specified:--module-path is not specified and this run-time image does not contain a jmods directory err.mods.must.be.specified:no modules specified to {0} err.path.not.found=path not found: {0} err.path.not.valid=invalid path: {0} @@ -157,3 +169,6 @@ warn.provider.notfound=No provider found for service specified to --suggest-prov no.suggested.providers=--bind-services option is specified. No additional providers suggested. suggested.providers.header=Suggested providers providers.header=Providers + +runtime.link.info=Linking based on the current run-time image +runtime.link.jprt.path.extra=(run-time image) diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index 21c5aebaa71..af97ff465de 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -86,7 +86,9 @@ requires.properties= \ vm.flagless \ container.support \ systemd.support \ - jdk.containerized + jdk.containerized \ + jlink.runtime.linkable \ + jlink.packagedModules # Minimum jtreg version requiredVersion=7.4+1 diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 6276932afbd..7b6276c7aa0 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -102,7 +102,9 @@ requires.properties= \ systemd.support \ release.implementor \ jdk.containerized \ - jdk.foreign.linker + jdk.foreign.linker \ + jlink.runtime.linkable \ + jlink.packagedModules # Minimum jtreg version requiredVersion=7.4+1 diff --git a/test/jdk/jdk/modules/etc/JmodExcludedFiles.java b/test/jdk/jdk/modules/etc/JmodExcludedFiles.java index 6c338a25b4f..90ca6840d52 100644 --- a/test/jdk/jdk/modules/etc/JmodExcludedFiles.java +++ b/test/jdk/jdk/modules/etc/JmodExcludedFiles.java @@ -25,6 +25,7 @@ * @test * @bug 8159927 * @modules java.base/jdk.internal.util + * @requires jlink.packagedModules * @run main JmodExcludedFiles * @summary Test that JDK JMOD files do not include native debug symbols */ diff --git a/test/jdk/tools/jlink/ImageFileCreatorTest.java b/test/jdk/tools/jlink/ImageFileCreatorTest.java index 84f77340f42..b6466c6a4d9 100644 --- a/test/jdk/tools/jlink/ImageFileCreatorTest.java +++ b/test/jdk/tools/jlink/ImageFileCreatorTest.java @@ -34,11 +34,12 @@ import java.util.List; import java.util.Set; import java.util.stream.Stream; + +import jdk.tools.jlink.builder.ImageBuilder; import jdk.tools.jlink.internal.Archive; +import jdk.tools.jlink.internal.ExecutableImage; import jdk.tools.jlink.internal.ImageFileCreator; import jdk.tools.jlink.internal.ImagePluginStack; -import jdk.tools.jlink.internal.ExecutableImage; -import jdk.tools.jlink.builder.ImageBuilder; import jdk.tools.jlink.plugin.ResourcePool; @@ -223,6 +224,6 @@ public void storeFiles(ResourcePool content) { ImagePluginStack stack = new ImagePluginStack(noopBuilder, Collections.emptyList(), null, false); - ImageFileCreator.create(archives, ByteOrder.nativeOrder(), stack); + ImageFileCreator.create(archives, ByteOrder.nativeOrder(), stack, false, null); } } diff --git a/test/jdk/tools/jlink/IntegrationTest.java b/test/jdk/tools/jlink/IntegrationTest.java index e85d8f0d984..686dd194ada 100644 --- a/test/jdk/tools/jlink/IntegrationTest.java +++ b/test/jdk/tools/jlink/IntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -25,7 +25,6 @@ import java.io.FileReader; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -38,20 +37,18 @@ import java.util.Properties; import java.util.Set; import java.util.function.Function; -import jdk.tools.jlink.internal.Jlink; -import jdk.tools.jlink.internal.JlinkTask; + import jdk.tools.jlink.builder.DefaultImageBuilder; -import jdk.tools.jlink.internal.Platform; -import jdk.tools.jlink.plugin.ResourcePool; -import jdk.tools.jlink.plugin.ResourcePoolBuilder; -import jdk.tools.jlink.plugin.Plugin; import jdk.tools.jlink.internal.ExecutableImage; +import jdk.tools.jlink.internal.Jlink; import jdk.tools.jlink.internal.Jlink.JlinkConfiguration; import jdk.tools.jlink.internal.Jlink.PluginsConfiguration; +import jdk.tools.jlink.internal.JlinkTask; +import jdk.tools.jlink.internal.Platform; import jdk.tools.jlink.internal.PostProcessor; -import jdk.tools.jlink.internal.plugins.DefaultCompressPlugin; -import jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin; - +import jdk.tools.jlink.plugin.Plugin; +import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolBuilder; import tests.Helper; import tests.JImageGenerator; @@ -74,8 +71,6 @@ */ public class IntegrationTest { - private static final List ordered = new ArrayList<>(); - public static class MyPostProcessor implements PostProcessor, Plugin { public static final String NAME = "mypostprocessor"; @@ -162,7 +157,7 @@ private static void test() throws Exception { limits.add("java.management"); JlinkConfiguration config = new Jlink.JlinkConfiguration(output, mods, - JlinkTask.newModuleFinder(modulePaths, limits, mods)); + JlinkTask.newModuleFinder(modulePaths, limits, mods), false, false, false); List lst = new ArrayList<>(); diff --git a/test/jdk/tools/jlink/JLinkDedupTestBatchSizeOne.java b/test/jdk/tools/jlink/JLinkDedupTestBatchSizeOne.java index 8b2dee1b45a..c7af8865a79 100644 --- a/test/jdk/tools/jlink/JLinkDedupTestBatchSizeOne.java +++ b/test/jdk/tools/jlink/JLinkDedupTestBatchSizeOne.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -21,14 +21,16 @@ * questions. */ -import jdk.test.lib.compiler.CompilerUtils; -import tests.JImageGenerator; - import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import jdk.test.lib.compiler.CompilerUtils; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import tests.JImageGenerator; + + /* * @test * @summary Make sure that modules can be linked using jlink @@ -54,10 +56,6 @@ public class JLinkDedupTestBatchSizeOne { private static final Path SRC_DIR = Paths.get(TEST_SRC, "dedup", "src"); private static final Path MODS_DIR = Paths.get("mods"); - private static final String MODULE_PATH = - Paths.get(JAVA_HOME, "jmods").toString() + - File.pathSeparator + MODS_DIR.toString(); - // the names of the modules in this test private static String[] modules = new String[]{"m1", "m2", "m3", "m4"}; @@ -69,8 +67,13 @@ private static boolean hasJmods() { return true; } - public static void compileAll() throws Throwable { - if (!hasJmods()) return; + private static String modulePath(boolean linkableRuntime) { + return (linkableRuntime ? "" : (Paths.get(JAVA_HOME, "jmods").toString() + + File.pathSeparator)) + MODS_DIR.toString(); + } + + public static void compileAll(boolean linkableRuntime) throws Throwable { + if (!linkableRuntime && !hasJmods()) return; for (String mn : modules) { Path msrc = SRC_DIR.resolve(mn); @@ -80,11 +83,15 @@ public static void compileAll() throws Throwable { } public static void main(String[] args) throws Throwable { - compileAll(); + boolean linkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + System.out.println("Running test on " + + (linkableRuntime ? "enabled" : "disabled") + + " capability of linking from the run-time image."); + compileAll(linkableRuntime); Path image = Paths.get("bug8311591"); JImageGenerator.getJLinkTask() - .modulePath(MODULE_PATH) + .modulePath(modulePath(linkableRuntime)) .output(image.resolve("out-jlink-dedup")) .addMods("m1") .addMods("m2") diff --git a/test/jdk/tools/jlink/JLinkHelpCapabilityTest.java b/test/jdk/tools/jlink/JLinkHelpCapabilityTest.java new file mode 100644 index 00000000000..50f570251d2 --- /dev/null +++ b/test/jdk/tools/jlink/JLinkHelpCapabilityTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.io.PrintWriter; +import java.io.StringWriter; +import java.util.spi.ToolProvider; + +import jdk.tools.jlink.internal.LinkableRuntimeImage; + +/* + * @test + * @summary Test jlink --help for capability output + * @modules jdk.jlink/jdk.tools.jlink.internal + * @requires vm.compMode != "Xcomp" + * @run main/othervm -Duser.language=en JLinkHelpCapabilityTest + */ +public class JLinkHelpCapabilityTest { + static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink") + .orElseThrow(() -> + new RuntimeException("jlink tool not found") + ); + + public static void main(String[] args) throws Exception { + boolean runtimeLinkCap = LinkableRuntimeImage.isLinkableRuntime(); + String capabilities = String.format("Linking from run-time image %s", + runtimeLinkCap ? "enabled" : "disabled"); + { + // Verify capability in --help output + StringWriter writer = new StringWriter(); + PrintWriter pw = new PrintWriter(writer); + JLINK_TOOL.run(pw, pw, "--help"); + String output = writer.toString().trim(); + String lines[] = output.split("\n"); + String capabilitiesMsg = null; + boolean seenCap = false; + for (int i = 0; i < lines.length; i++) { + if (lines[i].startsWith("Capabilities:")) { + seenCap = true; + continue; // skip 'Capabilities:' + } + if (!seenCap) { + continue; + } else { + // Line after capabilities is the message we care about + capabilitiesMsg = lines[i].trim(); + break; + } + } + System.out.println("DEBUG: Capabilities:"); + System.out.println("DEBUG: " + capabilitiesMsg); + if (!capabilities.equals(capabilitiesMsg)) { + System.err.println(output); + throw new AssertionError("'--help': Capabilities mismatch. Expected: '" + + capabilities +"' but got '" + capabilitiesMsg + "'"); + } + } + } +} diff --git a/test/jdk/tools/jlink/multireleasejar/JLinkMRJavaBaseVersionTest.java b/test/jdk/tools/jlink/multireleasejar/JLinkMRJavaBaseVersionTest.java index 46912a68033..1fba932798d 100644 --- a/test/jdk/tools/jlink/multireleasejar/JLinkMRJavaBaseVersionTest.java +++ b/test/jdk/tools/jlink/multireleasejar/JLinkMRJavaBaseVersionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -27,6 +27,7 @@ * @summary jlink should use the version from java.base.jmod to find modules * @bug 8185130 * @summary jlink should throw error if target image and current JDK versions don't match + * @requires jlink.packagedModules * @modules java.base/jdk.internal.module * @library /test/lib * @build jdk.test.lib.process.* CheckRuntimeVersion diff --git a/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java b/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java index 5d06288fb59..1acad93f5d3 100644 --- a/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java +++ b/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java @@ -21,6 +21,9 @@ * questions. */ +import static java.lang.constant.ConstantDescs.CD_Object; +import static java.lang.constant.ConstantDescs.CD_int; + import java.io.IOException; import java.lang.classfile.ClassFile; import java.lang.constant.MethodTypeDesc; @@ -32,16 +35,15 @@ import java.util.stream.Collectors; import org.testng.Assert; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import jdk.tools.jlink.internal.LinkableRuntimeImage; import tests.Helper; import tests.JImageGenerator; import tests.JImageValidator; import tests.Result; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; - -import static java.lang.constant.ConstantDescs.CD_Object; -import static java.lang.constant.ConstantDescs.CD_int; /* * @test @@ -63,12 +65,20 @@ public class GenerateJLIClassesPluginTest { @BeforeTest public static void setup() throws Exception { - helper = Helper.newHelper(); + boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + System.out.println("DEBUG: Tests run on " + + (isLinkableRuntime ? "enabled" : "disabled") + + " capability of linking from the run-time image."); + System.out.println("DEBUG: default module-path, 'jmods', " + + (Helper.jdkHasPackagedModules() ? "" : "NOT ") + + "present."); + helper = Helper.newHelper(isLinkableRuntime); if (helper == null) { + // In case of no linkable run-time image and also no packaged + // modules, helper will be null. System.err.println("Test not run"); return; } - helper.generateDefaultModules(); } @Test @@ -79,7 +89,6 @@ public static void testSpecies() throws IOException { String fileString = "[SPECIES_RESOLVE] java.lang.invoke.BoundMethodHandle$Species_" + species + " (salvaged)\n"; Files.write(baseFile, fileString.getBytes(Charset.defaultCharset())); Result result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir("generate-jli-file")) .option("--generate-jli-classes=@" + baseFile.toString()) .addMods("java.base") @@ -105,7 +114,6 @@ public static void testInvalidSignatures() throws IOException { fileString = "[LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual L_L (success)\n"; Files.write(failFile, fileString.getBytes(Charset.defaultCharset())); Result result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir("invalid-signature")) .option("--generate-jli-classes=@" + failFile.toString()) .addMods("java.base") @@ -118,7 +126,6 @@ public static void testInvalidSignatures() throws IOException { @Test public static void nonExistentTraceFile() throws IOException { Result result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir("non-existent-tracefile")) .option("--generate-jli-classes=@NON_EXISTENT_FILE") .addMods("java.base") @@ -134,7 +141,6 @@ public static void testInvokers() throws IOException { Path invokersTrace = Files.createTempFile("invokers", "trace"); Files.writeString(invokersTrace, fileString, Charset.defaultCharset()); Result result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir("jli-invokers")) .option("--generate-jli-classes=@" + invokersTrace.toString()) .addMods("java.base") @@ -183,4 +189,5 @@ private static List classFilesForSpecies(Collection species) { .map(s -> "/java.base/java/lang/invoke/BoundMethodHandle$Species_" + s + ".class") .collect(Collectors.toList()); } + } diff --git a/test/jdk/tools/jlink/plugins/IncludeLocalesPluginTest.java b/test/jdk/tools/jlink/plugins/IncludeLocalesPluginTest.java index 2a0c43d3168..e2dd3403c83 100644 --- a/test/jdk/tools/jlink/plugins/IncludeLocalesPluginTest.java +++ b/test/jdk/tools/jlink/plugins/IncludeLocalesPluginTest.java @@ -22,25 +22,27 @@ */ import java.nio.file.Path; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; -import jdk.tools.jlink.plugin.PluginException; +import jdk.tools.jlink.internal.LinkableRuntimeImage; import jdk.tools.jlink.internal.TaskHelper; import jdk.tools.jlink.internal.plugins.PluginsResourceBundle; +import jdk.tools.jlink.plugin.PluginException; import tests.Helper; import tests.JImageGenerator; import tests.JImageValidator; import tests.Result; + /* * @test * @bug 8152143 8152704 8155649 8165804 8185841 8176841 8190918 * 8179071 8202537 8221432 8222098 8251317 8258794 8265315 - * 8296248 8306116 8174269 8333582 + * 8296248 8306116 8174269 * @summary IncludeLocalesPlugin tests * @author Naoto Sato * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) @@ -59,14 +61,14 @@ */ public class IncludeLocalesPluginTest { - private final static String moduleName = "IncludeLocalesTest"; + private static final String moduleName = "IncludeLocalesTest"; private static Helper helper; - private final static int INCLUDE_LOCALES_OPTION = 0; - private final static int ADDMODS_OPTION = 1; - private final static int EXPECTED_LOCATIONS = 2; - private final static int UNEXPECTED_PATHS = 3; - private final static int AVAILABLE_LOCALES = 4; - private final static int ERROR_MESSAGE = 5; + private static final int INCLUDE_LOCALES_OPTION = 0; + private static final int ADDMODS_OPTION = 1; + private static final int EXPECTED_LOCATIONS = 2; + private static final int UNEXPECTED_PATHS = 3; + private static final int AVAILABLE_LOCALES = 4; + private static final int ERROR_MESSAGE = 5; private static int errors; @@ -413,11 +415,18 @@ public class IncludeLocalesPluginTest { }; public static void main(String[] args) throws Exception { - helper = Helper.newHelper(); + boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + System.out.println("Running test on " + + (isLinkableRuntime ? "enabled" : "disabled") + + " capability of linking from the run-time image."); + System.out.println("Default module-path, 'jmods', " + + (Helper.jdkHasPackagedModules() ? "" : "NOT ") + + "present."); + + helper = Helper.newHelper(isLinkableRuntime); if (helper == null) { throw new RuntimeException("Helper could not be initialized"); } - helper.generateDefaultModules(); for (Object[] data : testData) { // create image for each test data @@ -425,14 +434,12 @@ public static void main(String[] args) throws Exception { if (data[INCLUDE_LOCALES_OPTION].toString().isEmpty()) { System.out.println("Invoking jlink with no --include-locales option"); result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir(moduleName)) .addMods((String) data[ADDMODS_OPTION]) .call(); } else { System.out.println("Invoking jlink with \"" + data[INCLUDE_LOCALES_OPTION] + "\""); result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir(moduleName)) .addMods((String) data[ADDMODS_OPTION]) .option((String) data[INCLUDE_LOCALES_OPTION]) diff --git a/test/jdk/tools/jlink/runtimeImage/AbstractLinkableRuntimeTest.java b/test/jdk/tools/jlink/runtimeImage/AbstractLinkableRuntimeTest.java new file mode 100644 index 00000000000..e7d5340e3b0 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/AbstractLinkableRuntimeTest.java @@ -0,0 +1,705 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Scanner; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import tests.Helper; +import tests.JImageGenerator; +import tests.JImageGenerator.JLinkTask; +import tests.JImageValidator; + +public abstract class AbstractLinkableRuntimeTest { + + protected static final boolean DEBUG = true; + + public void run() throws Exception { + boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + Helper helper = Helper.newHelper(isLinkableRuntime); + if (helper == null) { + System.err.println(AbstractLinkableRuntimeTest.class.getSimpleName() + + ": Test not run"); + return; + } + runTest(helper, isLinkableRuntime); + System.out.println(getClass().getSimpleName() + " PASSED!"); + } + + /** + * Main test entry point that actual tests ought to override. + * + * @param helper The jlink helper + * @param isLinkableRuntime {@code true} iff the JDK build under test already + * includes the linkable runtime capability in jlink. + * @throws Exception + */ + abstract void runTest(Helper helper, boolean isLinkableRuntime) throws Exception; + + /** + * Ensure 'java --list-modules' lists the correct set of modules in the given + * image. + * + * @param jlinkImage + * @param expectedModules + */ + protected void verifyListModules(Path image, + List expectedModules) throws Exception { + OutputAnalyzer out = runJavaCmd(image, List.of("--list-modules")); + List actual = parseListMods(out.getStdout()); + Collections.sort(actual); + if (!expectedModules.equals(actual)) { + throw new AssertionError("Different modules! Expected " + expectedModules + " got: " + actual); + } + } + + protected OutputAnalyzer runJavaCmd(Path image, List options) throws Exception { + Path targetJava = image.resolve("bin").resolve(getJava()); + List cmd = new ArrayList<>(); + cmd.add(targetJava.toString()); + for (String opt: options) { + cmd.add(opt); + } + List javaCmd = Collections.unmodifiableList(cmd); + OutputAnalyzer out; + try { + out = ProcessTools.executeCommand(javaCmd.toArray(new String[0])); + } catch (Throwable e) { + throw new Exception("Process failed to execute", e); + } + if (out.getExitValue() != 0) { + if (DEBUG) { + System.err.println("Process stdout was: "); + System.err.println(out.getStdout()); + System.err.println("Process stderr was: "); + System.err.println(out.getStderr()); + } + throw new AssertionError("'" + javaCmd.stream().collect(Collectors.joining(" ")) + "'" + + " expected to succeed!"); + } + return out; + } + + protected Path createJavaImageRuntimeLink(BaseJlinkSpec baseSpec) throws Exception { + return createJavaImageRuntimeLink(baseSpec, Collections.emptySet() /* exclude all jmods */); + } + + protected Path createJavaImageRuntimeLink(BaseJlinkSpec baseSpec, Set excludedJmods) throws Exception { + // Be sure we have a JDK without JMODs + Path runtimeJlinkImage = createRuntimeLinkImage(baseSpec, excludedJmods); + + // On Windows jvm.dll is in 'bin' after the jlink + Path libjvm = Path.of((isWindows() ? "bin" : "lib"), "server", System.mapLibraryName("jvm")); + JlinkSpecBuilder builder = new JlinkSpecBuilder(); + // And expect libjvm (not part of the jimage) to be present in the resulting image + builder.expectedFile(libjvm.toString()) + .helper(baseSpec.getHelper()) + .name(baseSpec.getName()) + .validatingModule(baseSpec.getValidatingModule()) + .imagePath(runtimeJlinkImage) + .expectedLocation("/java.base/java/lang/String.class"); + for (String m: baseSpec.getModules()) { + builder.addModule(m); + } + for (String extra: baseSpec.getExtraOptions()) { + builder.extraJlinkOpt(extra); + } + return jlinkUsingImage(builder.build()); + } + + protected Path jlinkUsingImage(JlinkSpec spec) throws Exception { + return jlinkUsingImage(spec, new RuntimeLinkOutputAnalyzerHandler()); + } + + protected Path jlinkUsingImage(JlinkSpec spec, OutputAnalyzerHandler handler) throws Exception { + return jlinkUsingImage(spec, handler, new DefaultSuccessExitPredicate()); + } + + protected Path jlinkUsingImage(JlinkSpec spec, OutputAnalyzerHandler handler, Predicate exitChecker) throws Exception { + String generatedImage = "target-run-time-" + spec.getName(); + Path targetImageDir = spec.getHelper().createNewImageDir(generatedImage); + Path targetJlink = spec.getImageToUse().resolve("bin").resolve(getJlink()); + String[] jlinkCmdArray = new String[] { + targetJlink.toString(), + "--output", targetImageDir.toString(), + "--verbose", + "--add-modules", spec.getModules().stream().collect(Collectors.joining(",")) + }; + List jlinkCmd = new ArrayList<>(); + jlinkCmd.addAll(Arrays.asList(jlinkCmdArray)); + if (spec.getExtraJlinkOpts() != null && !spec.getExtraJlinkOpts().isEmpty()) { + jlinkCmd.addAll(spec.getExtraJlinkOpts()); + } + if (spec.getModulePath() != null) { + for (String mp: spec.getModulePath()) { + jlinkCmd.add("--module-path"); + jlinkCmd.add(mp); + } + } + jlinkCmd = Collections.unmodifiableList(jlinkCmd); // freeze + System.out.println("DEBUG: run-time image based jlink command: " + + jlinkCmd.stream().collect(Collectors.joining(" "))); + OutputAnalyzer analyzer = null; + try { + analyzer = ProcessTools.executeProcess(jlinkCmd.toArray(new String[0])); + } catch (Throwable t) { + throw new AssertionError("Executing process failed!", t); + } + if (!exitChecker.test(analyzer)) { + if (DEBUG) { + System.err.println("Process stdout was: "); + System.err.println(analyzer.getStdout()); + System.err.println("Process stderr was: "); + System.err.println(analyzer.getStderr()); + } + // if the exit checker failed, we expected the other outcome + // i.e. fail for success and success for fail. + boolean successExit = analyzer.getExitValue() == 0; + String msg = String.format("Expected jlink to %s given a jmodless image. Exit code was: %d", + (successExit ? "fail" : "pass"), analyzer.getExitValue()); + throw new AssertionError(msg); + } + handler.handleAnalyzer(analyzer); // Give tests a chance to process in/output + + // validate the resulting image; Includes running 'java -version', only do this + // if the jlink succeeded. + if (analyzer.getExitValue() == 0) { + JImageValidator validator = new JImageValidator(spec.getValidatingModule(), spec.getExpectedLocations(), + targetImageDir.toFile(), spec.getUnexpectedLocations(), Collections.emptyList(), spec.getExpectedFiles()); + validator.validate(); // This doesn't validate locations + if (!spec.getExpectedLocations().isEmpty() || !spec.getUnexpectedLocations().isEmpty()) { + JImageValidator.validate(targetImageDir.resolve("lib").resolve("modules"), spec.getExpectedLocations(), spec.getUnexpectedLocations()); + } + } + return targetImageDir; + } + + /** + * Prepares the test for execution. This assumes the current runtime + * supports linking from it. However, since the 'jmods' dir might be present + * (default jmods module path), the 'jmods' directory needs to get removed + * to provoke actual linking from the run-time image. + * + * @param baseSpec + * @return A path to a JDK that is capable for linking from the run-time + * image. + * @throws Exception + */ + protected Path createRuntimeLinkImage(BaseJlinkSpec baseSpec) throws Exception { + return createRuntimeLinkImage(baseSpec, Collections.emptySet() /* exclude all jmods */); + } + + /** + * Prepares the test for execution. Creates a JDK with a jlink that has the + * capability to link from the run-time image (if needed). It further + * ensures that if packaged modules ('jmods' dir) are present, to remove + * them entirely or as specified in the {@link excludedJmodFiles} set. If + * that set is empty, all packaged modules will be removed. Note that with + * packaged modules present no run-time image based linking would be done. + * + * @param baseSpec + * The specification for the custom - run-time image link capable + * - JDK to create via jlink (if any) + * @param excludedJmods + * The set of jmod files to exclude in the base JDK. Empty set if + * all JMODs should be removed. + * @return A path to a JDK, including jdk.jlink, that has the run-time image + * link capability. + * + * @throws Exception + */ + protected Path createRuntimeLinkImage(BaseJlinkSpec baseSpec, + Set excludedJmodFiles) throws Exception { + // Depending on the shape of the JDK under test, we either only filter + // jmod files or create a run-time image link capable JDK on-the-fly. + Path from = null; + Path runtimeJlinkImage = null; + String finalName = baseSpec.getName() + "-jlink"; + if (baseSpec.isLinkableRuntime()) { + // The build is already run-time image link capable + String javaHome = System.getProperty("java.home"); + from = Path.of(javaHome); + } else { + // Create a run-time image capable JDK using --generate-linkable-runtime + Path tempRuntimeImage = Path.of(finalName + "-tmp"); + JLinkTask task = JImageGenerator.getJLinkTask(); + task.output(tempRuntimeImage) + .addMods("jdk.jlink") // jdk.jlink module is always needed for the test + .option("--generate-linkable-runtime"); + if (baseJDKhasPackagedModules()) { + Path jmodsPath = tempRuntimeImage.resolve("jmods"); + task.option("--keep-packaged-modules=" + jmodsPath); + } + for (String module: baseSpec.getModules()) { + task.addMods(module); + } + task.call().assertSuccess(); + from = tempRuntimeImage; + } + + // Create the target directory + runtimeJlinkImage = baseSpec.getHelper().createNewImageDir(finalName); + + // Remove JMODs as needed for the test + copyJDKTreeWithoutSpecificJmods(from, runtimeJlinkImage, excludedJmodFiles); + // Verify the base image is actually without desired packaged modules + if (excludedJmodFiles.isEmpty()) { + if (Files.exists(runtimeJlinkImage.resolve("jmods"))) { + throw new AssertionError("Must not contain 'jmods' directory"); + } + } else { + Path basePath = runtimeJlinkImage.resolve("jmods"); + for (String jmodFile: excludedJmodFiles) { + Path unexpectedFile = basePath.resolve(Path.of(jmodFile)); + if (Files.exists(unexpectedFile)) { + throw new AssertionError("Must not contain jmod: " + unexpectedFile); + } + } + } + return runtimeJlinkImage; + } + + private boolean baseJDKhasPackagedModules() { + Path jmodsPath = Path.of(System.getProperty("java.home"), "jmods"); + return jmodsPath.toFile().exists(); + } + + private void copyJDKTreeWithoutSpecificJmods(Path from, + Path to, + Set excludedJmods) throws Exception { + if (Files.exists(to)) { + throw new AssertionError("Expected target dir '" + to + "' to exist"); + } + FileVisitor fileVisitor = null; + if (excludedJmods.isEmpty()) { + fileVisitor = new ExcludeAllJmodsFileVisitor(from, to); + } else { + fileVisitor = new FileExcludingFileVisitor(excludedJmods, + from, + to); + } + Files.walkFileTree(from, fileVisitor); + } + + private List parseListMods(String output) throws Exception { + List outputLines = new ArrayList<>(); + try (Scanner lineScan = new Scanner(output)) { + while (lineScan.hasNextLine()) { + outputLines.add(lineScan.nextLine()); + } + } + return outputLines.stream() + .map(a -> { return a.split("@", 2)[0];}) + .filter(a -> !a.isBlank()) + .collect(Collectors.toList()); + } + + private String getJlink() { + return getBinary("jlink"); + } + + private String getJava() { + return getBinary("java"); + } + + private String getBinary(String binary) { + return isWindows() ? binary + ".exe" : binary; + } + + protected static boolean isWindows() { + return System.getProperty("os.name").startsWith("Windows"); + } + + static class ExcludeAllJmodsFileVisitor extends SimpleFileVisitor { + private final Path root; + private final Path destination; + + private ExcludeAllJmodsFileVisitor(Path root, + Path destination) { + this.destination = destination; + this.root = root; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attrs) throws IOException { + Objects.requireNonNull(dir); + Path relative = root.relativize(dir); + if (relative.getFileName().equals(Path.of("jmods"))) { + return FileVisitResult.SKIP_SUBTREE; + } + // Create dir in destination location + Path targetDir = destination.resolve(relative); + if (!Files.exists(targetDir)) { + Files.createDirectory(targetDir); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Path relative = root.relativize(file); + Files.copy(file, destination.resolve(relative), StandardCopyOption.REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + } + + static class FileExcludingFileVisitor extends SimpleFileVisitor { + + private final Set filesToExclude; + private final Path root; + private final Path destination; + + private FileExcludingFileVisitor(Set filesToExclude, + Path root, + Path destination) { + this.filesToExclude = filesToExclude; + this.destination = destination; + this.root = root; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attrs) throws IOException { + Objects.requireNonNull(dir); + Path relative = root.relativize(dir); + // Create dir in destination location + Path targetDir = destination.resolve(relative); + if (!Files.exists(targetDir)) { + Files.createDirectory(targetDir); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Path relative = root.relativize(file); + // Skip files as determined by the exclude set + String fileName = file.getFileName().toString(); + if (!filesToExclude.contains(fileName)) { + Files.copy(file, destination.resolve(relative), StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } + + } + + static class BaseJlinkSpec { + final Helper helper; + final String name; + final String validatingModule; + final List modules; + final List extraOptions; + final boolean isLinkableRuntime; + + BaseJlinkSpec(Helper helper, String name, String validatingModule, + List modules, List extraOptions, boolean isLinkableRuntime) { + this.helper = helper; + this.name = name; + this.modules = modules; + this.extraOptions = extraOptions; + this.validatingModule = validatingModule; + this.isLinkableRuntime = isLinkableRuntime; + } + + public String getValidatingModule() { + return validatingModule; + } + + public Helper getHelper() { + return helper; + } + + public String getName() { + return name; + } + + public List getModules() { + return modules; + } + + public List getExtraOptions() { + return extraOptions; + } + + public boolean isLinkableRuntime() { + return isLinkableRuntime; + } + } + + static class BaseJlinkSpecBuilder { + Helper helper; + String name; + String validatingModule; + List modules = new ArrayList<>(); + List extraOptions = new ArrayList<>(); + boolean isLinkableRuntime; + + BaseJlinkSpecBuilder addModule(String module) { + modules.add(module); + return this; + } + + BaseJlinkSpecBuilder addExtraOption(String option) { + extraOptions.add(option); + return this; + } + + BaseJlinkSpecBuilder setLinkableRuntime() { + isLinkableRuntime = true; + return this; + } + + BaseJlinkSpecBuilder helper(Helper helper) { + this.helper = helper; + return this; + } + + BaseJlinkSpecBuilder name(String name) { + this.name = name; + return this; + } + + BaseJlinkSpecBuilder validatingModule(String module) { + this.validatingModule = module; + return this; + } + + BaseJlinkSpec build() { + if (name == null) { + throw new IllegalStateException("Name must be set"); + } + if (helper == null) { + throw new IllegalStateException("helper must be set"); + } + if (modules.isEmpty()) { + throw new IllegalStateException("modules must be set"); + } + if (validatingModule == null) { + throw new IllegalStateException("the module which should get validated must be set"); + } + return new BaseJlinkSpec(helper, name, validatingModule, modules, extraOptions, isLinkableRuntime); + } + } + + static class JlinkSpec { + final Path imageToUse; + final Helper helper; + final String name; + final List modules; + final String validatingModule; + final List expectedLocations; + final List unexpectedLocations; + final String[] expectedFiles; + final List extraJlinkOpts; + final List modulePath; + + JlinkSpec(Path imageToUse, Helper helper, String name, List modules, + String validatingModule, List expectedLocations, + List unexpectedLocations, String[] expectedFiles, + List extraJlinkOpts, + List modulePath) { + this.imageToUse = imageToUse; + this.helper = helper; + this.name = name; + this.modules = modules; + this.validatingModule = validatingModule; + this.expectedLocations = expectedLocations; + this.unexpectedLocations = unexpectedLocations; + this.expectedFiles = expectedFiles; + this.extraJlinkOpts = extraJlinkOpts; + this.modulePath = modulePath; + } + + public Path getImageToUse() { + return imageToUse; + } + + public Helper getHelper() { + return helper; + } + + public String getName() { + return name; + } + + public List getModules() { + return modules; + } + + public String getValidatingModule() { + return validatingModule; + } + + public List getExpectedLocations() { + return expectedLocations; + } + + public List getUnexpectedLocations() { + return unexpectedLocations; + } + + public String[] getExpectedFiles() { + return expectedFiles; + } + + public List getExtraJlinkOpts() { + return extraJlinkOpts; + } + + public List getModulePath() { + return modulePath; + } + } + + static class JlinkSpecBuilder { + Path imageToUse; + Helper helper; + String name; + List modules = new ArrayList<>(); + String validatingModule; + List expectedLocations = new ArrayList<>(); + List unexpectedLocations = new ArrayList<>(); + List expectedFiles = new ArrayList<>(); + List extraJlinkOpts = new ArrayList<>(); + List modulePath = new ArrayList<>(); + + JlinkSpec build() { + if (imageToUse == null) { + throw new IllegalStateException("No image to use for jlink specified!"); + } + if (helper == null) { + throw new IllegalStateException("No helper specified!"); + } + if (name == null) { + throw new IllegalStateException("No name for the image location specified!"); + } + if (validatingModule == null) { + throw new IllegalStateException("No module specified for after generation validation!"); + } + return new JlinkSpec(imageToUse, + helper, + name, + modules, + validatingModule, + expectedLocations, + unexpectedLocations, + expectedFiles.toArray(new String[0]), + extraJlinkOpts, + modulePath); + } + + JlinkSpecBuilder imagePath(Path image) { + this.imageToUse = image; + return this; + } + + JlinkSpecBuilder helper(Helper helper) { + this.helper = helper; + return this; + } + + JlinkSpecBuilder name(String name) { + this.name = name; + return this; + } + + JlinkSpecBuilder addModule(String module) { + modules.add(module); + return this; + } + + JlinkSpecBuilder validatingModule(String module) { + this.validatingModule = module; + return this; + } + + JlinkSpecBuilder addModulePath(String modulePath) { + this.modulePath.add(modulePath); + return this; + } + + JlinkSpecBuilder expectedLocation(String location) { + expectedLocations.add(location); + return this; + } + + JlinkSpecBuilder unexpectedLocation(String location) { + unexpectedLocations.add(location); + return this; + } + + JlinkSpecBuilder expectedFile(String file) { + expectedFiles.add(file); + return this; + } + + JlinkSpecBuilder extraJlinkOpt(String opt) { + extraJlinkOpts.add(opt); + return this; + } + } + + static abstract class OutputAnalyzerHandler { + + public abstract void handleAnalyzer(OutputAnalyzer out); + + } + + static class RuntimeLinkOutputAnalyzerHandler extends OutputAnalyzerHandler { + + @Override + public void handleAnalyzer(OutputAnalyzer out) { + out.shouldContain("Linking based on the current run-time image"); + } + + } + + static class DefaultSuccessExitPredicate implements Predicate { + + @Override + public boolean test(OutputAnalyzer t) { + return t.getExitValue() == 0; + } + + } +} diff --git a/test/jdk/tools/jlink/runtimeImage/AddOptionsTest.java b/test/jdk/tools/jlink/runtimeImage/AddOptionsTest.java new file mode 100644 index 00000000000..1ffe1240d07 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/AddOptionsTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.List; +import java.util.Scanner; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + +/* + * @test + * @summary Test --add-options jlink plugin when linking from the run-time image + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g AddOptionsTest + */ +public class AddOptionsTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + AddOptionsTest test = new AddOptionsTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .addExtraOption("--add-options") + .addExtraOption("-Xlog:gc=info:stderr -XX:+UseParallelGC") + .name("java-base-with-opts") + .addModule("java.base") + .validatingModule("java.base") + .helper(helper); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path finalImage = createJavaImageRuntimeLink(builder.build()); + verifyListModules(finalImage, List.of("java.base")); + verifyParallelGCInUse(finalImage); + } + + private void verifyParallelGCInUse(Path finalImage) throws Exception { + OutputAnalyzer analyzer = runJavaCmd(finalImage, List.of("--version")); + boolean foundMatch = false; + try (Scanner lineScan = new Scanner(analyzer.getStderr())) { + while (lineScan.hasNextLine()) { + String line = lineScan.nextLine(); + if (line.endsWith("Using Parallel")) { + foundMatch = true; + break; + } + } + } + if (!foundMatch) { + throw new AssertionError("Expected Parallel GC in place for jlinked image"); + } + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/BasicJlinkMissingJavaBase.java b/test/jdk/tools/jlink/runtimeImage/BasicJlinkMissingJavaBase.java new file mode 100644 index 00000000000..b0d2a2d66f5 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/BasicJlinkMissingJavaBase.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import tests.Helper; + + +/* + * @test + * @summary Test basic linking from the run-time image with java.base.jmod missing + * but java.xml.jmod present. It should link from the run-time image without errors. + * @requires (jlink.packagedModules & vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g BasicJlinkMissingJavaBase + */ +public class BasicJlinkMissingJavaBase extends AbstractLinkableRuntimeTest { + + @Override + public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + Path finalImage = createJavaXMLRuntimeLink(helper, "java-xml", isLinkableRuntime); + verifyListModules(finalImage, List.of("java.base", "java.xml")); + } + + private Path createJavaXMLRuntimeLink(Helper helper, String name, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder(); + builder.helper(helper) + .name(name) + .addModule("java.xml") + .validatingModule("java.xml"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Set excludedJmods = Set.of("java.base.jmod"); + return createJavaImageRuntimeLink(builder.build(), excludedJmods); + } + + public static void main(String[] args) throws Exception { + BasicJlinkMissingJavaBase test = new BasicJlinkMissingJavaBase(); + test.run(); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/BasicJlinkTest.java b/test/jdk/tools/jlink/runtimeImage/BasicJlinkTest.java new file mode 100644 index 00000000000..b97ebff9b49 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/BasicJlinkTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.List; + +import tests.Helper; + + +/* + * @test + * @summary Test basic linking from the run-time image + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g BasicJlinkTest false + */ +public class BasicJlinkTest extends AbstractLinkableRuntimeTest { + + @Override + public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + Path finalImage = createJavaBaseRuntimeLink(helper, "java-base", isLinkableRuntime); + verifyListModules(finalImage, List.of("java.base")); + } + + private Path createJavaBaseRuntimeLink(Helper helper, String name, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder(); + builder.helper(helper) + .name(name) + .addModule("java.base") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + return createJavaImageRuntimeLink(builder.build()); + } + + public static void main(String[] args) throws Exception { + BasicJlinkTest test = new BasicJlinkTest(); + test.run(); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/CapturingHandler.java b/test/jdk/tools/jlink/runtimeImage/CapturingHandler.java new file mode 100644 index 00000000000..b6b6105394d --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/CapturingHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 jdk.test.lib.process.OutputAnalyzer; + +class CapturingHandler extends AbstractLinkableRuntimeTest.OutputAnalyzerHandler { + + private OutputAnalyzer output; + + public String stdErr() { + return output.getStderr(); + } + + public OutputAnalyzer analyzer() { + return output; + } + + @Override + public void handleAnalyzer(OutputAnalyzer out) { + this.output = out; + } +} \ No newline at end of file diff --git a/test/jdk/tools/jlink/runtimeImage/CustomModuleJlinkTest.java b/test/jdk/tools/jlink/runtimeImage/CustomModuleJlinkTest.java new file mode 100644 index 00000000000..369bccfecfc --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/CustomModuleJlinkTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.List; + +import tests.Helper; + + +/* + * @test + * @summary Test jmod-less jlink with a custom module + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g CustomModuleJlinkTest + */ +public class CustomModuleJlinkTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + CustomModuleJlinkTest test = new CustomModuleJlinkTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + String customModule = "leaf1"; + helper.generateDefaultJModule(customModule); + + // create a base image for linking from the run-time image + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("cmod-jlink") + .addModule("java.base") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path jlinkImage = createRuntimeLinkImage(builder.build()); + + // Next jlink using the run-time image for java.base, but take + // the custom module from the module path. + Path finalImage = jlinkUsingImage(new JlinkSpecBuilder() + .imagePath(jlinkImage) + .helper(helper) + .name(customModule) + .addModulePath(helper.defaultModulePath(false)) + .expectedLocation(String.format("/%s/%s/com/foo/bar/X.class", customModule, customModule)) + .addModule(customModule) + .validatingModule(customModule) + .build()); + // Expected only the transitive closure of "leaf1" module in the --list-modules + // output of the java launcher. + List expectedModules = List.of("java.base", customModule); + verifyListModules(finalImage, expectedModules); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/GenerateJLIClassesTest.java b/test/jdk/tools/jlink/runtimeImage/GenerateJLIClassesTest.java new file mode 100644 index 00000000000..533a8db30d0 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/GenerateJLIClassesTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import tests.Helper; + +/* + * @test + * @summary Verify JLI class generation in run-time image link mode + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g GenerateJLIClassesTest + */ +public class GenerateJLIClassesTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + GenerateJLIClassesTest test = new GenerateJLIClassesTest(); + test.run(); + } + + /* + * java.lang.invoke.BoundMethodHandle$Species_* classes get generated + * by the GenerateJLiClassesPlugin. This test ensures that potentially + * generated JLI classes from the run-time image don't populate to the + * target image in the run-time image based link mode. + */ + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + Path baseFile = Files.createTempFile("base", "trace"); + String species = "LLLLLLLLLLLLLLLLLLL"; + String fileString = "[SPECIES_RESOLVE] java.lang.invoke.BoundMethodHandle$Species_" + species + " (salvaged)\n"; + Files.write(baseFile, fileString.getBytes(StandardCharsets.UTF_8)); + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .addModule("java.base") + .name("jlink.jli-jmodless") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + + Path runtimeLinkableImage = createRuntimeLinkImage(builder.build()); + // Finally attempt another jmodless link reducing modules to java.base only, + // and asking for specific jli classes. + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(runtimeLinkableImage) + .name("java.base-jli-derived") + .addModule("java.base") + .extraJlinkOpt("--generate-jli-classes=@" + baseFile.toString()) + .expectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_" + species + ".class") + .expectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_L.class") + .unexpectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_" + species.substring(1) + ".class") + .unexpectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_LL.class") + .validatingModule("java.base") + .build()); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/JImageHelper.java b/test/jdk/tools/jlink/runtimeImage/JImageHelper.java new file mode 100644 index 00000000000..ba92e1f35f2 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/JImageHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, Red Hat, Inc. + * 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 java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.internal.jimage.BasicImageReader; +import jdk.internal.jimage.ImageLocation; + +/** + * + * JDK Modular image iterator + */ +public class JImageHelper { + + private JImageHelper() { + // Don't instantiate + } + + public static List listContents(Path jimage) throws IOException { + try(BasicImageReader reader = BasicImageReader.open(jimage)) { + List entries = new ArrayList<>(); + for (String s : reader.getEntryNames()) { + entries.add(s); + } + Collections.sort(entries); + return entries; + } + } + + public static byte[] getLocationBytes(String location, Path jimage) throws IOException { + try(BasicImageReader reader = BasicImageReader.open(jimage)) { + ImageLocation il = reader.findLocation(location); + byte[] r = reader.getResource(il); + if (r == null) { + throw new IllegalStateException(String.format("bytes for %s not found!", location)); + } + return r; + } + } +} diff --git a/test/jdk/tools/jlink/runtimeImage/JavaSEReproducibleTest.java b/test/jdk/tools/jlink/runtimeImage/JavaSEReproducibleTest.java new file mode 100644 index 00000000000..d923358aed9 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/JavaSEReproducibleTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Files; +import java.nio.file.Path; + +import tests.Helper; + + +/* + * @test + * @summary Test reproducibility of linking an java.se image using the run-time + * image. + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g JavaSEReproducibleTest + */ +public class JavaSEReproducibleTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + JavaSEReproducibleTest test = new JavaSEReproducibleTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + String javaSeModule = "java.se"; + // create a java.se using jmod-less approach + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .addModule(javaSeModule) + .validatingModule(javaSeModule); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + builder.name("java-se-repro1"); + Path javaSEJmodLess1 = createJavaImageRuntimeLink(builder.build()); + + // create another java.se version using jmod-less approach + builder.name("java-se-repro2"); + Path javaSEJmodLess2 = createJavaImageRuntimeLink(builder.build()); + if (Files.mismatch(javaSEJmodLess1.resolve("lib").resolve("modules"), + javaSEJmodLess2.resolve("lib").resolve("modules")) != -1L) { + throw new RuntimeException("jlink producing inconsistent result for " + javaSeModule + " (jmod-less)"); + } + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/KeepPackagedModulesFailTest.java b/test/jdk/tools/jlink/runtimeImage/KeepPackagedModulesFailTest.java new file mode 100644 index 00000000000..8094579ecd5 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/KeepPackagedModulesFailTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.function.Predicate; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + + +/* + * @test + * @summary Verify that jlink with an empty module path, but trying to use + * --keep-packaged-modules fails as expected. + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g KeepPackagedModulesFailTest + */ +public class KeepPackagedModulesFailTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + KeepPackagedModulesFailTest test = new KeepPackagedModulesFailTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + // create a base image for linking from the run-time image + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("jlink-fail") + .addModule("java.base") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path baseImage = createRuntimeLinkImage(builder.build()); + + CapturingHandler handler = new CapturingHandler(); + Predicate exitFailPred = new Predicate<>() { + + @Override + public boolean test(OutputAnalyzer t) { + return t.getExitValue() != 0; // expect failure + } + }; + // Attempt a jlink using the run-time image and also using option + // --keep-packaged-modules, which should fail. + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(baseImage) + .name("java-base-jlink-keep-packaged-target") + .addModule("java.base") + .extraJlinkOpt("--keep-packaged-modules=foo") + .validatingModule("java.base") + .build(), handler, exitFailPred); + OutputAnalyzer analyzer = handler.analyzer(); + if (analyzer.getExitValue() == 0) { + throw new AssertionError("Expected jlink to have failed!"); + } + analyzer.stdoutShouldContain("Error"); + analyzer.stdoutShouldContain("--keep-packaged-modules"); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/ModifiedFilesExitTest.java b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesExitTest.java new file mode 100644 index 00000000000..709494b6256 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesExitTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.function.Predicate; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + +/* + * @test + * @summary Verify jlink fails by default when linking from the run-time image + * and files have been modified + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g ModifiedFilesExitTest + */ +public class ModifiedFilesExitTest extends ModifiedFilesTest { + + public static void main(String[] args) throws Exception { + ModifiedFilesExitTest test = new ModifiedFilesExitTest(); + test.run(); + } + + @Override + String initialImageName() { + return "java-base-jlink-with-mod-exit"; + } + + @Override + void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) + throws Exception { + CapturingHandler handler = new CapturingHandler(); + Predicate exitFailPred = new Predicate<>() { + + @Override + public boolean test(OutputAnalyzer t) { + return t.getExitValue() != 0; // expect failure + } + }; + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(initialImage) + .name("java-base-jlink-with-mod-exit-target") + .addModule("java.base") + .validatingModule("java.base") + .build(), handler, exitFailPred); + OutputAnalyzer analyzer = handler.analyzer(); + if (analyzer.getExitValue() == 0) { + throw new AssertionError("Expected jlink to fail due to modified file!"); + } + analyzer.stdoutShouldContain(modifiedFile.toString() + " has been modified"); + // Verify the error message is reasonable + analyzer.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException"); + analyzer.stdoutShouldNotContain("java.lang.IllegalArgumentException"); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/ModifiedFilesTest.java b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesTest.java new file mode 100644 index 00000000000..305fdd39171 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +import tests.Helper; + +public abstract class ModifiedFilesTest extends AbstractLinkableRuntimeTest { + + abstract String initialImageName(); + abstract void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) throws Exception; + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .name(initialImageName()) + .addModule("java.base") + .validatingModule("java.base") + .helper(helper); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path initialImage = createRuntimeLinkImage(builder.build()); + + Path netPropertiesFile = modifyFileInImage(initialImage); + + testAndAssert(netPropertiesFile, helper, initialImage); + } + + protected Path modifyFileInImage(Path jmodLessImg) + throws IOException, AssertionError { + // modify net.properties config file + Path netPropertiesFile = jmodLessImg.resolve("conf").resolve("net.properties"); + Properties props = new Properties(); + try (InputStream is = Files.newInputStream(netPropertiesFile)) { + props.load(is); + } + String prevVal = (String)props.put("java.net.useSystemProxies", Boolean.TRUE.toString()); + if (prevVal == null || Boolean.getBoolean(prevVal) != false) { + throw new AssertionError("Expected previous value to be false!"); + } + try (OutputStream out = Files.newOutputStream(netPropertiesFile)) { + props.store(out, "Modified net.properties file!"); + } + return netPropertiesFile; + } +} diff --git a/test/jdk/tools/jlink/runtimeImage/ModifiedFilesWarningTest.java b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesWarningTest.java new file mode 100644 index 00000000000..f52691dd859 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesWarningTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + +/* + * @test + * @summary Verify warnings are being produced when linking from the run-time + * image and files have been modified + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g ModifiedFilesWarningTest + */ +public class ModifiedFilesWarningTest extends ModifiedFilesTest { + + protected static final String IGNORE_MODIFIED_RUNTIME_OPT = "--ignore-modified-runtime"; + + public static void main(String[] args) throws Exception { + ModifiedFilesWarningTest test = new ModifiedFilesWarningTest(); + test.run(); + } + + @Override + String initialImageName() { + return "java-base-jlink-with-mod-warn"; + } + + @Override + void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) throws Exception { + CapturingHandler handler = new CapturingHandler(); + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(initialImage) + .name("java-base-jlink-with-mod-warn-target") + .addModule("java.base") + .validatingModule("java.base") + .extraJlinkOpt(IGNORE_MODIFIED_RUNTIME_OPT) // only generate a warning + .build(), handler); + OutputAnalyzer out = handler.analyzer(); + // verify we get the warning message + out.stdoutShouldMatch("Warning: .* has been modified"); + out.stdoutShouldNotContain("java.lang.IllegalArgumentException"); + out.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException"); + } +} diff --git a/test/jdk/tools/jlink/runtimeImage/MultiHopTest.java b/test/jdk/tools/jlink/runtimeImage/MultiHopTest.java new file mode 100644 index 00000000000..88f91f238bd --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/MultiHopTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.function.Predicate; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + + +/* + * @test + * @summary Verify that a jlink using the run-time image cannot include jdk.jlink + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g MultiHopTest + */ +public class MultiHopTest extends AbstractLinkableRuntimeTest { + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + Path jdkJlinkJmodless = createJDKJlinkJmodLess(helper, "jdk.jlink-multi-hop1", isLinkableRuntime); + CapturingHandler handler = new CapturingHandler(); + Predicate exitFailPred = new Predicate<>() { + + @Override + public boolean test(OutputAnalyzer a) { + return a.getExitValue() != 0; // expect failure + } + }; + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(jdkJlinkJmodless) + .name("jdk-jlink-multi-hop1-target") + .addModule("jdk.jlink") + .validatingModule("java.base") + .build(), handler, exitFailPred); + OutputAnalyzer analyzer = handler.analyzer(); + if (analyzer.getExitValue() == 0) { + throw new AssertionError("Expected jlink to fail due to including jdk.jlink"); + } + String expectedMsg = "This JDK does not contain packaged modules " + + "and cannot be used to create another image with " + + "the jdk.jlink module"; + analyzer.stdoutShouldContain(expectedMsg); + analyzer.stdoutShouldNotContain("Exception"); // ensure error message is sane + } + + private Path createJDKJlinkJmodLess(Helper helper, String name, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder(); + builder.helper(helper) + .name(name) + .addModule("jdk.jlink") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + return createRuntimeLinkImage(builder.build()); + } + + public static void main(String[] args) throws Exception { + MultiHopTest test = new MultiHopTest(); + test.run(); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java b/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java new file mode 100644 index 00000000000..9910be5f919 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import tests.Helper; +import tests.JImageGenerator; + + +/* + * @test + * @summary Compare packaged-modules jlink with a run-time image based jlink to + * produce the same result + * @requires (jlink.packagedModules & vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g PackagedModulesVsRuntimeImageLinkTest + */ +public class PackagedModulesVsRuntimeImageLinkTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + PackagedModulesVsRuntimeImageLinkTest test = new PackagedModulesVsRuntimeImageLinkTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + // create a java.se using jmod-less approach + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("java-se-jmodless") + .addModule("java.se") + .validatingModule("java.se"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path javaSEruntimeLink = createJavaImageRuntimeLink(builder.build()); + + // create a java.se using packaged modules (jmod-full) + Path javaSEJmodFull = JImageGenerator.getJLinkTask() + .output(helper.createNewImageDir("java-se-jmodfull")) + .addMods("java.se").call().assertSuccess(); + + compareRecursively(javaSEruntimeLink, javaSEJmodFull); + } + + // Visit all files in the given directories checking that they're byte-by-byte identical + private static void compareRecursively(Path javaSEJmodLess, + Path javaSEJmodFull) throws IOException, AssertionError { + FilesCapturingVisitor jmodFullVisitor = new FilesCapturingVisitor(javaSEJmodFull); + FilesCapturingVisitor jmodLessVisitor = new FilesCapturingVisitor(javaSEJmodLess); + Files.walkFileTree(javaSEJmodFull, jmodFullVisitor); + Files.walkFileTree(javaSEJmodLess, jmodLessVisitor); + List jmodFullFiles = jmodFullVisitor.filesVisited(); + List jmodLessFiles = jmodLessVisitor.filesVisited(); + Collections.sort(jmodFullFiles); + Collections.sort(jmodLessFiles); + + if (jmodFullFiles.size() != jmodLessFiles.size()) { + throw new AssertionError(String.format("Size of files different for jmod-less (%d) vs jmod-full (%d) java.se jlink", jmodLessFiles.size(), jmodFullFiles.size())); + } + String jimageFile = Path.of("lib").resolve("modules").toString(); + // Compare all files except the modules image + for (int i = 0; i < jmodFullFiles.size(); i++) { + String jmodFullPath = jmodFullFiles.get(i); + String jmodLessPath = jmodLessFiles.get(i); + if (!jmodFullPath.equals(jmodLessPath)) { + throw new AssertionError(String.format("jmod-full path (%s) != jmod-less path (%s)", jmodFullPath, jmodLessPath)); + } + if (jmodFullPath.equals(jimageFile)) { + continue; + } + Path a = javaSEJmodFull.resolve(Path.of(jmodFullPath)); + Path b = javaSEJmodLess.resolve(Path.of(jmodLessPath)); + if (Files.mismatch(a, b) != -1L) { + handleFileMismatch(a, b); + } + } + // Compare jimage contents by iterating its entries and comparing their + // paths and content bytes + // + // Note: The files aren't byte-by-byte comparable (probably due to string hashing + // and offset differences in container bytes) + Path jimageJmodLess = javaSEJmodLess.resolve(Path.of("lib")).resolve(Path.of("modules")); + Path jimageJmodFull = javaSEJmodFull.resolve(Path.of("lib")).resolve(Path.of("modules")); + List jimageContentJmodLess = JImageHelper.listContents(jimageJmodLess); + List jimageContentJmodFull = JImageHelper.listContents(jimageJmodFull); + if (jimageContentJmodLess.size() != jimageContentJmodFull.size()) { + throw new AssertionError(String.format("Size of jimage content differs for jmod-less (%d) v. jmod-full (%d)", jimageContentJmodLess.size(), jimageContentJmodFull.size())); + } + for (int i = 0; i < jimageContentJmodFull.size(); i++) { + if (!jimageContentJmodFull.get(i).equals(jimageContentJmodLess.get(i))) { + throw new AssertionError(String.format("Jimage content differs at index %d: jmod-full was: '%s' jmod-less was: '%s'", + i, + jimageContentJmodFull.get(i), + jimageContentJmodLess.get(i) + )); + } + String loc = jimageContentJmodFull.get(i); + if (isTreeInfoResource(loc)) { + // Skip container bytes as those are offsets to the content + // of the container which might be different between jlink runs. + continue; + } + byte[] resBytesFull = JImageHelper.getLocationBytes(loc, jimageJmodFull); + byte[] resBytesLess = JImageHelper.getLocationBytes(loc, jimageJmodLess); + if (resBytesFull.length != resBytesLess.length || Arrays.mismatch(resBytesFull, resBytesLess) != -1) { + throw new AssertionError("Content bytes mismatch for " + loc); + } + } + } + + private static boolean isTreeInfoResource(String path) { + return path.startsWith("/packages") || path.startsWith("/modules"); + } + + private static void handleFileMismatch(Path a, Path b) { + throw new AssertionError("Files mismatch: " + a + " vs. " + b); + } + + static class FilesCapturingVisitor extends SimpleFileVisitor { + private final Path basePath; + private final List filePaths = new ArrayList<>(); + public FilesCapturingVisitor(Path basePath) { + this.basePath = basePath; + } + + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + Path relative = basePath.relativize(path); + filePaths.add(relative.toString()); + return FileVisitResult.CONTINUE; + } + + List filesVisited() { + return filePaths; + } + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/PatchedJDKModuleJlinkTest.java b/test/jdk/tools/jlink/runtimeImage/PatchedJDKModuleJlinkTest.java new file mode 100644 index 00000000000..3baa824e049 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/PatchedJDKModuleJlinkTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Predicate; +import java.util.spi.ToolProvider; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + + +/* + * @test + * @summary Test run-time link with --patch-module. Expect failure. + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g PatchedJDKModuleJlinkTest + */ +public class PatchedJDKModuleJlinkTest extends AbstractLinkableRuntimeTest { + + @Override + public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + String imageName = "java-base-patched"; + Path runtimeLinkImage = createRuntimeLinkImage(helper, imageName + "-base", isLinkableRuntime); + + // Prepare patched module content + Path patchSource = Path.of("java-base-patch-src"); + Path pkg = patchSource.resolve("java", "lang"); + Path extraClass = pkg.resolve("MyJlinkPatchInteger.java"); + String source = """ + package java.lang; + public class MyJlinkPatchInteger { + public int add(int a, int b) { + return a + b; + } + } + """; + Files.createDirectories(pkg); + Files.writeString(extraClass, source); + Path patchClasses = Path.of("java-base-patch-classes"); + Files.createDirectories(patchClasses); + ToolProvider javac = ToolProvider.findFirst("javac") + .orElseThrow(() -> new AssertionError("javac not found")); + javac.run(System.out, System.err, new String[] { + "-d", patchClasses.toString(), + "--patch-module=java.base=" + patchSource.toAbsolutePath().toString(), + extraClass.toAbsolutePath().toString() + }); + + // Perform a run-time image link expecting a failure + CapturingHandler handler = new CapturingHandler(); + Predicate exitFailPred = new Predicate<>() { + + @Override + public boolean test(OutputAnalyzer t) { + return t.getExitValue() != 0; // expect failure + } + }; + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(runtimeLinkImage) + .name(imageName + "-derived") + .addModule("java.base") + .validatingModule("java.base") + .extraJlinkOpt("-J--patch-module=java.base=" + + patchClasses.toAbsolutePath().toString()) + .build(), handler, exitFailPred); + OutputAnalyzer analyzer = handler.analyzer(); + if (analyzer.getExitValue() == 0) { + throw new AssertionError("Expected jlink to fail due to patched module!"); + } + analyzer.stdoutShouldContain("MyJlinkPatchInteger.class not found in the modules image."); + analyzer.stdoutShouldContain("--patch-module is not supported"); + // Verify the error message is reasonable + analyzer.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException"); + analyzer.stdoutShouldNotContain("java.lang.IllegalArgumentException"); + } + + private Path createRuntimeLinkImage(Helper helper, String name, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .name(name) + .addModule("java.base") + .validatingModule("java.base") + .helper(helper); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + return createRuntimeLinkImage(builder.build()); + } + + public static void main(String[] args) throws Exception { + PatchedJDKModuleJlinkTest test = new PatchedJDKModuleJlinkTest(); + test.run(); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/SystemModulesTest.java b/test/jdk/tools/jlink/runtimeImage/SystemModulesTest.java new file mode 100644 index 00000000000..fac8cac112d --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/SystemModulesTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import tests.Helper; +import tests.JImageValidator; + + +/* + * @test + * @summary Test appropriate handling of generated SystemModules* classes in run-time image link mode + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g SystemModulesTest + */ +public class SystemModulesTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + SystemModulesTest test = new SystemModulesTest(); + test.run(); + } + + /* + * SystemModule classes are module specific. If the jlink is based on the + * modules image, then earlier generated SystemModule classes shall not get + * propagated. + */ + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + // create an image with a module containing a main entrypoint (jdk.httpserver), + // thus producing the SystemModules$0.class. Add jdk.jdwp.agent as a module which + // isn't resolved by default, so as to generate SystemModules$default.class + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("httpserver-jlink-jmodless-derived") + .addModule("jdk.httpserver") + .addModule("jdk.jdwp.agent") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path javaseJmodless = createJavaImageRuntimeLink(builder.build()); + // Verify that SystemModules$0.class etc. are there, due to httpserver and jdwp.agent + JImageValidator.validate(javaseJmodless.resolve("lib").resolve("modules"), + List.of("/java.base/jdk/internal/module/SystemModules$default.class", + "/java.base/jdk/internal/module/SystemModules$0.class", + "/java.base/jdk/internal/module/SystemModules$all.class"), + Collections.emptyList()); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/SystemModulesTest2.java b/test/jdk/tools/jlink/runtimeImage/SystemModulesTest2.java new file mode 100644 index 00000000000..6be4ad7321c --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/SystemModulesTest2.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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 java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import tests.Helper; +import tests.JImageValidator; + + +/* + * @test + * @summary Test disabled SystemModulesPlugin in run-time image link mode. Expect + * generated classes to not be there. + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g SystemModulesTest2 + */ +public class SystemModulesTest2 extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + SystemModulesTest2 test = new SystemModulesTest2(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + // See SystemModulesTest which enables the system-modules plugin. With + // it disabled, we expect for the generated classes to not be there. + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("jlink-jmodless-sysmod2") + .addModule("jdk.httpserver") + .validatingModule("java.base") + .addExtraOption("--disable-plugin") + .addExtraOption("system-modules"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path runtimeImageLinkTarget = createJavaImageRuntimeLink(builder.build()); + JImageValidator.validate(runtimeImageLinkTarget.resolve("lib").resolve("modules"), + Collections.emptyList(), + List.of("/java.base/jdk/internal/module/SystemModules$all.class", + "/java.base/jdk/internal/module/SystemModules$default.class", + "/java.base/jdk/internal/module/SystemModules$0.class")); + } + +} diff --git a/test/jdk/tools/lib/tests/Helper.java b/test/jdk/tools/lib/tests/Helper.java index 0acf6ec8bf3..337d767b0b2 100644 --- a/test/jdk/tools/lib/tests/Helper.java +++ b/test/jdk/tools/lib/tests/Helper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -64,20 +64,30 @@ public class Helper { private final Map> moduleDependencies = new HashMap<>(); private final List bootClasses; private final FileSystem fs; + private final boolean linkableRuntime; + private static final Path JDK_HOME = Paths.get(System.getProperty("test.jdk")); public static Helper newHelper() throws IOException { - Path jdkHome = Paths.get(System.getProperty("test.jdk")); - if (!Files.exists(jdkHome.resolve("jmods"))) { + return newHelper(false); + } + + public static Helper newHelper(boolean linkableRuntime) throws IOException { + if (!linkableRuntime && !jdkHasPackagedModules()) { // Skip test if the jmods directory is missing (e.g. exploded image) System.err.println("Test not run, NO jmods directory"); return null; } - return new Helper(jdkHome); + return new Helper(JDK_HOME, linkableRuntime); + } + + public static boolean jdkHasPackagedModules() { + return Files.exists(JDK_HOME.resolve("jmods")); } - private Helper(Path jdkHome) throws IOException { + private Helper(Path jdkHome, boolean linkableRuntime) throws IOException { + this.linkableRuntime = linkableRuntime; this.stdjmods = jdkHome.resolve("jmods").normalize(); - if (!Files.exists(stdjmods)) { + if (!linkableRuntime && !Files.exists(stdjmods)) { throw new IOException("Standard jMods do not exist."); } this.fs = FileSystems.getFileSystem(URI.create("jrt:/")); @@ -140,7 +150,8 @@ public String defaultModulePath() { } public String defaultModulePath(boolean includeStdMods) { - return (includeStdMods? stdjmods.toAbsolutePath().toString() : "") + File.pathSeparator + String standardMods = linkableRuntime ? "" : stdjmods.toAbsolutePath().toString() + File.pathSeparator; + return (includeStdMods? standardMods : "") + jmods.toAbsolutePath().toString() + File.pathSeparator + jars.toAbsolutePath().toString() + File.pathSeparator + explodedmodsclasses.toAbsolutePath().toString(); @@ -184,7 +195,7 @@ public Result generateDefaultJModule(String moduleName, List classNames, generateGarbage(jmodsclasses.resolve(moduleName)); Path jmodFile = jmods.resolve(moduleName + ".jmod"); - JModTask task = JImageGenerator.getJModTask() + JModTask task = JImageGenerator.getJModTask(linkableRuntime) .jmod(jmodFile) .addJmods(stdjmods) .addJmods(jmods.toAbsolutePath()) diff --git a/test/jdk/tools/lib/tests/JImageGenerator.java b/test/jdk/tools/lib/tests/JImageGenerator.java index 330547148d0..b872ca4f584 100644 --- a/test/jdk/tools/lib/tests/JImageGenerator.java +++ b/test/jdk/tools/lib/tests/JImageGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -159,7 +159,11 @@ private static void copy(InputStream in, OutputStream out) throws IOException { } public static JModTask getJModTask() { - return new JModTask(); + return getJModTask(false); + } + + public static JModTask getJModTask(boolean linkableRuntime) { + return new JModTask(linkableRuntime); } public static JLinkTask getJLinkTask() { @@ -350,11 +354,16 @@ public static class JModTask { private final List jars = new ArrayList<>(); private final List jmods = new ArrayList<>(); private final List options = new ArrayList<>(); + private final boolean linkableRuntime; private Path output; private String hashModules; private String mainClass; private String moduleVersion; + private JModTask(boolean linkableRuntime) { + this.linkableRuntime = linkableRuntime; + } + public JModTask addNativeLibraries(Path cp) { this.libs.add(cp); return this; @@ -414,7 +423,7 @@ private String modulePath() { // This is expect FIRST jmods THEN jars, if you change this, some tests could fail String jmods = toPath(this.jmods); String jars = toPath(this.jars); - return jmods + File.pathSeparator + jars; + return linkableRuntime ? jars : jmods + File.pathSeparator + jars; } private String toPath(List paths) { diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 4f00846116c..775b95959c6 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -138,6 +138,7 @@ public Map call() { map.put("jdk.containerized", this::jdkContainerized); map.put("vm.flagless", this::isFlagless); map.put("jdk.foreign.linker", this::jdkForeignLinker); + map.put("jlink.packagedModules", this::packagedModules); vmGC(map); // vm.gc.X = true/false vmGCforCDS(map); // may set vm.gc vmOptFinalFlags(map); @@ -715,6 +716,21 @@ private String jdkContainerized() { return "" + "true".equalsIgnoreCase(isEnabled); } + private String packagedModules() { + // Some jlink tests require packaged modules being present (jmods). + // For a runtime linkable image build packaged modules aren't present + try { + Path jmodsDir = Path.of(System.getProperty("java.home"), "jmods"); + if (jmodsDir.toFile().exists()) { + return Boolean.TRUE.toString(); + } else { + return Boolean.FALSE.toString(); + } + } catch (Throwable t) { + return Boolean.FALSE.toString(); + } + } + /** * Checks if we are in almost out-of-box configuration, i.e. the flags * which JVM is started with don't affect its behavior "significantly". diff --git a/test/langtools/tools/javac/plugin/AutostartPlugins.java b/test/langtools/tools/javac/plugin/AutostartPlugins.java index eeac5e0ecc6..5eb5b16bad5 100644 --- a/test/langtools/tools/javac/plugin/AutostartPlugins.java +++ b/test/langtools/tools/javac/plugin/AutostartPlugins.java @@ -30,7 +30,7 @@ * jdk.compiler/com.sun.tools.javac.main * jdk.jlink * @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask - * @run main AutostartPlugins + * @run main/othervm AutostartPlugins */ import java.io.IOException; diff --git a/test/langtools/tools/javac/plugin/InternalAPI.java b/test/langtools/tools/javac/plugin/InternalAPI.java index 523c9365c99..70bb024ede9 100644 --- a/test/langtools/tools/javac/plugin/InternalAPI.java +++ b/test/langtools/tools/javac/plugin/InternalAPI.java @@ -31,7 +31,7 @@ * jdk.compiler/com.sun.tools.javac.main * jdk.jlink * @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask - * @run main InternalAPI + * @run main/othervm InternalAPI */ import java.io.IOException; From 8417aff6ebb1153cc2924c95a5f3ec7159ee8187 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 11 Nov 2024 15:14:10 +0000 Subject: [PATCH 16/23] 8343925: [BACKOUT] JDK-8342650 Move getChars to DecimalDigits Reviewed-by: jpai, alanb, liach --- .../java/lang/AbstractStringBuilder.java | 8 +- .../share/classes/java/lang/Integer.java | 4 +- .../share/classes/java/lang/Long.java | 4 +- .../classes/java/lang/StringConcatHelper.java | 16 +- .../share/classes/java/lang/StringLatin1.java | 115 +++++++ .../share/classes/java/lang/StringUTF16.java | 118 +++++++ .../share/classes/java/lang/System.java | 8 + .../share/classes/java/math/BigDecimal.java | 127 +++++-- .../jdk/internal/access/JavaLangAccess.java | 4 + .../jdk/internal/util/DecimalDigits.java | 315 +----------------- .../patches/java.base/java/lang/Helper.java | 12 +- .../bench/java/lang/StringBuilders.java | 50 --- 12 files changed, 376 insertions(+), 405 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index fd9dcf60e54..b40f6274412 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -830,9 +830,9 @@ public AbstractStringBuilder append(int i) { int spaceNeeded = count + DecimalDigits.stringSize(i); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - DecimalDigits.getCharsLatin1(i, spaceNeeded, value); + StringLatin1.getChars(i, spaceNeeded, value); } else { - DecimalDigits.getCharsUTF16(i, spaceNeeded, value); + StringUTF16.getChars(i, count, spaceNeeded, value); } this.count = spaceNeeded; return this; @@ -855,9 +855,9 @@ public AbstractStringBuilder append(long l) { int spaceNeeded = count + DecimalDigits.stringSize(l); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - DecimalDigits.getCharsLatin1(l, spaceNeeded, value); + StringLatin1.getChars(l, spaceNeeded, value); } else { - DecimalDigits.getCharsUTF16(l, spaceNeeded, value); + StringUTF16.getChars(l, count, spaceNeeded, value); } this.count = spaceNeeded; return this; diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 5f73d61e5d4..e666e977c61 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -432,11 +432,11 @@ public static String toString(int i) { int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - DecimalDigits.getCharsLatin1(i, size, buf); + StringLatin1.getChars(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; - DecimalDigits.getCharsUTF16(i, size, buf); + StringUTF16.getChars(i, size, buf); return new String(buf, UTF16); } } diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 7df9ddfb270..8c083b3ec84 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -462,11 +462,11 @@ public static String toString(long i) { int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - DecimalDigits.getCharsLatin1(i, size, buf); + StringLatin1.getChars(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; - DecimalDigits.getCharsUTF16(i, size, buf); + StringUTF16.getChars(i, size, buf); return new String(buf, UTF16); } } diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index 632fe0f58b5..b635d0dee0f 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -298,12 +298,12 @@ static long prepend(long indexCoder, byte[] buf, char value, String prefix) { static long prepend(long indexCoder, byte[] buf, int value, String prefix) { int index = (int)indexCoder; if (indexCoder < UTF16) { - index = DecimalDigits.getCharsLatin1(value, index, buf); + index = StringLatin1.getChars(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); return index; } else { - index = DecimalDigits.getCharsUTF16(value, index, buf); + index = StringUTF16.getChars(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); return index | UTF16; @@ -324,12 +324,12 @@ static long prepend(long indexCoder, byte[] buf, int value, String prefix) { static long prepend(long indexCoder, byte[] buf, long value, String prefix) { int index = (int)indexCoder; if (indexCoder < UTF16) { - index = DecimalDigits.getCharsLatin1(value, index, buf); + index = StringLatin1.getChars(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); return index; } else { - index = DecimalDigits.getCharsUTF16(value, index, buf); + index = StringUTF16.getChars(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); return index | UTF16; @@ -682,11 +682,11 @@ static int prepend(int index, byte coder, byte[] buf, char value, String prefix) */ static int prepend(int index, byte coder, byte[] buf, int value, String prefix) { if (coder == String.LATIN1) { - index = DecimalDigits.getCharsLatin1(value, index, buf); + index = StringLatin1.getChars(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { - index = DecimalDigits.getCharsUTF16(value, index, buf); + index = StringUTF16.getChars(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); } @@ -706,11 +706,11 @@ static int prepend(int index, byte coder, byte[] buf, int value, String prefix) */ static int prepend(int index, byte coder, byte[] buf, long value, String prefix) { if (coder == String.LATIN1) { - index = DecimalDigits.getCharsLatin1(value, index, buf); + index = StringLatin1.getChars(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { - index = DecimalDigits.getCharsUTF16(value, index, buf); + index = StringUTF16.getChars(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); } diff --git a/src/java.base/share/classes/java/lang/StringLatin1.java b/src/java.base/share/classes/java/lang/StringLatin1.java index abe42c2c7c7..c12b8afc21f 100644 --- a/src/java.base/share/classes/java/lang/StringLatin1.java +++ b/src/java.base/share/classes/java/lang/StringLatin1.java @@ -34,6 +34,7 @@ import java.util.stream.StreamSupport; import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.IntrinsicCandidate; import static java.lang.String.LATIN1; @@ -85,6 +86,120 @@ public static byte[] inflate(byte[] value, int off, int len) { return ret; } + /** + * Places characters representing the integer i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Integer.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Integer.MIN_VALUE that overflows + * integer. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + static int getChars(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q; + int charPos = index; + + boolean negative = i < 0; + if (!negative) { + i = -i; + } + + // Generate two digits per iteration + while (i <= -100) { + q = i / 100; + charPos -= 2; + writeDigitPair(buf, charPos, (q * 100) - i); + i = q; + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + writeDigitPair(buf, charPos, -i); + } else { + buf[--charPos] = (byte)('0' - i); + } + + if (negative) { + buf[--charPos] = (byte)'-'; + } + return charPos; + } + + /** + * Places characters representing the long i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Long.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Long.MIN_VALUE that overflows + * long. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + static int getChars(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + writeDigitPair(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + writeDigitPair(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + writeDigitPair(buf, charPos, -i2); + } else { + buf[--charPos] = (byte)('0' - i2); + } + + if (negative) { + buf[--charPos] = (byte)'-'; + } + return charPos; + } + + private static void writeDigitPair(byte[] buf, int charPos, int value) { + short pair = DecimalDigits.digitPair(value); + buf[charPos] = (byte)(pair); + buf[charPos + 1] = (byte)(pair >> 8); + } + public static void getChars(byte[] value, int srcBegin, int srcEnd, char[] dst, int dstBegin) { inflate(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); } diff --git a/src/java.base/share/classes/java/lang/StringUTF16.java b/src/java.base/share/classes/java/lang/StringUTF16.java index a1dcca8ffad..f04b991827f 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -35,6 +35,7 @@ import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -1512,6 +1513,20 @@ public static int codePointCountSB(byte[] val, int beginIndex, int endIndex) { return codePointCount(val, beginIndex, endIndex, true /* checked */); } + public static int getChars(int i, int begin, int end, byte[] value) { + checkBoundsBeginEnd(begin, end, value); + int pos = getChars(i, end, value); + assert begin == pos; + return pos; + } + + public static int getChars(long l, int begin, int end, byte[] value) { + checkBoundsBeginEnd(begin, end, value); + int pos = getChars(l, end, value); + assert begin == pos; + return pos; + } + public static boolean contentEquals(byte[] v1, byte[] v2, int len) { checkBoundsOffCount(0, len, v2); for (int i = 0; i < len; i++) { @@ -1647,6 +1662,109 @@ public static int lastIndexOfLatin1(byte[] src, int srcCount, static final int MAX_LENGTH = Integer.MAX_VALUE >> 1; + // Used by trusted callers. Assumes all necessary bounds checks have + // been done by the caller. + + /** + * This is a variant of {@link StringLatin1#getChars(int, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + static int getChars(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q, r; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using ints + while (i <= -100) { + q = i / 100; + r = (q * 100) - i; + i = q; + charPos -= 2; + putPair(buf, charPos, r); + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + putPair(buf, charPos, -i); + } else { + putChar(buf, --charPos, '0' - i); + } + + if (negative) { + putChar(buf, --charPos, '-'); + } + return charPos; + } + + /** + * This is a variant of {@link StringLatin1#getChars(long, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + static int getChars(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPair(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPair(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPair(buf, charPos, -i2); + } else { + putChar(buf, --charPos, '0' - i2); + } + + if (negative) { + putChar(buf, --charPos, '-'); + } + return charPos; + } + + private static void putPair(byte[] buf, int charPos, int v) { + int packed = (int) DecimalDigits.digitPair(v); + putChar(buf, charPos, packed & 0xFF); + putChar(buf, charPos + 1, packed >> 8); + } + // End of trusted methods. + public static void checkIndex(int off, byte[] val) { String.checkIndex(off, length(val)); } diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 451ed8e6bfc..5b04bca4f44 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2648,6 +2648,14 @@ public byte stringCoder(String str) { return str.coder(); } + public int getCharsLatin1(long i, int index, byte[] buf) { + return StringLatin1.getChars(i, index, buf); + } + + public int getCharsUTF16(long i, int index, byte[] buf) { + return StringUTF16.getChars(i, index, buf); + } + public String join(String prefix, String suffix, String delimiter, String[] elements, int size) { return String.join(prefix, suffix, delimiter, elements, size); } diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index b00970963b6..abd49aa69bc 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -35,15 +35,9 @@ import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.StreamCorruptedException; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; -import jdk.internal.access.JavaLangAccess; -import jdk.internal.access.SharedSecrets; -import jdk.internal.util.DecimalDigits; - /** * Immutable, arbitrary-precision signed decimal numbers. A {@code * BigDecimal} consists of an arbitrary precision integer @@ -334,8 +328,6 @@ * @since 1.1 */ public class BigDecimal extends Number implements Comparable { - private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); - /* * Let l = log_2(10). * Then, L < l < L + ulp(L) / 2, that is, L = roundTiesToEven(l). @@ -4172,6 +4164,103 @@ public BigDecimal ulp() { return BigDecimal.valueOf(1, this.scale(), 1); } + // Private class to build a string representation for BigDecimal object. The + // StringBuilder field acts as a buffer to hold the temporary representation + // of BigDecimal. The cmpCharArray holds all the characters for the compact + // representation of BigDecimal (except for '-' sign' if it is negative) if + // its intCompact field is not INFLATED. + static class StringBuilderHelper { + final StringBuilder sb; // Placeholder for BigDecimal string + final char[] cmpCharArray; // character array to place the intCompact + + StringBuilderHelper() { + sb = new StringBuilder(32); + // All non negative longs can be made to fit into 19 character array. + cmpCharArray = new char[19]; + } + + // Accessors. + StringBuilder getStringBuilder() { + sb.setLength(0); + return sb; + } + + char[] getCompactCharArray() { + return cmpCharArray; + } + + /** + * Places characters representing the intCompact in {@code long} into + * cmpCharArray and returns the offset to the array where the + * representation starts. + * + * @param intCompact the number to put into the cmpCharArray. + * @return offset to the array where the representation starts. + * Note: intCompact must be greater or equal to zero. + */ + int putIntCompact(long intCompact) { + assert intCompact >= 0; + + long q; + int r; + // since we start from the least significant digit, charPos points to + // the last character in cmpCharArray. + int charPos = cmpCharArray.length; + + // Get 2 digits/iteration using longs until quotient fits into an int + while (intCompact > Integer.MAX_VALUE) { + q = intCompact / 100; + r = (int)(intCompact - q * 100); + intCompact = q; + cmpCharArray[--charPos] = DIGIT_ONES[r]; + cmpCharArray[--charPos] = DIGIT_TENS[r]; + } + + // Get 2 digits/iteration using ints when i2 >= 100 + int q2; + int i2 = (int)intCompact; + while (i2 >= 100) { + q2 = i2 / 100; + r = i2 - q2 * 100; + i2 = q2; + cmpCharArray[--charPos] = DIGIT_ONES[r]; + cmpCharArray[--charPos] = DIGIT_TENS[r]; + } + + cmpCharArray[--charPos] = DIGIT_ONES[i2]; + if (i2 >= 10) + cmpCharArray[--charPos] = DIGIT_TENS[i2]; + + return charPos; + } + + static final char[] DIGIT_TENS = { + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', + '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', + '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', + '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', + '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', + '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', + '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', + '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', + '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', + }; + + static final char[] DIGIT_ONES = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + }; + } + /** * Lay out this {@code BigDecimal} into a {@code char[]} array. * The Java 1.2 equivalent to this was called {@code getValueString}. @@ -4182,8 +4271,6 @@ public BigDecimal ulp() { * {@code BigDecimal} */ private String layoutChars(boolean sci) { - long intCompact = this.intCompact; - int scale = this.scale; if (scale == 0) // zero scale is trivial return (intCompact != INFLATED) ? Long.toString(intCompact): @@ -4193,24 +4280,18 @@ private String layoutChars(boolean sci) { // currency fast path int lowInt = (int)intCompact % 100; int highInt = (int)intCompact / 100; - int highIntSize = DecimalDigits.stringSize(highInt); - byte[] buf = new byte[highIntSize + 3]; - DecimalDigits.putPairLatin1(buf, highIntSize + 1, lowInt); - buf[highIntSize] = '.'; - DecimalDigits.getCharsLatin1(highInt, highIntSize, buf); - try { - return JLA.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); - } catch (CharacterCodingException cce) { - throw new AssertionError(cce); - } + return (Integer.toString(highInt) + '.' + + StringBuilderHelper.DIGIT_TENS[lowInt] + + StringBuilderHelper.DIGIT_ONES[lowInt]) ; } + StringBuilderHelper sbHelper = new StringBuilderHelper(); char[] coeff; int offset; // offset is the starting index for coeff array // Get the significand as an absolute value if (intCompact != INFLATED) { - coeff = new char[19]; - offset = DecimalDigits.getChars(Math.abs(intCompact), coeff.length, coeff); + offset = sbHelper.putIntCompact(Math.abs(intCompact)); + coeff = sbHelper.getCompactCharArray(); } else { offset = 0; coeff = intVal.abs().toString().toCharArray(); @@ -4220,7 +4301,7 @@ private String layoutChars(boolean sci) { // If E-notation is needed, length will be: +1 if negative, +1 // if '.' needed, +2 for "E+", + up to 10 for adjusted exponent. // Otherwise it could have +1 if negative, plus leading "0.00000" - StringBuilder buf = new StringBuilder(32);; + StringBuilder buf = sbHelper.getStringBuilder(); if (signum() < 0) // prefix '-' if negative buf.append('-'); int coeffLen = coeff.length - offset; diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index ecfdbd28095..0436cbb314f 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -487,6 +487,10 @@ public interface JavaLangAccess { */ Object classData(Class c); + int getCharsLatin1(long i, int index, byte[] buf); + + int getCharsUTF16(long i, int index, byte[] buf); + /** * Returns the {@link NativeLibraries} object associated with the provided class loader. * This is used by {@link SymbolLookup#loaderLookup()}. diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 75e67e3f9cc..83438e59b82 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -25,18 +25,14 @@ package jdk.internal.util; -import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.Stable; -import static jdk.internal.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; - /** * Digits class for decimal digits. * * @since 21 */ public final class DecimalDigits { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); /** * Each element of the array represents the packaging of two ascii characters based on little endian:

@@ -80,6 +76,15 @@ public final class DecimalDigits { private DecimalDigits() { } + /** + * For values from 0 to 99 return a short encoding a pair of ASCII-encoded digit characters in little-endian + * @param i value to convert + * @return a short encoding a pair of ASCII-encoded digit characters + */ + public static short digitPair(int i) { + return DIGITS[i]; + } + /** * Returns the string representation size for a given int value. * @@ -131,306 +136,4 @@ public static int stringSize(long x) { } return 19 + d; } - - /** - * Places characters representing the integer i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @implNote This method converts positive inputs into negative - * values, to cover the Integer.MIN_VALUE case. Converting otherwise - * (negative to positive) will expose -Integer.MIN_VALUE that overflows - * integer. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - public static int getCharsLatin1(int i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - int q; - int charPos = index; - - boolean negative = i < 0; - if (!negative) { - i = -i; - } - - // Generate two digits per iteration - while (i <= -100) { - q = i / 100; - charPos -= 2; - putPairLatin1(buf, charPos, (q * 100) - i); - i = q; - } - - // We know there are at most two digits left at this point. - if (i < -9) { - charPos -= 2; - putPairLatin1(buf, charPos, -i); - } else { - putCharLatin1(buf, --charPos, '0' - i); - } - - if (negative) { - putCharLatin1(buf, --charPos, '-'); - } - return charPos; - } - - - /** - * Places characters representing the long i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @implNote This method converts positive inputs into negative - * values, to cover the Long.MIN_VALUE case. Converting otherwise - * (negative to positive) will expose -Long.MIN_VALUE that overflows - * long. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - public static int getCharsLatin1(long i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - putPairLatin1(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - putPairLatin1(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - putPairLatin1(buf, charPos, -i2); - } else { - putCharLatin1(buf, --charPos, '0' - i2); - } - - if (negative) { - putCharLatin1(buf, --charPos, '-'); - } - return charPos; - } - - - /** - * This is a variant of {@link DecimalDigits#getCharsLatin1(int, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - public static int getCharsUTF16(int i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - int q, r; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using ints - while (i <= -100) { - q = i / 100; - r = (q * 100) - i; - i = q; - charPos -= 2; - putPairUTF16(buf, charPos, r); - } - - // We know there are at most two digits left at this point. - if (i < -9) { - charPos -= 2; - putPairUTF16(buf, charPos, -i); - } else { - putCharUTF16(buf, --charPos, '0' - i); - } - - if (negative) { - putCharUTF16(buf, --charPos, '-'); - } - return charPos; - } - - - /** - * This is a variant of {@link DecimalDigits#getCharsLatin1(long, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - public static int getCharsUTF16(long i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - putPairUTF16(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - putPairUTF16(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - putPairUTF16(buf, charPos, -i2); - } else { - putCharUTF16(buf, --charPos, '0' - i2); - } - - if (negative) { - putCharUTF16(buf, --charPos, '-'); - } - return charPos; - } - - /** - * This is a variant of {@link DecimalDigits#getCharsUTF16(long, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - public static int getChars(long i, int index, char[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - putPair(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - putPair(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - putPair(buf, charPos, -i2); - } else { - buf[--charPos] = (char) ('0' - i2); - } - - if (negative) { - buf[--charPos] = '-'; - } - return charPos; - } - - /** - * Insert the 2-chars integer into the buf as 2 decimal digit ASCII chars, - * only least significant 16 bits of {@code v} are used. - * @param buf byte buffer to copy into - * @param charPos insert point - * @param v to convert - */ - public static void putPair(char[] buf, int charPos, int v) { - int packed = DIGITS[v]; - buf[charPos ] = (char) (packed & 0xFF); - buf[charPos + 1] = (char) (packed >> 8); - } - - /** - * Insert the 2-bytes integer into the buf as 2 decimal digit ASCII bytes, - * only least significant 16 bits of {@code v} are used. - * @param buf byte buffer to copy into - * @param charPos insert point - * @param v to convert - */ - public static void putPairLatin1(byte[] buf, int charPos, int v) { - int packed = DIGITS[v]; - putCharLatin1(buf, charPos, packed & 0xFF); - putCharLatin1(buf, charPos + 1, packed >> 8); - } - - /** - * Insert the 2-chars integer into the buf as 2 decimal digit UTF16 bytes, - * only least significant 16 bits of {@code v} are used. - * @param buf byte buffer to copy into - * @param charPos insert point - * @param v to convert - */ - public static void putPairUTF16(byte[] buf, int charPos, int v) { - int packed = DIGITS[v]; - putCharUTF16(buf, charPos, packed & 0xFF); - putCharUTF16(buf, charPos + 1, packed >> 8); - } - - private static void putCharLatin1(byte[] buf, int charPos, int c) { - UNSAFE.putByte(buf, ARRAY_BYTE_BASE_OFFSET + charPos, (byte) c); - } - - private static void putCharUTF16(byte[] buf, int charPos, int c) { - UNSAFE.putChar(buf, ARRAY_BYTE_BASE_OFFSET + (charPos << 1), (char) c); - } } diff --git a/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java b/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java index a60354ec2fc..5ecc01aa2bc 100644 --- a/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java +++ b/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java @@ -23,8 +23,6 @@ package java.lang; -import jdk.internal.util.DecimalDigits; - /** * A helper class to get access to package-private members */ @@ -119,17 +117,11 @@ public static int codePointCountSB(byte[] val, int beginIndex, int endIndex) { } public static int getChars(int i, int begin, int end, byte[] value) { - StringUTF16.checkBoundsBeginEnd(begin, end, value); - int pos = DecimalDigits.getCharsUTF16(i, end, value); - assert begin == pos; - return pos; + return StringUTF16.getChars(i, begin, end, value); } public static int getChars(long l, int begin, int end, byte[] value) { - StringUTF16.checkBoundsBeginEnd(begin, end, value); - int pos = DecimalDigits.getCharsUTF16(l, end, value); - assert begin == pos; - return pos; + return StringUTF16.getChars(l, begin, end, value); } public static boolean contentEquals(byte[] v1, byte[] v2, int len) { diff --git a/test/micro/org/openjdk/bench/java/lang/StringBuilders.java b/test/micro/org/openjdk/bench/java/lang/StringBuilders.java index e41bd361ff5..ed5c0d30db8 100644 --- a/test/micro/org/openjdk/bench/java/lang/StringBuilders.java +++ b/test/micro/org/openjdk/bench/java/lang/StringBuilders.java @@ -54,8 +54,6 @@ public class StringBuilders { private StringBuilder sbLatin2; private StringBuilder sbUtf16; private StringBuilder sbUtf17; - private int[] intsArray; - private long[] longArray; @Setup public void setup() { @@ -71,13 +69,6 @@ public void setup() { sbLatin2 = new StringBuilder("Latin1 string"); sbUtf16 = new StringBuilder("UTF-\uFF11\uFF16 string"); sbUtf17 = new StringBuilder("UTF-\uFF11\uFF16 string"); - int size = 16; - intsArray = new int[size]; - longArray = new long[size]; - for (int i = 0; i < longArray.length; i++) { - intsArray[i] = ((100 * i + i) << 24) + 4543 + i * 4; - longArray[i] = ((100L * i + i) << 32) + 4543 + i * 4L; - } } @Benchmark @@ -233,47 +224,6 @@ public String toStringCharWithInt8() { return result.toString(); } - @Benchmark - public int appendWithIntLatin1() { - StringBuilder buf = sbLatin1; - buf.setLength(0); - for (long l : longArray) { - buf.append(l); - } - return buf.length(); - } - - @Benchmark - public int appendWithIntUtf16() { - StringBuilder buf = sbUtf16; - buf.setLength(0); - buf.setLength(0); - for (long l : longArray) { - buf.append(l); - } - return buf.length(); - } - - @Benchmark - public int appendWithLongLatin1() { - StringBuilder buf = sbLatin1; - buf.setLength(0); - for (long l : longArray) { - buf.append(l); - } - return buf.length(); - } - - @Benchmark - public int appendWithLongUtf16() { - StringBuilder buf = sbUtf16; - buf.setLength(0); - buf.setLength(0); - for (long l : longArray) { - buf.append(l); - } - return buf.length(); - } @Benchmark public int appendWithBool8Latin1() { From 117e63ae675123ab6403d3c3745315a59c401aa5 Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Mon, 11 Nov 2024 15:21:56 +0000 Subject: [PATCH 17/23] 8343442: Add since checker tests to the networking area modules Reviewed-by: jpai, dfuchs --- .../java.net.http/JavaNetHttpCheckSince.java | 30 +++++++++++++++++++ .../JdkHttpServerCheckSince.java | 30 +++++++++++++++++++ .../modules/jdk.net/JdkNetCheckSince.java | 30 +++++++++++++++++++ .../modules/jdk.sctp/JdkSctpCheckSince.java | 30 +++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 test/jdk/tools/sincechecker/modules/java.net.http/JavaNetHttpCheckSince.java create mode 100644 test/jdk/tools/sincechecker/modules/jdk.httpserver/JdkHttpServerCheckSince.java create mode 100644 test/jdk/tools/sincechecker/modules/jdk.net/JdkNetCheckSince.java create mode 100644 test/jdk/tools/sincechecker/modules/jdk.sctp/JdkSctpCheckSince.java diff --git a/test/jdk/tools/sincechecker/modules/java.net.http/JavaNetHttpCheckSince.java b/test/jdk/tools/sincechecker/modules/java.net.http/JavaNetHttpCheckSince.java new file mode 100644 index 00000000000..389bb456439 --- /dev/null +++ b/test/jdk/tools/sincechecker/modules/java.net.http/JavaNetHttpCheckSince.java @@ -0,0 +1,30 @@ +/* + * 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 8343442 + * @summary Test for `@since` in java.net.http module + * @library /test/lib /test/jdk/tools/sincechecker + * @run main SinceChecker java.net.http + */ diff --git a/test/jdk/tools/sincechecker/modules/jdk.httpserver/JdkHttpServerCheckSince.java b/test/jdk/tools/sincechecker/modules/jdk.httpserver/JdkHttpServerCheckSince.java new file mode 100644 index 00000000000..0c846995359 --- /dev/null +++ b/test/jdk/tools/sincechecker/modules/jdk.httpserver/JdkHttpServerCheckSince.java @@ -0,0 +1,30 @@ +/* + * 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 8343442 + * @summary Test for `@since` in jdk.httpserver module + * @library /test/lib /test/jdk/tools/sincechecker + * @run main SinceChecker jdk.httpserver + */ diff --git a/test/jdk/tools/sincechecker/modules/jdk.net/JdkNetCheckSince.java b/test/jdk/tools/sincechecker/modules/jdk.net/JdkNetCheckSince.java new file mode 100644 index 00000000000..b0c5462106e --- /dev/null +++ b/test/jdk/tools/sincechecker/modules/jdk.net/JdkNetCheckSince.java @@ -0,0 +1,30 @@ +/* + * 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 8343442 + * @summary Test for `@since` in jdk.net module + * @library /test/lib /test/jdk/tools/sincechecker + * @run main SinceChecker jdk.net + */ diff --git a/test/jdk/tools/sincechecker/modules/jdk.sctp/JdkSctpCheckSince.java b/test/jdk/tools/sincechecker/modules/jdk.sctp/JdkSctpCheckSince.java new file mode 100644 index 00000000000..921bd5eefc2 --- /dev/null +++ b/test/jdk/tools/sincechecker/modules/jdk.sctp/JdkSctpCheckSince.java @@ -0,0 +1,30 @@ +/* + * 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 8343442 + * @summary Test for `@since` in jdk.sctp module + * @library /test/lib /test/jdk/tools/sincechecker + * @run main SinceChecker jdk.sctp + */ From edb27bcbed8596f352ef93af755a726f147dbf31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Sj=C3=B6len?= Date: Mon, 11 Nov 2024 16:10:57 +0000 Subject: [PATCH 18/23] 8343755: Unproblemlist java/lang/Thread/jni/AttachCurrentThread/AttachTest.java Reviewed-by: alanb, syan --- test/jdk/ProblemList.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 1ae9623ef14..3afcf1a32c9 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -518,8 +518,6 @@ java/lang/invoke/LFCaching/LFMultiThreadCachingTest.java 8151492 generic- java/lang/invoke/LFCaching/LFGarbageCollectedTest.java 8078602 generic-all java/lang/invoke/lambda/LambdaFileEncodingSerialization.java 8249079 linux-all java/lang/invoke/RicochetTest.java 8251969 generic-all -java/lang/Thread/jni/AttachCurrentThread/AttachTest.java#id0 8343244 generic-all -java/lang/Thread/jni/AttachCurrentThread/AttachTest.java#id1 8343244 generic-all ############################################################################ From d17efc54afd7f04bf4ae89fd4c1f720bccdcc47e Mon Sep 17 00:00:00 2001 From: Richard Reingruber Date: Mon, 11 Nov 2024 16:35:22 +0000 Subject: [PATCH 19/23] 8343774: Positive list platforms for ir checks of compiler/c2/TestCastX2NotProcessedIGVN.java Reviewed-by: fyang, amitkumar, roland --- test/hotspot/jtreg/compiler/c2/TestCastX2NotProcessedIGVN.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/compiler/c2/TestCastX2NotProcessedIGVN.java b/test/hotspot/jtreg/compiler/c2/TestCastX2NotProcessedIGVN.java index 6ab45af39c5..086711085b4 100644 --- a/test/hotspot/jtreg/compiler/c2/TestCastX2NotProcessedIGVN.java +++ b/test/hotspot/jtreg/compiler/c2/TestCastX2NotProcessedIGVN.java @@ -62,7 +62,8 @@ public static void test1Runner() { } @Test - @IR(counts = {IRNode.LOAD_VECTOR_I, "> 1"}) + @IR(counts = {IRNode.LOAD_VECTOR_I, "> 1"}, + applyIfPlatformOr = {"x64", "true", "aarch64", "true"}) public static int test2(int stop, int[] array) { int v = 0; stop = Math.min(stop, Integer.MAX_VALUE / 4); From 321e9cb28ebb2d962ade26161253e32ced75987b Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 11 Nov 2024 19:57:26 +0000 Subject: [PATCH 20/23] 8342707: Prepare Gatherers for graduation from Preview Reviewed-by: alanb, liach --- .../share/classes/java/util/stream/Gatherer.java | 13 ++++--------- .../share/classes/java/util/stream/GathererOp.java | 2 +- .../share/classes/java/util/stream/Gatherers.java | 6 ++---- .../share/classes/java/util/stream/Stream.java | 7 ++----- .../classes/jdk/internal/javac/PreviewFeature.java | 1 - test/jdk/java/util/stream/GathererAPITest.java | 1 - .../java/util/stream/GathererShortCircuitTest.java | 1 - test/jdk/java/util/stream/GathererTest.java | 1 - test/jdk/java/util/stream/GatherersFoldTest.java | 1 - .../util/stream/GatherersMapConcurrentTest.java | 1 - test/jdk/java/util/stream/GatherersScanTest.java | 1 - .../java/util/stream/GatherersWindowFixedTest.java | 1 - .../util/stream/GatherersWindowSlidingTest.java | 1 - .../java/util/stream/ops/ref/GatherFMRPar.java | 4 ++-- .../java/util/stream/ops/ref/GatherFMRSeq.java | 4 ++-- .../stream/ops/ref/GatherFlatMapInfinitySeq.java | 4 ++-- .../java/util/stream/ops/ref/GatherFlatMapSeq.java | 4 ++-- .../java/util/stream/ops/ref/GatherMapPar.java | 4 ++-- .../java/util/stream/ops/ref/GatherMapSeq.java | 4 ++-- .../java/util/stream/ops/ref/GatherMiscPar.java | 4 ++-- .../java/util/stream/ops/ref/GatherMiscSeq.java | 4 ++-- .../java/util/stream/ops/ref/GatherReducePar.java | 4 ++-- .../java/util/stream/ops/ref/GatherReduceSeq.java | 4 ++-- .../util/stream/ops/ref/GatherWhileOrdered.java | 4 ++-- 24 files changed, 31 insertions(+), 50 deletions(-) diff --git a/src/java.base/share/classes/java/util/stream/Gatherer.java b/src/java.base/share/classes/java/util/stream/Gatherer.java index 40c3c682e73..267ec62b931 100644 --- a/src/java.base/share/classes/java/util/stream/Gatherer.java +++ b/src/java.base/share/classes/java/util/stream/Gatherer.java @@ -24,7 +24,6 @@ */ package java.util.stream; -import jdk.internal.javac.PreviewFeature; import jdk.internal.vm.annotation.ForceInline; import java.util.*; @@ -195,9 +194,8 @@ * @param the potentially mutable state type of the gatherer operation * (often hidden as an implementation detail) * @param the type of output elements from the gatherer operation - * @since 22 + * @since 24 */ -@PreviewFeature(feature = PreviewFeature.Feature.STREAM_GATHERERS) public interface Gatherer { /** * A function that produces an instance of the intermediate state used for @@ -481,10 +479,9 @@ static Gatherer of( * A Downstream object is the next stage in a pipeline of operations, * to which elements can be sent. * @param the type of elements this downstream accepts - * @since 22 + * @since 24 */ @FunctionalInterface - @PreviewFeature(feature = PreviewFeature.Feature.STREAM_GATHERERS) interface Downstream { /** @@ -524,10 +521,9 @@ interface Downstream { * @param the type of state used by this integrator * @param the type of elements this integrator consumes * @param the type of results this integrator can produce - * @since 22 + * @since 24 */ @FunctionalInterface - @PreviewFeature(feature = PreviewFeature.Feature.STREAM_GATHERERS) interface Integrator { /** * Performs an action given: the current state, the next element, and @@ -584,10 +580,9 @@ static Greedy ofGreedy(Greedy greedy) { * @param the type of state used by this integrator * @param the type of elements this greedy integrator receives * @param the type of results this greedy integrator can produce - * @since 22 + * @since 24 */ @FunctionalInterface - @PreviewFeature(feature = PreviewFeature.Feature.STREAM_GATHERERS) interface Greedy extends Integrator { } } } diff --git a/src/java.base/share/classes/java/util/stream/GathererOp.java b/src/java.base/share/classes/java/util/stream/GathererOp.java index 37f01901201..39758bd834a 100644 --- a/src/java.base/share/classes/java/util/stream/GathererOp.java +++ b/src/java.base/share/classes/java/util/stream/GathererOp.java @@ -45,7 +45,7 @@ * The performance-critical code below contains some more complicated encodings: * therefore, make sure to run benchmarks to verify changes to prevent regressions. * - * @since 22 + * @since 24 */ final class GathererOp extends ReferencePipeline { @SuppressWarnings("unchecked") diff --git a/src/java.base/share/classes/java/util/stream/Gatherers.java b/src/java.base/share/classes/java/util/stream/Gatherers.java index c201ee54609..b394f6fc7d8 100644 --- a/src/java.base/share/classes/java/util/stream/Gatherers.java +++ b/src/java.base/share/classes/java/util/stream/Gatherers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -25,7 +25,6 @@ package java.util.stream; import jdk.internal.access.SharedSecrets; -import jdk.internal.javac.PreviewFeature; import jdk.internal.vm.annotation.ForceInline; import java.util.ArrayDeque; @@ -48,9 +47,8 @@ * operations, such as windowing functions, folding functions, * transforming elements concurrently, etc. * - * @since 22 + * @since 24 */ -@PreviewFeature(feature = PreviewFeature.Feature.STREAM_GATHERERS) public final class Gatherers { private Gatherers() { } // This class is not intended to be instantiated diff --git a/src/java.base/share/classes/java/util/stream/Stream.java b/src/java.base/share/classes/java/util/stream/Stream.java index 30b87bd8b54..1dd13133fe1 100644 --- a/src/java.base/share/classes/java/util/stream/Stream.java +++ b/src/java.base/share/classes/java/util/stream/Stream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -24,8 +24,6 @@ */ package java.util.stream; -import jdk.internal.javac.PreviewFeature; - import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -1096,9 +1094,8 @@ U reduce(U identity, * @param The element type of the new stream * @param gatherer a gatherer * @return the new stream - * @since 22 + * @since 24 */ - @PreviewFeature(feature = PreviewFeature.Feature.STREAM_GATHERERS) default Stream gather(Gatherer gatherer) { return StreamSupport.stream(spliterator(), isParallel()) .gather(gatherer) diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 483093e66eb..4b2a0629706 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -76,7 +76,6 @@ public enum Feature { STRUCTURED_CONCURRENCY, @JEP(number=466, title="ClassFile API", status="Second Preview") CLASSFILE_API, - @JEP(number=473, title="Stream Gatherers", status="Second Preview") STREAM_GATHERERS, @JEP(number=476, title="Module Import Declarations", status="Preview") MODULE_IMPORTS, diff --git a/test/jdk/java/util/stream/GathererAPITest.java b/test/jdk/java/util/stream/GathererAPITest.java index 4bcda85ccbc..364d3065acb 100644 --- a/test/jdk/java/util/stream/GathererAPITest.java +++ b/test/jdk/java/util/stream/GathererAPITest.java @@ -35,7 +35,6 @@ /** * @test * @summary Testing public API of Gatherer - * @enablePreview * @run junit GathererAPITest */ diff --git a/test/jdk/java/util/stream/GathererShortCircuitTest.java b/test/jdk/java/util/stream/GathererShortCircuitTest.java index 059d90a6e7f..7e755dbe23c 100644 --- a/test/jdk/java/util/stream/GathererShortCircuitTest.java +++ b/test/jdk/java/util/stream/GathererShortCircuitTest.java @@ -32,7 +32,6 @@ * @test * @bug 8328316 * @summary Testing Gatherer behavior under short circuiting - * @enablePreview * @run junit GathererShortCircuitTest */ diff --git a/test/jdk/java/util/stream/GathererTest.java b/test/jdk/java/util/stream/GathererTest.java index 3d8e608cc6c..7a03c0b35ed 100644 --- a/test/jdk/java/util/stream/GathererTest.java +++ b/test/jdk/java/util/stream/GathererTest.java @@ -37,7 +37,6 @@ /** * @test * @summary Testing the Gatherer contract - * @enablePreview * @library /lib/testlibrary/bootlib * @build java.base/java.util.stream.DefaultMethodStreams * @run junit GathererTest diff --git a/test/jdk/java/util/stream/GatherersFoldTest.java b/test/jdk/java/util/stream/GatherersFoldTest.java index caecff19e3a..0e0535882f9 100644 --- a/test/jdk/java/util/stream/GatherersFoldTest.java +++ b/test/jdk/java/util/stream/GatherersFoldTest.java @@ -34,7 +34,6 @@ /** * @test * @summary Tests the API and contract of Gatherers.fold - * @enablePreview * @run junit GatherersFoldTest */ diff --git a/test/jdk/java/util/stream/GatherersMapConcurrentTest.java b/test/jdk/java/util/stream/GatherersMapConcurrentTest.java index 5ff845f55b9..f7cc1c42a3c 100644 --- a/test/jdk/java/util/stream/GatherersMapConcurrentTest.java +++ b/test/jdk/java/util/stream/GatherersMapConcurrentTest.java @@ -38,7 +38,6 @@ /** * @test * @summary Tests the API and contract of Gatherers.mapConcurrent - * @enablePreview * @run junit GatherersMapConcurrentTest */ diff --git a/test/jdk/java/util/stream/GatherersScanTest.java b/test/jdk/java/util/stream/GatherersScanTest.java index 993a616233c..51b7c0ef0eb 100644 --- a/test/jdk/java/util/stream/GatherersScanTest.java +++ b/test/jdk/java/util/stream/GatherersScanTest.java @@ -35,7 +35,6 @@ /** * @test * @summary Tests the API and contract of Gatherers.scan - * @enablePreview * @run junit GatherersScanTest */ diff --git a/test/jdk/java/util/stream/GatherersWindowFixedTest.java b/test/jdk/java/util/stream/GatherersWindowFixedTest.java index 69426dfd768..cddfee1b098 100644 --- a/test/jdk/java/util/stream/GatherersWindowFixedTest.java +++ b/test/jdk/java/util/stream/GatherersWindowFixedTest.java @@ -36,7 +36,6 @@ /** * @test * @summary Tests the API and contract of Gatherers.windowFixed - * @enablePreview * @run junit GatherersWindowFixedTest */ diff --git a/test/jdk/java/util/stream/GatherersWindowSlidingTest.java b/test/jdk/java/util/stream/GatherersWindowSlidingTest.java index 27ed6ecc4dd..29ffa54e882 100644 --- a/test/jdk/java/util/stream/GatherersWindowSlidingTest.java +++ b/test/jdk/java/util/stream/GatherersWindowSlidingTest.java @@ -36,7 +36,6 @@ /** * @test * @summary Tests the API and contract of Gatherers.windowSliding - * @enablePreview * @run junit GatherersWindowSlidingTest */ diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFMRPar.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFMRPar.java index cd83ab86034..91e09c2a077 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFMRPar.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFMRPar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -49,7 +49,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherFMRPar { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFMRSeq.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFMRSeq.java index a1973a45831..639d4290089 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFMRSeq.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFMRSeq.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -49,7 +49,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherFMRSeq { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFlatMapInfinitySeq.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFlatMapInfinitySeq.java index 2b804551dbf..119f3917da3 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFlatMapInfinitySeq.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFlatMapInfinitySeq.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -48,7 +48,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherFlatMapInfinitySeq { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFlatMapSeq.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFlatMapSeq.java index 31c1f047c12..cb5e1443f1d 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFlatMapSeq.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherFlatMapSeq.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -48,7 +48,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherFlatMapSeq { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMapPar.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMapPar.java index b7c60af6cdd..de0be6d86f1 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMapPar.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMapPar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -48,7 +48,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherMapPar { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMapSeq.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMapSeq.java index 1dbbbd05009..1c6f7acdfa5 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMapSeq.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMapSeq.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -48,7 +48,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherMapSeq { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMiscPar.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMiscPar.java index 12f5c98702b..0f8e1ef2fad 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMiscPar.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMiscPar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -49,7 +49,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherMiscPar { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMiscSeq.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMiscSeq.java index a60d02af7b6..c6382111287 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMiscSeq.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherMiscSeq.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -51,7 +51,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherMiscSeq { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherReducePar.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherReducePar.java index 6742ee98590..097ffa283b5 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherReducePar.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherReducePar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -49,7 +49,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherReducePar { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherReduceSeq.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherReduceSeq.java index 7356fdb979b..b460143d32b 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherReduceSeq.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherReduceSeq.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -51,7 +51,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherReduceSeq { diff --git a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherWhileOrdered.java b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherWhileOrdered.java index f0b3fef4438..f93ca5454c7 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherWhileOrdered.java +++ b/test/micro/org/openjdk/bench/java/util/stream/ops/ref/GatherWhileOrdered.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -49,7 +49,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) -@Fork(jvmArgs = "--enable-preview", value = 1) +@Fork(value = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class GatherWhileOrdered { From 26f6bef26fd21fb5c051dcf36c68ecc54f152c86 Mon Sep 17 00:00:00 2001 From: "Keith W. Campbell" Date: Fri, 8 Nov 2024 16:34:45 -0500 Subject: [PATCH 21/23] Propagate DEBUG_LEVEL to OpenJ9 VM Signed-off-by: Keith W. Campbell --- closed/OpenJ9.gmk | 1 + closed/openj9_version_info.h.in | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/closed/OpenJ9.gmk b/closed/OpenJ9.gmk index 6dfc9a7cc4a..aa7b6d0a887 100644 --- a/closed/OpenJ9.gmk +++ b/closed/OpenJ9.gmk @@ -358,6 +358,7 @@ stage-j9 : OPENJ9_VERSION_VARS := \ COMPANY_NAME \ COMPILER_VERSION_STRING \ + DEBUG_LEVEL \ J9JDK_EXT_NAME \ J9JDK_EXT_VERSION \ OPENJ9_TAG \ diff --git a/closed/openj9_version_info.h.in b/closed/openj9_version_info.h.in index 8ef9b786070..20aa0e81e9f 100644 --- a/closed/openj9_version_info.h.in +++ b/closed/openj9_version_info.h.in @@ -1,8 +1,7 @@ /* * =========================================================================== - * (c) Copyright IBM Corp. 2017, 2022 All Rights Reserved + * (c) Copyright IBM Corp. 2017, 2024 All Rights Reserved * =========================================================================== - * * 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. @@ -39,4 +38,6 @@ #define J9JDK_EXT_NAME "@J9JDK_EXT_NAME@" #define JAVA_VENDOR "@COMPANY_NAME@" +#define JDK_DEBUG_LEVEL "@DEBUG_LEVEL@" + #endif /* OPENJ9_VERSION_INFO_H */ From 990479fb260699e5ed478fcdfc3018b6e58a0a89 Mon Sep 17 00:00:00 2001 From: "Keith W. Campbell" Date: Thu, 12 Sep 2024 07:56:52 -0400 Subject: [PATCH 22/23] Support new properties in OpenJ9PropsExt systemd.support introduced upstream in * 8333446: Add tests for hierarchical container support jlink.packagedModules introduced upstream in * 8311302: Implement JEP 493: Linking Run-Time Images without JMODs Signed-off-by: Keith W. Campbell --- .../jtreg-ext/requires/OpenJ9PropsExt.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/closed/test/jtreg-ext/requires/OpenJ9PropsExt.java b/closed/test/jtreg-ext/requires/OpenJ9PropsExt.java index bc8a9830b4c..88308515e71 100644 --- a/closed/test/jtreg-ext/requires/OpenJ9PropsExt.java +++ b/closed/test/jtreg-ext/requires/OpenJ9PropsExt.java @@ -22,10 +22,14 @@ */ package requires; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import jdk.test.lib.Platform; public class OpenJ9PropsExt implements Callable> { @@ -34,6 +38,8 @@ public Map call() { Map map = new HashMap<>(); try { map.put("container.support", "true"); + map.put("jlink.packagedModules", Boolean.toString(packagedModules())); + map.put("systemd.support", Boolean.toString(systemdSupport())); map.put("vm.bits", vmBits()); map.put("vm.cds", "false"); map.put("vm.cds.write.archived.java.heap", "false"); @@ -62,10 +68,39 @@ public Map call() { return map; } + /** + * @return whether the current SDK includes packaged modules + */ + private static boolean packagedModules() { + try { + return Path.of(System.getProperty("java.home"), "jmods").toFile().exists(); + } catch (Throwable t) { + return false; + } + } + + /** + * @return whether systemd is available on the current platform + */ + private static boolean systemdSupport() { + if (Platform.isLinux()) { + try { + Process probe = new ProcessBuilder("which", "systemd-run").start(); + probe.waitFor(10, TimeUnit.SECONDS); + if (probe.exitValue() == 0) { + return true; + } + } catch (Exception e) { + // assume not supported + } + } + return false; + } + /** * @return VM bitness, the value of the "sun.arch.data.model" property. */ - protected String vmBits() throws Exception { + private static String vmBits() throws Exception { String dataModel = System.getProperty("sun.arch.data.model"); if (dataModel != null) { return dataModel; From 354ae2bf105efce231f7d9abd43da0f07b04fe97 Mon Sep 17 00:00:00 2001 From: Nathan Henderson Date: Tue, 12 Nov 2024 15:35:21 -0500 Subject: [PATCH 23/23] Remove synchronized block in Thread isDead method Related: https://github.com/eclipse-openj9/openj9/pull/20415 Related: https://github.com/eclipse-openj9/openj9/issues/20414 Signed-off-by: Nathan Henderson --- src/java.base/share/classes/java/lang/Thread.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Thread.java b/src/java.base/share/classes/java/lang/Thread.java index 46f87bdf369..23a9734a168 100644 --- a/src/java.base/share/classes/java/lang/Thread.java +++ b/src/java.base/share/classes/java/lang/Thread.java @@ -3149,10 +3149,8 @@ private Thread(String vmName, Object vmThreadGroup, int vmPriority, boolean vmIs } private boolean isDead() { - // Has already started, is not alive anymore, and has been removed from the ThreadGroup - synchronized (interruptLock) { - return (started && (eetop == NO_REF)); - } + /* Has already started and is not alive anymore. */ + return started && (eetop == NO_REF); } Thread(Runnable runnable, String threadName, boolean isSystemThreadGroup, boolean inheritThreadLocals, boolean isDaemon, ClassLoader contextClassLoader) {