From 74ae3c688b37e693e20eb4e17c631897c5464400 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 11 Nov 2024 00:40:26 +0000 Subject: [PATCH 01/18] 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 d0c558ed93a1a..b635d0dee0ffe 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 7b0f273e37625461baa333a3ef20fbbd93647243 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Mon, 11 Nov 2024 01:40:10 +0000 Subject: [PATCH 02/18] 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 1ae9623ef14c2..4b483a4b076be 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 ca69a53b76bf0106443985a1207fbd3d9036e1ab Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Mon, 11 Nov 2024 04:31:56 +0000 Subject: [PATCH 03/18] 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 0bb1daed28a1a..4f9a6426dff20 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 e1d684c6451ae7875b61a92603c19336ccd734d6 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 11 Nov 2024 05:06:56 +0000 Subject: [PATCH 04/18] 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 b40f627441230..fd9dcf60e540e 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 e666e977c61a5..5f73d61e5d472 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 8c083b3ec8431..7df9ddfb2708b 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 b635d0dee0ffe..632fe0f58b58b 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 c12b8afc21f37..abe42c2c7c72f 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 f04b991827f7f..a1dcca8ffad18 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 5b04bca4f44af..451ed8e6bfc59 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 abd49aa69bccf..b00970963b6b3 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 0436cbb314f8e..ecfdbd28095d5 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 83438e59b8276..75e67e3f9cc90 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 5ecc01aa2bc29..a60354ec2fce2 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 ed5c0d30db866..e41bd361ff5f3 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 5f338e9adbcf7fe7ee90abfd34a24a3a93c22211 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Mon, 11 Nov 2024 06:18:25 +0000 Subject: [PATCH 05/18] 8342946: Replace predicate walking code in Loop Unrolling with a predicate visitor Reviewed-by: roland, kvn --- src/hotspot/share/opto/loopTransform.cpp | 50 ++------- src/hotspot/share/opto/loopnode.hpp | 2 +- src/hotspot/share/opto/predicates.cpp | 130 ++++++++++++++++++++++- src/hotspot/share/opto/predicates.hpp | 34 ++++++ 4 files changed, 174 insertions(+), 42 deletions(-) diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index b8150ab302e0a..06c43697350e2 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -1779,47 +1779,19 @@ bool IdealLoopTree::is_invariant(Node* n) const { // Search the Assertion Predicates added by loop predication and/or range check elimination and update them according // to the new stride. -void PhaseIdealLoop::update_main_loop_assertion_predicates(Node* ctrl, CountedLoopNode* loop_head, Node* init, - const int stride_con) { - Node* entry = ctrl; - Node* prev_proj = ctrl; - LoopNode* outer_loop_head = loop_head->skip_strip_mined(); - IdealLoopTree* outer_loop = get_loop(outer_loop_head); +void PhaseIdealLoop::update_main_loop_assertion_predicates(CountedLoopNode* main_loop_head) { + Node* init = main_loop_head->init_trip(); // Compute the value of the loop induction variable at the end of the // first iteration of the unrolled loop: init + new_stride_con - init_inc - int new_stride_con = stride_con * 2; - Node* max_value = _igvn.intcon(new_stride_con); - set_ctrl(max_value, C->root()); - - while (entry != nullptr && entry->is_Proj() && entry->in(0)->is_If()) { - IfNode* iff = entry->in(0)->as_If(); - ProjNode* proj = iff->proj_out(1 - entry->as_Proj()->_con); - if (!proj->unique_ctrl_out()->is_Halt()) { - break; - } - Node* bol = iff->in(1); - if (bol->is_OpaqueTemplateAssertionPredicate()) { - assert(assertion_predicate_has_loop_opaque_node(iff), "must find OpaqueLoop* nodes"); - // This is a Template Assertion Predicate for the initial or last access. - // Create an Initialized Assertion Predicates for it accordingly: - // - For the initial access a[init] (same as before) - // - For the last access a[init+new_stride-orig_stride] (with the new unroll stride) - prev_proj = create_initialized_assertion_predicate(iff, init, max_value, prev_proj); - } else if (bol->is_OpaqueInitializedAssertionPredicate()) { - // This is one of the two Initialized Assertion Predicates: - // - For the initial access a[init] - // - For the last access a[init+old_stride-orig_stride] - // We could keep the one for the initial access but we do not know which one we currently have here. Just kill both. - _igvn.replace_input_of(iff, 1, _igvn.intcon(1)); - } - assert(!bol->is_OpaqueNotNull() || !loop_head->is_main_loop(), "OpaqueNotNull should not be at main loop"); - entry = entry->in(0)->in(0); - } - if (prev_proj != ctrl) { - _igvn.replace_input_of(outer_loop_head, LoopNode::EntryControl, prev_proj); - set_idom(outer_loop_head, prev_proj, dom_depth(outer_loop_head)); - } + int unrolled_stride_con = main_loop_head->stride_con() * 2; + Node* unrolled_stride = _igvn.intcon(unrolled_stride_con); + set_ctrl(unrolled_stride, C->root()); + + Node* loop_entry = main_loop_head->skip_strip_mined()->in(LoopNode::EntryControl); + PredicateIterator predicate_iterator(loop_entry); + UpdateStrideForAssertionPredicates update_stride_for_assertion_predicates(unrolled_stride, this); + predicate_iterator.for_each(update_stride_for_assertion_predicates); } // Source Loop: Cloned - peeled_loop_head @@ -1936,7 +1908,7 @@ void PhaseIdealLoop::do_unroll(IdealLoopTree *loop, Node_List &old_new, bool adj assert(old_trip_count > 1 && (!adjust_min_trip || stride_p <= MIN2(max_jint / 2 - 2, MAX2(1<<3, Matcher::max_vector_size(T_BYTE)) * loop_head->unrolled_count())), "sanity"); - update_main_loop_assertion_predicates(ctrl, loop_head, init, stride_con); + update_main_loop_assertion_predicates(loop_head); // Adjust loop limit to keep valid iterations number after unroll. // Use (limit - stride) instead of (((limit - init)/stride) & (-2))*stride diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index d4f8e2e254a12..a28ccd628e8ac 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -948,7 +948,7 @@ class PhaseIdealLoop : public PhaseTransform { private: DEBUG_ONLY(static void count_opaque_loop_nodes(Node* n, uint& init, uint& stride);) static void get_assertion_predicates(ParsePredicateSuccessProj* parse_predicate_proj, Unique_Node_List& list, bool get_opaque = false); - void update_main_loop_assertion_predicates(Node* ctrl, CountedLoopNode* loop_head, Node* init, int stride_con); + void update_main_loop_assertion_predicates(CountedLoopNode* main_loop_head); void initialize_assertion_predicates_for_peeled_loop(CountedLoopNode* peeled_loop_head, CountedLoopNode* remaining_loop_head, uint first_node_index_in_cloned_loop_body, diff --git a/src/hotspot/share/opto/predicates.cpp b/src/hotspot/share/opto/predicates.cpp index ee21806356709..e8287299d0da7 100644 --- a/src/hotspot/share/opto/predicates.cpp +++ b/src/hotspot/share/opto/predicates.cpp @@ -152,7 +152,6 @@ void TemplateAssertionPredicate::rewire_loop_data_dependencies(IfTrueNode* targe } } - // Template Assertion Predicates always have the dedicated OpaqueTemplateAssertionPredicate to identify them. bool TemplateAssertionPredicate::is_predicate(Node* node) { if (!may_be_assertion_predicate_if(node)) { @@ -179,6 +178,24 @@ IfTrueNode* TemplateAssertionPredicate::clone_and_replace_init(Node* new_control return success_proj; } +// Replace the input to OpaqueLoopStrideNode with 'new_stride' and leave the other nodes unchanged. +void TemplateAssertionPredicate::replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn) const { + TemplateAssertionExpression expression(opaque_node()); + expression.replace_opaque_stride_input(new_stride, igvn); +} + +// Create a new Initialized Assertion Predicate from this template at 'new_control' and return the success projection +// of the newly created Initialized Assertion Predicate. +IfTrueNode* TemplateAssertionPredicate::initialize(PhaseIdealLoop* phase, Node* new_control) const { + assert(phase->assertion_predicate_has_loop_opaque_node(head()), + "must find OpaqueLoop* nodes for Template Assertion Predicate"); + InitializedAssertionPredicateCreator initialized_assertion_predicate(phase); + IfTrueNode* success_proj = initialized_assertion_predicate.create_from_template(head(), new_control); + assert(!phase->assertion_predicate_has_loop_opaque_node(success_proj->in(0)->as_If()), + "Initialized Assertion Predicates do not have OpaqueLoop* nodes in the bool expression anymore"); + return success_proj; +} + // Initialized Assertion Predicates always have the dedicated OpaqueInitiailizedAssertionPredicate node to identify // them. bool InitializedAssertionPredicate::is_predicate(Node* node) { @@ -189,6 +206,12 @@ bool InitializedAssertionPredicate::is_predicate(Node* node) { return if_node->in(1)->is_OpaqueInitializedAssertionPredicate(); } +void InitializedAssertionPredicate::kill(PhaseIdealLoop* phase) const { + Node* true_con = phase->igvn().intcon(1); + phase->set_ctrl(true_con, phase->C->root()); + phase->igvn().replace_input_of(_if_node, 1, true_con); +} + #ifdef ASSERT // Check that the block has at most one Parse Predicate and that we only find Regular Predicate nodes (i.e. IfProj, // If, or RangeCheck nodes). @@ -388,6 +411,63 @@ TemplateAssertionExpression::clone(const TransformStrategyForOpaqueLoopNodes& tr return opaque_node_clone->as_OpaqueTemplateAssertionPredicate(); } +// This class is used to replace the input to OpaqueLoopStrideNode with a new node while leaving the other nodes +// unchanged. +class ReplaceOpaqueStrideInput : public StackObj { + PhaseIterGVN& _igvn; + Unique_Node_List _nodes_to_visit; + + public: + ReplaceOpaqueStrideInput(OpaqueTemplateAssertionPredicateNode* start_node, PhaseIterGVN& igvn) : _igvn(igvn) { + _nodes_to_visit.push(start_node); + } + NONCOPYABLE(ReplaceOpaqueStrideInput); + + void replace(Node* new_opaque_stride_input) { + for (uint i = 0; i < _nodes_to_visit.size(); i++) { + Node* next = _nodes_to_visit[i]; + for (uint j = 1; j < next->req(); j++) { + Node* input = next->in(j); + if (input->is_OpaqueLoopStride()) { + assert(TemplateAssertionExpressionNode::is_maybe_in_expression(input), "must also pass node filter"); + _igvn.replace_input_of(input, 1, new_opaque_stride_input); + } else if (TemplateAssertionExpressionNode::is_maybe_in_expression(input)) { + _nodes_to_visit.push(input); + } + } + } + } +}; + +// Replace the input to OpaqueLoopStrideNode with 'new_stride' and leave the other nodes unchanged. +void TemplateAssertionExpression::replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn) { + ReplaceOpaqueStrideInput replace_opaque_stride_input(_opaque_node, igvn); + replace_opaque_stride_input.replace(new_stride); +} + +// The transformations of this class fold the OpaqueLoop* nodes by returning their inputs. +class RemoveOpaqueLoopNodesStrategy : public TransformStrategyForOpaqueLoopNodes { + public: + Node* transform_opaque_init(OpaqueLoopInitNode* opaque_init) const override { + return opaque_init->in(1); + } + + Node* transform_opaque_stride(OpaqueLoopStrideNode* opaque_stride) const override { + return opaque_stride->in(1); + } +}; + +OpaqueInitializedAssertionPredicateNode* +TemplateAssertionExpression::clone_and_fold_opaque_loop_nodes(Node* new_control, PhaseIdealLoop* phase) { + RemoveOpaqueLoopNodesStrategy remove_opaque_loop_nodes_strategy; + OpaqueTemplateAssertionPredicateNode* cloned_template_opaque = clone(remove_opaque_loop_nodes_strategy, new_control, + phase); + OpaqueInitializedAssertionPredicateNode* opaque_initialized_opaque = + new OpaqueInitializedAssertionPredicateNode(cloned_template_opaque->in(1)->as_Bool(), phase->C); + phase->register_new_node(opaque_initialized_opaque, new_control); + return opaque_initialized_opaque; +} + // Check if this node belongs a Template Assertion Expression (including OpaqueLoop* nodes). bool TemplateAssertionExpressionNode::is_in_expression(Node* node) { if (is_maybe_in_expression(node)) { @@ -664,6 +744,19 @@ IfTrueNode* InitializedAssertionPredicateCreator::create_from_template(IfNode* t NOT_PRODUCT(COMMA template_assertion_predicate->assertion_predicate_type())); } +// Create a new Initialized Assertion Predicate from 'template_assertion_predicate' by cloning it but omitting the +// OpaqueLoop*Notes (i.e. taking their inputs instead). +IfTrueNode* InitializedAssertionPredicateCreator::create_from_template(IfNode* template_assertion_predicate, + Node* new_control) { + OpaqueTemplateAssertionPredicateNode* template_opaque = + template_assertion_predicate->in(1)->as_OpaqueTemplateAssertionPredicate(); + TemplateAssertionExpression template_assertion_expression(template_opaque); + OpaqueInitializedAssertionPredicateNode* assertion_expression = + template_assertion_expression.clone_and_fold_opaque_loop_nodes(new_control, _phase); + return create_control_nodes(new_control, template_assertion_predicate->Opcode(), assertion_expression + NOT_PRODUCT(COMMA template_assertion_predicate->assertion_predicate_type())); +} + // Create a new Initialized Assertion Predicate directly without a template. IfTrueNode* InitializedAssertionPredicateCreator::create(Node* operand, Node* new_control, const jint stride, const int scale, Node* offset, Node* range NOT_PRODUCT(COMMA @@ -768,7 +861,7 @@ void CreateAssertionPredicatesVisitor::visit(const TemplateAssertionPredicate& t // Create an Initialized Assertion Predicate from the provided Template Assertion Predicate. IfTrueNode* CreateAssertionPredicatesVisitor::initialize_from_template( -const TemplateAssertionPredicate& template_assertion_predicate) const { + const TemplateAssertionPredicate& template_assertion_predicate) const { IfNode* template_head = template_assertion_predicate.head(); IfTrueNode* initialized_predicate = _phase->create_initialized_assertion_predicate(template_head, _init, _stride, _new_control); @@ -783,3 +876,36 @@ IfTrueNode* CreateAssertionPredicatesVisitor::clone_template_and_replace_init_in _phase->register_new_node(opaque_init, _new_control); return template_assertion_predicate.clone_and_replace_init(_new_control, opaque_init, _phase); } + +// Clone the Template Assertion Predicate and set a new input for the OpaqueLoopStrideNode. +void UpdateStrideForAssertionPredicates::visit(const TemplateAssertionPredicate& template_assertion_predicate) { + replace_opaque_stride_input(template_assertion_predicate); + Node* template_tail_control_out = template_assertion_predicate.tail()->unique_ctrl_out(); + IfTrueNode* initialized_success_proj = initialize_from_updated_template(template_assertion_predicate); + connect_initialized_assertion_predicate(template_tail_control_out, initialized_success_proj); +} + +// Replace the input to OpaqueLoopStrideNode with 'new_stride' and leave the other nodes unchanged. +void UpdateStrideForAssertionPredicates::replace_opaque_stride_input( + const TemplateAssertionPredicate& template_assertion_predicate) const { + template_assertion_predicate.replace_opaque_stride_input(_new_stride, _phase->igvn()); +} + +IfTrueNode* UpdateStrideForAssertionPredicates::initialize_from_updated_template( + const TemplateAssertionPredicate& template_assertion_predicate) const { + IfTrueNode* initialized_success_proj = template_assertion_predicate.initialize(_phase, template_assertion_predicate.tail()); + return initialized_success_proj; +} + +// The newly created Initialized Assertion Predicate can safely be inserted because this visitor is already visiting +// the Template Assertion Predicate above this. So, we will not accidentally visit this again and kill it with the +// visit() method for Initialized Assertion Predicates. +void UpdateStrideForAssertionPredicates::connect_initialized_assertion_predicate( + Node* new_control_out, IfTrueNode* initialized_success_proj) const { + if (new_control_out->is_Loop()) { + _phase->igvn().replace_input_of(new_control_out, LoopNode::EntryControl, initialized_success_proj); + } else { + _phase->igvn().replace_input_of(new_control_out, 0, initialized_success_proj); + } + _phase->set_idom(new_control_out, initialized_success_proj, _phase->dom_depth(new_control_out)); +} diff --git a/src/hotspot/share/opto/predicates.hpp b/src/hotspot/share/opto/predicates.hpp index dc834a18399d2..d7171db7115cf 100644 --- a/src/hotspot/share/opto/predicates.hpp +++ b/src/hotspot/share/opto/predicates.hpp @@ -404,6 +404,8 @@ class TemplateAssertionPredicate : public Predicate { } IfTrueNode* clone_and_replace_init(Node* new_control, OpaqueLoopInitNode* new_opaque_init, PhaseIdealLoop* phase) const; + void replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn) const; + IfTrueNode* initialize(PhaseIdealLoop* phase, Node* new_control) const; void rewire_loop_data_dependencies(IfTrueNode* target_predicate, const NodeInLoopBody& data_in_loop_body, PhaseIdealLoop* phase) const; static bool is_predicate(Node* node); @@ -434,6 +436,7 @@ class InitializedAssertionPredicate : public Predicate { return _success_proj; } + void kill(PhaseIdealLoop* phase) const; static bool is_predicate(Node* node); }; @@ -461,6 +464,8 @@ class TemplateAssertionExpression : public StackObj { OpaqueTemplateAssertionPredicateNode* clone_and_replace_init(Node* new_init, Node* new_ctrl, PhaseIdealLoop* phase); OpaqueTemplateAssertionPredicateNode* clone_and_replace_init_and_stride(Node* new_control, Node* new_init, Node* new_stride, PhaseIdealLoop* phase); + void replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn); + OpaqueInitializedAssertionPredicateNode* clone_and_fold_opaque_loop_nodes(Node* new_ctrl, PhaseIdealLoop* phase); }; // Class to represent a node being part of a Template Assertion Expression. Note that this is not an IR node. @@ -608,6 +613,7 @@ class InitializedAssertionPredicateCreator : public StackObj { IfTrueNode* create_from_template(IfNode* template_assertion_predicate, Node* new_control, Node* new_init, Node* new_stride); + IfTrueNode* create_from_template(IfNode* template_assertion_predicate, Node* new_control); IfTrueNode* create(Node* operand, Node* new_control, jint stride, int scale, Node* offset, Node* range NOT_PRODUCT(COMMA AssertionPredicateType assertion_predicate_type)); @@ -1032,4 +1038,32 @@ class TemplateAssertionPredicateCollector : public PredicateVisitor { } }; +// This visitor updates the stride for an Assertion Predicate during Loop Unrolling. The inputs to the OpaqueLoopStride +// nodes Template of Template Assertion Predicates are updated and new Initialized Assertion Predicates are created +// from the updated templates. The old Initialized Assertion Predicates are killed. +class UpdateStrideForAssertionPredicates : public PredicateVisitor { + Node* const _new_stride; + PhaseIdealLoop* const _phase; + + void replace_opaque_stride_input(const TemplateAssertionPredicate& template_assertion_predicate) const; + IfTrueNode* initialize_from_updated_template(const TemplateAssertionPredicate& template_assertion_predicate) const; + void connect_initialized_assertion_predicate(Node* new_control_out, IfTrueNode* initialized_success_proj) const; + + public: + UpdateStrideForAssertionPredicates(Node* const new_stride, PhaseIdealLoop* phase) + : _new_stride(new_stride), + _phase(phase) {} + NONCOPYABLE(UpdateStrideForAssertionPredicates); + + using PredicateVisitor::visit; + + void visit(const TemplateAssertionPredicate& template_assertion_predicate) override; + + // Kill the old Initialized Assertion Predicates with old strides before unrolling. The new Initialized Assertion + // Predicates are inserted after the Template Assertion Predicate which ensures that we are not accidentally visiting + // and killing a newly created Initialized Assertion Predicate here. + void visit(const InitializedAssertionPredicate& initialized_assertion_predicate) override { + initialized_assertion_predicate.kill(_phase); + } +}; #endif // SHARE_OPTO_PREDICATES_HPP From 5b2f7f3b30adf9942fa8a3382e7661d6816fbb38 Mon Sep 17 00:00:00 2001 From: theoweidmannoracle Date: Mon, 11 Nov 2024 08:17:00 +0000 Subject: [PATCH 06/18] 8343837: Remove unnecessary reinterpret_cast from C2AccessValuePtr Reviewed-by: thartmann, kvn, kbarrett --- src/hotspot/share/gc/shared/c2/barrierSetC2.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp index 00fbf1f2c9f8b..a78fd434ad94e 100644 --- a/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp +++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -92,9 +92,9 @@ class C2AccessValuePtr: public C2AccessValue { public: C2AccessValuePtr(Node* node, const TypePtr* type) : - C2AccessValue(node, reinterpret_cast(type)) {} + C2AccessValue(node, type) {} - const TypePtr* type() const { return reinterpret_cast(_type); } + const TypePtr* type() const { return _type->is_ptr(); } }; // This class wraps a bunch of context parameters that are passed around in the From ae6bb3cd29bd4cdbb2df320fbfe0dabb7c0647d7 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 11 Nov 2024 09:08:00 +0000 Subject: [PATCH 07/18] 8343824: Remove unused InstructionFlags in C1 Reviewed-by: kvn, dlong --- src/hotspot/share/c1/c1_Instruction.hpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/hotspot/share/c1/c1_Instruction.hpp b/src/hotspot/share/c1/c1_Instruction.hpp index e577e7fd90bad..e950afc981d19 100644 --- a/src/hotspot/share/c1/c1_Instruction.hpp +++ b/src/hotspot/share/c1/c1_Instruction.hpp @@ -346,11 +346,8 @@ class Instruction: public CompilationResourceObj { NeedsNullCheckFlag = 0, CanTrapFlag, DirectCompareFlag, - IsEliminatedFlag, IsSafepointFlag, IsStaticFlag, - NeedsStoreCheckFlag, - NeedsWriteBarrierFlag, PreservesStateFlag, TargetIsFinalFlag, TargetIsLoadedFlag, @@ -361,7 +358,6 @@ class Instruction: public CompilationResourceObj { ProfileMDOFlag, IsLinkedInBlockFlag, NeedsRangeCheckFlag, - InWorkListFlag, DeoptimizeOnException, KillsMemoryFlag, OmitChecksFlag, @@ -840,14 +836,12 @@ LEAF(StoreField, AccessField) : AccessField(obj, offset, field, is_static, state_before, needs_patching) , _value(value) { - set_flag(NeedsWriteBarrierFlag, as_ValueType(field_type())->is_object()); ASSERT_VALUES pin(); } // accessors Value value() const { return _value; } - bool needs_write_barrier() const { return check_flag(NeedsWriteBarrierFlag); } // generic virtual void input_values_do(ValueVisitor* f) { AccessField::input_values_do(f); f->visit(&_value); } @@ -974,16 +968,12 @@ LEAF(StoreIndexed, AccessIndexed) : AccessIndexed(array, index, length, elt_type, state_before, mismatched) , _value(value), _profiled_method(nullptr), _profiled_bci(0), _check_boolean(check_boolean) { - set_flag(NeedsWriteBarrierFlag, (as_ValueType(elt_type)->is_object())); - set_flag(NeedsStoreCheckFlag, (as_ValueType(elt_type)->is_object())); ASSERT_VALUES pin(); } // accessors Value value() const { return _value; } - bool needs_write_barrier() const { return check_flag(NeedsWriteBarrierFlag); } - bool needs_store_check() const { return check_flag(NeedsStoreCheckFlag); } bool check_boolean() const { return _check_boolean; } // Helpers for MethodData* profiling void set_should_profile(bool value) { set_flag(ProfileMDOFlag, value); } From f12c370d73363f384e3425857a663d855660f33a Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Mon, 11 Nov 2024 09:24:44 +0000 Subject: [PATCH 08/18] 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 4fd3ee8ef1691..72ad2a4589e30 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 a93bd9dfdd7e340b10c24a15fb70a3801bfb373d Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Mon, 11 Nov 2024 09:32:09 +0000 Subject: [PATCH 09/18] 8343810: [s390x] is_uimm* methods should take unsigned arguments Reviewed-by: lucy --- src/hotspot/cpu/s390/assembler_s390.hpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp index ab09460bd8647..c98c100a06842 100644 --- a/src/hotspot/cpu/s390/assembler_s390.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.hpp @@ -56,24 +56,23 @@ class Immediate { } // Test if x is within signed immediate range for nbits. - static bool is_uimm(int64_t x, unsigned int nbits) { + static bool is_uimm(uint64_t x, unsigned int nbits) { // nbits == 0 --> false // nbits >= 64 --> true assert(1 <= nbits && nbits < 64, "don't call, use statically known result"); - const uint64_t xu = (unsigned long)x; const uint64_t maxplus1 = 1UL << nbits; - return xu < maxplus1; // Unsigned comparison. Negative inputs appear to be very large. + return x < maxplus1; // Unsigned comparison. Negative inputs appear to be very large. } - static bool is_uimm32(int64_t x) { + static bool is_uimm32(uint64_t x) { return is_uimm(x, 32); } - static bool is_uimm16(int64_t x) { + static bool is_uimm16(uint64_t x) { return is_uimm(x, 16); } - static bool is_uimm12(int64_t x) { + static bool is_uimm12(uint64_t x) { return is_uimm(x, 12); } - static bool is_uimm8(int64_t x) { + static bool is_uimm8(uint64_t x) { return is_uimm(x, 8); } }; From 5ca6698ba418e82ff93471fbb495759850f26f63 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Mon, 11 Nov 2024 09:34:43 +0000 Subject: [PATCH 10/18] 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 0ee6a926b41ca..bc6f6d30236df 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 ffa46cf8be4e1..4ca8e3830c8c0 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 99bfd870f37c3..0375a2ead6529 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 3e2e1a839e2bd..a9b0315e96058 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 ec13364cdab5a52f704bc5d1575f3da17380b4f2 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/18] 8343067: C2: revisit constant-offset AddP chains after successful input idealizations Reviewed-by: kvn, vlivanov --- src/hotspot/share/opto/phaseX.cpp | 14 ++++- .../TestCombineAddPWithConstantOffsets.java | 56 +++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 6 ++ 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/irTests/igvn/TestCombineAddPWithConstantOffsets.java diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index f766146a894fc..c91a7bba07826 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -27,6 +27,7 @@ #include "gc/shared/c2/barrierSetC2.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" +#include "opto/addnode.hpp" #include "opto/block.hpp" #include "opto/callnode.hpp" #include "opto/castnode.hpp" @@ -1634,12 +1635,19 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } } - // If changed AddP inputs, check Stores for loop invariant - if( use_op == Op_AddP ) { + // If changed AddP inputs: + // - check Stores for loop invariant, and + // - if the changed input is the offset, check constant-offset AddP users for + // address expression flattening. + if (use_op == Op_AddP) { + bool offset_changed = n == use->in(AddPNode::Offset); for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { Node* u = use->fast_out(i2); - if (u->is_Mem()) + if (u->is_Mem()) { worklist.push(u); + } else if (offset_changed && u->is_AddP() && u->in(AddPNode::Offset)->is_Con()) { + worklist.push(u); + } } } // If changed initialization activity, check dependent Stores 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 0000000000000..69963316fa964 --- /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 fc55662d8013f..1d586e972be3d 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 cbe8448f28895b887157c1f23a0cf70070449509 Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Mon, 11 Nov 2024 10:15:02 +0000 Subject: [PATCH 12/18] 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 fec0a93161b44..c5c2290019b50 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 62afd6577aba5..231355043d57b 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 7c2cef58835c2..aa721a683d7bd 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 0785d340f48b6..a28d85c146f16 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 b1a9491844a165bf5ae54c50b4f8573bd3f3e24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Mon, 11 Nov 2024 10:36:46 +0000 Subject: [PATCH 13/18] 8343321: Bad verify in LockStack::oops_do() Reviewed-by: stefank, rkennke, coleenp --- src/hotspot/share/runtime/lockStack.inline.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/runtime/lockStack.inline.hpp b/src/hotspot/share/runtime/lockStack.inline.hpp index 515ca94c741e0..6c25a2a3a9871 100644 --- a/src/hotspot/share/runtime/lockStack.inline.hpp +++ b/src/hotspot/share/runtime/lockStack.inline.hpp @@ -216,7 +216,9 @@ inline bool LockStack::contains(oop o) const { } inline void LockStack::oops_do(OopClosure* cl) { - verify("pre-oops-do"); + // We don't perform pre oops_do verify here because this function + // is used by the GC to fix the oops. + int end = to_index(_top); for (int i = 0; i < end; i++) { cl->do_oop(&_base[i]); From 36e12955b2129f2075a203a0b39198f256083a24 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 11 Nov 2024 11:31:53 +0000 Subject: [PATCH 14/18] 8343929: Remove PreservedMarksSet::createTask() after JDK-8305895 Reviewed-by: ayang, shade --- src/hotspot/share/gc/shared/preservedMarks.cpp | 4 ---- src/hotspot/share/gc/shared/preservedMarks.hpp | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/hotspot/share/gc/shared/preservedMarks.cpp b/src/hotspot/share/gc/shared/preservedMarks.cpp index 4daba541d29e8..bc241fb5daf45 100644 --- a/src/hotspot/share/gc/shared/preservedMarks.cpp +++ b/src/hotspot/share/gc/shared/preservedMarks.cpp @@ -143,10 +143,6 @@ void PreservedMarksSet::restore(WorkerThreads* workers) { assert_empty(); } -WorkerTask* PreservedMarksSet::create_task() { - return new RestorePreservedMarksTask(this); -} - void PreservedMarksSet::reclaim() { assert_empty(); diff --git a/src/hotspot/share/gc/shared/preservedMarks.hpp b/src/hotspot/share/gc/shared/preservedMarks.hpp index 3fc8c62bff16d..84bf18e41afb6 100644 --- a/src/hotspot/share/gc/shared/preservedMarks.hpp +++ b/src/hotspot/share/gc/shared/preservedMarks.hpp @@ -114,8 +114,6 @@ class PreservedMarksSet : public CHeapObj { // is null, perform the work serially in the current thread. void restore(WorkerThreads* workers); - WorkerTask* create_task(); - // Reclaim stack array. void reclaim(); From 5016132291b8cb94c37ffb9397282927df87a3c5 Mon Sep 17 00:00:00 2001 From: Kevin Walls Date: Mon, 11 Nov 2024 12:26:38 +0000 Subject: [PATCH 15/18] 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 f15c416f29635..fb24055cc8f71 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 4b483a4b076be..1ae9623ef14c2 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 f3ba7676043756f7cf95d5215e18bd65e9f167e6 Mon Sep 17 00:00:00 2001 From: Tobias Holenstein Date: Mon, 11 Nov 2024 13:25:42 +0000 Subject: [PATCH 16/18] 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 e47518de4a722..f9c2e991d4631 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 591bbe371a3d1..e8cfd2968dc29 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 617f59a591d4d..e24ffa476a25d 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 0000000000000..7a38587eb3855 --- /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 24815527a0eec..24547b19b6a30 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 7ac76fdefbada..bbdf08dc8b84b 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 2ec358082f0896480bdbfcb289b4ba2bff0dd828 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Mon, 11 Nov 2024 13:35:25 +0000 Subject: [PATCH 17/18] 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 10fc8041325c7..acdc594b0097e 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 c5c2290019b50..cf8a856de96d2 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 231355043d57b..bcd54058c28fc 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 5664c195003c6..466c5d0a14cfe 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 0000000000000..755afea8c60b3 --- /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 1c5b0a3cd57c3..465a1cae8d98b 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 d9dbf1d06613f..15998d6b92959 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 0000000000000..935af4585ad7a --- /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 2b4e6ca0a976c..23b3dfb7079f5 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 0000000000000..5e540be7ced6a --- /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 0000000000000..a007350c16f81 --- /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 0000000000000..12e8708477c53 --- /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 0000000000000..9f54fd63476ff --- /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 e09eadbb3d0ff..9e18177d9c84f 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 21c5aebaa716c..af97ff465def4 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 6276932afbd14..7b6276c7aa058 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 6c338a25b4f09..90ca6840d52a6 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 84f77340f42c1..b6466c6a4d91d 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 e85d8f0d98443..686dd194adafd 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 8b2dee1b45ad7..c7af8865a793b 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 0000000000000..50f570251d208 --- /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 46912a68033dc..1fba932798d90 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 5d06288fb59b5..1acad93f5d357 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 2a0c43d3168e0..e2dd3403c83f5 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 0000000000000..e7d5340e3b00a --- /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 0000000000000..1ffe1240d07e7 --- /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 0000000000000..b0d2a2d66f5b3 --- /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 0000000000000..b97ebff9b4906 --- /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 0000000000000..b6b6105394df6 --- /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 0000000000000..369bccfecfce4 --- /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 0000000000000..533a8db30d0cc --- /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 0000000000000..ba92e1f35f2f2 --- /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 0000000000000..d923358aed90d --- /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 0000000000000..8094579ecd505 --- /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 0000000000000..709494b62566a --- /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 0000000000000..305fdd3917153 --- /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 0000000000000..f52691dd85927 --- /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 0000000000000..88f91f238bd87 --- /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 0000000000000..9910be5f91974 --- /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 0000000000000..3baa824e04990 --- /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 0000000000000..fac8cac112d14 --- /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 0000000000000..6be4ad7321cea --- /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 0acf6ec8bf3c3..337d767b0b221 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 330547148d02f..b872ca4f5842d 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 4f00846116cb6..775b95959c6e1 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 eeac5e0ecc608..5eb5b16bad533 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 523c9365c99c8..70bb024ede9a4 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 babb52a08361b00eb4bc6e2e109b1fdc198dbd59 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 11 Nov 2024 14:21:21 +0000 Subject: [PATCH 18/18] 8343931: Removed unused code in CompressedKlassPointers::initialize_for_given_encoding Reviewed-by: chagedorn --- src/hotspot/share/oops/compressedKlass.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hotspot/share/oops/compressedKlass.cpp b/src/hotspot/share/oops/compressedKlass.cpp index 0cfc0e3c60be3..f3c6fe92897be 100644 --- a/src/hotspot/share/oops/compressedKlass.cpp +++ b/src/hotspot/share/oops/compressedKlass.cpp @@ -154,8 +154,6 @@ void CompressedKlassPointers::calc_lowest_highest_narrow_klass_id() { // set this encoding scheme. Used by CDS at runtime to re-instate the scheme used to pre-compute klass ids for // archived heap objects. void CompressedKlassPointers::initialize_for_given_encoding(address addr, size_t len, address requested_base, int requested_shift) { - address const end = addr + len; - if (len > max_klass_range_size()) { stringStream ss; ss.print("Class space size and CDS archive size combined (%zu) "