diff --git a/src/java.base/share/classes/java/math/BigInteger.java b/src/java.base/share/classes/java/math/BigInteger.java index 3b751c85c55..53db80f0417 100644 --- a/src/java.base/share/classes/java/math/BigInteger.java +++ b/src/java.base/share/classes/java/math/BigInteger.java @@ -351,12 +351,18 @@ public BigInteger(byte[] val, int off, int len) { throw new NumberFormatException("Zero length BigInteger"); } Objects.checkFromIndexSize(off, len, val.length); + if (len == 0) { + mag = ZERO.mag; + signum = ZERO.signum; + return; + } - if (val[off] < 0) { - mag = makePositive(val, off, len); + int b = val[off]; + if (b < 0) { + mag = makePositive(b, val, off, len); signum = -1; } else { - mag = stripLeadingZeroBytes(val, off, len); + mag = stripLeadingZeroBytes(b, val, off, len); signum = (mag.length == 0 ? 0 : 1); } if (mag.length >= MAX_MAG_LENGTH) { @@ -4604,77 +4610,167 @@ private static int[] trustedStripLeadingZeroInts(int[] val) { return keep == 0 ? val : java.util.Arrays.copyOfRange(val, keep, vlen); } - /** + private static int[] stripLeadingZeroBytes(byte[] a, int from, int len) { + return stripLeadingZeroBytes(Integer.MIN_VALUE, a, from, len); + } + + /* * Returns a copy of the input array stripped of any leading zero bytes. + * The returned array is either empty, or its 0-th element is non-zero, + * meeting the "minimal" requirement for field mag (see comment on mag). + * + * The range [from, from + len) must be well-formed w.r.t. array a. + * + * b < -128 means that a[from] has not yet been read. + * Otherwise, b must be a[from], have been read only once before invoking + * this method, and len > 0 must hold. */ - private static int[] stripLeadingZeroBytes(byte[] a, int off, int len) { - int indexBound = off + len; - int keep; - - // Find first nonzero byte - for (keep = off; keep < indexBound && a[keep] == 0; keep++) - ; - - // Allocate new array and copy relevant part of input array - int intLength = ((indexBound - keep) + 3) >>> 2; - int[] result = new int[intLength]; - int b = indexBound - 1; - for (int i = intLength-1; i >= 0; i--) { - result[i] = a[b--] & 0xff; - int bytesRemaining = b - keep + 1; - int bytesToTransfer = Math.min(3, bytesRemaining); - for (int j=8; j <= (bytesToTransfer << 3); j += 8) - result[i] |= ((a[b--] & 0xff) << j); + private static int[] stripLeadingZeroBytes(int b, byte[] a, int from, int len) { + /* + * Except for the first byte, each read access to the input array a + * is of the form a[from++]. + * The index from is never otherwise altered, except right below, + * and only increases in steps of 1, always up to index to. + * Hence, each byte in the array is read exactly once, from lower to + * higher indices (from most to least significant byte). + */ + if (len == 0) { + return ZERO.mag; } - return result; + int to = from + len; + if (b < -128) { + b = a[from]; + } + /* Either way, a[from] has now been read exactly once, skip to next. */ + ++from; + /* + * Set up the shortest int[] for the sequence of the bytes + * b, a[from+1], ..., a[to-1] (len > 0) + * Shortest means first skipping leading zeros. + */ + for (; b == 0 && from < to; b = a[from++]) + ; //empty body + if (b == 0) { + /* Here, from == to as well. All bytes are zeros. */ + return ZERO.mag; + } + /* + * Allocate just enough ints to hold (to - from + 1) bytes, that is + * ((to - from + 1) + 3) / 4 = (to - from) / 4 + 1 + */ + int[] res = new int[((to - from) >> 2) + 1]; + /* + * A "digit" is a group of 4 adjacent bytes aligned w.r.t. index to. + * (Implied 0 bytes are prepended as needed.) + * b is the most significant byte not 0. + * Digit d0 spans the range of indices that includes current (from - 1). + */ + int d0 = b & 0xFF; + while (((to - from) & 0x3) != 0) { + d0 = d0 << 8 | a[from++] & 0xFF; + } + res[0] = d0; + /* + * Prepare the remaining digits. + * (to - from) is a multiple of 4, so prepare an int for every 4 bytes. + * This is a candidate for Unsafe.copy[Swap]Memory(). + */ + int i = 1; + while (from < to) { + res[i++] = a[from++] << 24 | (a[from++] & 0xFF) << 16 + | (a[from++] & 0xFF) << 8 | (a[from++] & 0xFF); + } + return res; } - /** + /* * Takes an array a representing a negative 2's-complement number and * returns the minimal (no leading zero bytes) unsigned whose value is -a. + * + * len > 0 must hold. + * The range [from, from + len) must be well-formed w.r.t. array a. + * b is assumed to be the result of reading a[from] and to meet b < 0. */ - private static int[] makePositive(byte[] a, int off, int len) { - int keep, k; - int indexBound = off + len; - - // Find first non-sign (0xff) byte of input - for (keep=off; keep < indexBound && a[keep] == -1; keep++) - ; - - - /* Allocate output array. If all non-sign bytes are 0x00, we must - * allocate space for one extra output byte. */ - for (k=keep; k < indexBound && a[k] == 0; k++) - ; - - int extraByte = (k == indexBound) ? 1 : 0; - int intLength = ((indexBound - keep + extraByte) + 3) >>> 2; - int result[] = new int[intLength]; - - /* Copy one's complement of input into output, leaving extra - * byte (if it exists) == 0x00 */ - int b = indexBound - 1; - for (int i = intLength-1; i >= 0; i--) { - result[i] = a[b--] & 0xff; - int numBytesToTransfer = Math.min(3, b-keep+1); - if (numBytesToTransfer < 0) - numBytesToTransfer = 0; - for (int j=8; j <= 8*numBytesToTransfer; j += 8) - result[i] |= ((a[b--] & 0xff) << j); - - // Mask indicates which bits must be complemented - int mask = -1 >>> (8*(3-numBytesToTransfer)); - result[i] = ~result[i] & mask; + private static int[] makePositive(int b, byte[] a, int from, int len) { + /* + * By assumption, b == a[from] < 0 and len > 0. + * + * First collect the bytes into the resulting array res. + * Then convert res to two's complement. + * + * Except for b == a[from], each read access to the input array a + * is of the form a[from++]. + * The index from is never otherwise altered, except right below, + * and only increases in steps of 1, always up to index to. + * Hence, each byte in the array is read exactly once, from lower to + * higher indices (from most to least significant byte). + */ + int to = from + len; + /* b == a[from] has been read exactly once, skip to next index. */ + ++from; + /* Skip leading -1 bytes. */ + for (; b == -1 && from < to; b = a[from++]) + ; //empty body + /* + * A "digit" is a group of 4 adjacent bytes aligned w.r.t. index to. + * b is the most significant byte not -1, or -1 only if from == to. + * Digit d0 spans the range of indices that includes current (from - 1). + * (Implied -1 bytes are prepended to array a as needed.) + * It usually corresponds to res[0], except for the special case below. + */ + int d0 = -1 << 8 | b & 0xFF; + while (((to - from) & 0x3) != 0) { + d0 = d0 << 8 | (b = a[from++]) & 0xFF; + } + int f = from; // keeps the current from for sizing purposes later + /* Skip zeros adjacent to d0, if at all. */ + for (; b == 0 && from < to; b = a[from++]) + ; //empty body + /* + * b is the most significant non-zero byte at or after (f - 1), + * or 0 only if from == to. + * Digit d spans the range of indices that includes (f - 1). + */ + int d = b & 0xFF; + while (((to - from) & 0x3) != 0) { + d = d << 8 | a[from++] & 0xFF; } - - // Add one to one's complement to generate two's complement - for (int i=result.length-1; i >= 0; i--) { - result[i] = (int)((result[i] & LONG_MASK) + 1); - if (result[i] != 0) - break; + /* + * If the situation here is like this: + * index: f to == from + * ..., -1,-1, 0,0,0,0, 0,0,0,0, ..., 0,0,0,0 + * digit: d0 d + * then, as shown, the number of zeros is a positive multiple of 4. + * The array res needs a minimal length of (1 + 1 + (to - f) / 4) + * to accommodate the two's complement, including a leading 1. + * In any other case, there is at least one byte that is non-zero. + * The array for the two's complement has length (0 + 1 + (to - f) / 4). + * c is 1, resp., 0 for the two situations. + */ + int c = (to - from | d0 | d) == 0 ? 1 : 0; + int[] res = new int[c + 1 + ((to - f) >> 2)]; + res[0] = c == 0 ? d0 : -1; + int i = res.length - ((to - from) >> 2); + if (i > 1) { + res[i - 1] = d; } - - return result; + /* + * Prepare the remaining digits. + * (to - from) is a multiple of 4, so prepare an int for every 4 bytes. + * This is a candidate for Unsafe.copy[Swap]Memory(). + */ + while (from < to) { + res[i++] = a[from++] << 24 | (a[from++] & 0xFF) << 16 + | (a[from++] & 0xFF) << 8 | (a[from++] & 0xFF); + } + /* Convert to two's complement. Here, i == res.length */ + while (--i >= 0 && res[i] == 0) + ; // empty body + res[i] = -res[i]; + while (--i >= 0) { + res[i] = ~res[i]; + } + return res; } /** diff --git a/src/java.base/share/classes/jdk/internal/foreign/layout/SequenceLayoutImpl.java b/src/java.base/share/classes/jdk/internal/foreign/layout/SequenceLayoutImpl.java index bcbf76ea338..16cffdad253 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/layout/SequenceLayoutImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/layout/SequenceLayoutImpl.java @@ -179,7 +179,7 @@ public SequenceLayout flatten() { @Override public String toString() { - boolean max = (Long.MAX_VALUE / elementLayout.byteSize()) == elemCount; + boolean max = (Long.MAX_VALUE / Math.max(1, elementLayout.byteSize())) == elemCount; return decorateLayoutString(String.format("[%s:%s]", max ? "*" : elemCount, elementLayout)); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java index 875ce982235..e705fb0cc10 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java @@ -376,9 +376,10 @@ private Content generateContent(Element holder, DocTree tag) StyledText externalSnippet = null; try { - Diags d = (text, pos) -> { + Diags d = (key, pos) -> { var path = utils.getCommentHelper(holder) .getDocTreePath(snippetTag.getBody()); + var text = resources.getText(key); config.getReporter().print(Diagnostic.Kind.WARNING, path, pos, pos, pos, text); }; @@ -397,7 +398,7 @@ private Content generateContent(Element holder, DocTree tag) try { var finalFileObject = fileObject; - Diags d = (text, pos) -> messages.warning(finalFileObject, pos, pos, pos, text); + Diags d = (key, pos) -> messages.warning(finalFileObject, pos, pos, pos, key); if (externalContent != null) { externalSnippet = parse(resources, d, language, externalContent); } @@ -484,7 +485,7 @@ private StyledText parse(Resources resources, Diags diags, Optional la } public interface Diags { - void warn(String text, int pos); + void warn(String key, int pos); } private static String stringValueOf(AttributeTree at) throws BadSnippetException { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Parser.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Parser.java index 7c60da78dbe..907c2231a5c 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Parser.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Parser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2023, 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 @@ -161,7 +161,7 @@ record OffsetAndLine(int offset, String line) { } } } if (parsedTags.isEmpty()) { // (2) - diags.warn(resources.getText("doclet.snippet.markup.spurious"), next.offset() + markedUpLine.start("markup")); + diags.warn("doclet.snippet.markup.spurious", next.offset() + markedUpLine.start("markup")); line = rawLine + (addLineTerminator ? "\n" : ""); } else { // (3) hasMarkup = true; diff --git a/test/hotspot/jtreg/runtime/NMT/VirtualAllocCommitMerge.java b/test/hotspot/jtreg/runtime/NMT/VirtualAllocCommitMerge.java index abae9cabcea..9cc8c39d897 100644 --- a/test/hotspot/jtreg/runtime/NMT/VirtualAllocCommitMerge.java +++ b/test/hotspot/jtreg/runtime/NMT/VirtualAllocCommitMerge.java @@ -24,12 +24,15 @@ /* * @test * @summary Test merging of committed virtual memory and that we track it correctly + * @comment needs to be executed with -Xint (or, alternatively, -Xcomp -Xbatch) since it relies on comparing + * NMT call stacks, and we must make sure that all functions on the stack that NMT sees are either compiled + * from the get-go or stay always interpreted. * @library /test/lib * @modules java.base/jdk.internal.misc * java.management * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail VirtualAllocCommitMerge + * @run main/othervm -Xbootclasspath/a:. -Xint -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail VirtualAllocCommitMerge * */ diff --git a/test/jdk/com/sun/jdi/EATests.java b/test/jdk/com/sun/jdi/EATests.java index 67351e36cb7..d3a615e8d8d 100644 --- a/test/jdk/com/sun/jdi/EATests.java +++ b/test/jdk/com/sun/jdi/EATests.java @@ -42,6 +42,7 @@ * -XX:+WhiteBoxAPI * -Xbatch * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks + * -XX:LockingMode=1 * @run driver EATests * -XX:+UnlockDiagnosticVMOptions * -Xms256m -Xmx256m @@ -50,6 +51,7 @@ * -XX:+WhiteBoxAPI * -Xbatch * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:-EliminateLocks -XX:+EliminateNestedLocks + * -XX:LockingMode=1 * @run driver EATests * -XX:+UnlockDiagnosticVMOptions * -Xms256m -Xmx256m @@ -58,6 +60,7 @@ * -XX:+WhiteBoxAPI * -Xbatch * -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks + * -XX:LockingMode=1 * @run driver EATests * -XX:+UnlockDiagnosticVMOptions * -Xms256m -Xmx256m @@ -66,6 +69,44 @@ * -XX:+WhiteBoxAPI * -Xbatch * -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks + * -XX:LockingMode=1 + * + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks + * -XX:LockingMode=2 + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:-EliminateLocks -XX:+EliminateNestedLocks + * -XX:LockingMode=2 + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks + * -XX:LockingMode=2 + * @run driver EATests + * -XX:+UnlockDiagnosticVMOptions + * -Xms256m -Xmx256m + * -Xbootclasspath/a:. + * -XX:CompileCommand=dontinline,*::dontinline_* + * -XX:+WhiteBoxAPI + * -Xbatch + * -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks + * -XX:LockingMode=2 * * @comment Excercise -XX:+DeoptimizeObjectsALot. Mostly to prevent bit-rot because the option is meant to stress object deoptimization * with non-synthetic workloads. @@ -208,11 +249,13 @@ public static void main(String[] args) { // Relocking test cases new EARelockingSimpleTarget() .run(); + new EARelockingSimpleWithAccessInOtherThreadTarget() .run(); new EARelockingRecursiveTarget() .run(); new EARelockingNestedInflatedTarget() .run(); new EARelockingNestedInflated_02Target() .run(); new EARelockingArgEscapeLWLockedInCalleeFrameTarget() .run(); new EARelockingArgEscapeLWLockedInCalleeFrame_2Target() .run(); + new EARelockingArgEscapeLWLockedInCalleeFrameNoRecursiveTarget() .run(); new EAGetOwnedMonitorsTarget() .run(); new EAEntryCountTarget() .run(); new EARelockingObjectCurrentlyWaitingOnTarget() .run(); @@ -328,11 +371,13 @@ protected void runTests() throws Exception { // Relocking test cases new EARelockingSimple() .run(this); + new EARelockingSimpleWithAccessInOtherThread() .run(this); new EARelockingRecursive() .run(this); new EARelockingNestedInflated() .run(this); new EARelockingNestedInflated_02() .run(this); new EARelockingArgEscapeLWLockedInCalleeFrame() .run(this); new EARelockingArgEscapeLWLockedInCalleeFrame_2() .run(this); + new EARelockingArgEscapeLWLockedInCalleeFrameNoRecursive() .run(this); new EAGetOwnedMonitors() .run(this); new EAEntryCount() .run(this); new EARelockingObjectCurrentlyWaitingOn() .run(this); @@ -1707,6 +1752,62 @@ public void dontinline_testMethod() { ///////////////////////////////////////////////////////////////////////////// +// The debugger reads and publishes an object with eliminated locking to an instance field. +// A 2nd thread in the debuggee finds it there and changes its state using a synchronized method. +// Without eager relocking the accesses are unsynchronized which can be observed. +class EARelockingSimpleWithAccessInOtherThread extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + String l1ClassName = EARelockingSimpleWithAccessInOtherThreadTarget.SyncCounter.class.getName(); + ObjectReference ctr = getLocalRef(bpe.thread().frame(1), l1ClassName, "l1"); + setField(testCase, "sharedCounter", ctr); + terminateEndlessLoop(); + } +} + +class EARelockingSimpleWithAccessInOtherThreadTarget extends EATestCaseBaseTarget { + + public static class SyncCounter { + private int val; + public synchronized int inc() { return val++; } + } + + public volatile SyncCounter sharedCounter; + + @Override + public void setUp() { + super.setUp(); + doLoop = true; + Thread.ofPlatform().daemon().start(() -> { + while (doLoop) { + SyncCounter ctr = sharedCounter; + if (ctr != null) { + ctr.inc(); + } + } + }); + } + + public void dontinline_testMethod() { + SyncCounter l1 = new SyncCounter(); + synchronized (l1) { // Eliminated locking + l1.inc(); + dontinline_brkpt(); // Debugger publishes l1 to sharedCounter. + iResult = l1.inc(); // Changes by the 2nd thread will be observed if l1 + // was not relocked before passing it to the debugger. + } + } + + @Override + public int getExpectedIResult() { + return 1; + } +} + +///////////////////////////////////////////////////////////////////////////// + // Test recursive locking class EARelockingRecursiveTarget extends EATestCaseBaseTarget { @@ -1905,6 +2006,48 @@ public int getExpectedIResult() { ///////////////////////////////////////////////////////////////////////////// +/** + * Similar to {@link EARelockingArgEscapeLWLockedInCalleeFrame_2Target}. It does + * not use recursive locking and exposed a bug in the lightweight-locking implementation. + */ +class EARelockingArgEscapeLWLockedInCalleeFrameNoRecursive extends EATestCaseBaseDebugger { + + public void runTestCase() throws Exception { + BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); + printStack(bpe.thread()); + @SuppressWarnings("unused") + ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "l1"); + } +} + +class EARelockingArgEscapeLWLockedInCalleeFrameNoRecursiveTarget extends EATestCaseBaseTarget { + + @Override + public void setUp() { + super.setUp(); + testMethodDepth = 2; + } + + public void dontinline_testMethod() { + XYVal l1 = new XYVal(1, 1); // NoEscape, scalar replaced + XYVal l2 = new XYVal(4, 2); // NoEscape, scalar replaced + XYVal l3 = new XYVal(5, 3); // ArgEscape + synchronized (l1) { // eliminated + synchronized (l2) { // eliminated + l3.dontinline_sync_method(this); // l3 escapes + } + } + iResult = l2.x + l2.y; + } + + @Override + public int getExpectedIResult() { + return 6; + } +} + +///////////////////////////////////////////////////////////////////////////// + /** * Test relocking eliminated (nested) locks of an object on which the * target thread currently waits. diff --git a/test/jdk/java/foreign/TestLayouts.java b/test/jdk/java/foreign/TestLayouts.java index ee758cf884d..f7e0a8b7468 100644 --- a/test/jdk/java/foreign/TestLayouts.java +++ b/test/jdk/java/foreign/TestLayouts.java @@ -216,6 +216,21 @@ public void testSequenceOverflow() { () -> MemoryLayout.sequenceLayout(0, JAVA_LONG).withElementCount(Long.MAX_VALUE)); } + @Test + public void testSequenceLayoutWithZeroLength() { + SequenceLayout layout = MemoryLayout.sequenceLayout(0, JAVA_INT); + assertEquals(layout.toString(), "[0:i4]"); + + SequenceLayout nested = MemoryLayout.sequenceLayout(0, layout); + assertEquals(nested.toString(), "[0:[0:i4]]"); + + SequenceLayout layout2 = MemoryLayout.sequenceLayout(0, JAVA_INT); + assertEquals(layout, layout2); + + SequenceLayout nested2 = MemoryLayout.sequenceLayout(0, layout2); + assertEquals(nested, nested2); + } + @Test public void testStructOverflow() { assertThrows(IllegalArgumentException.class, // negative diff --git a/test/jdk/java/math/BigInteger/ByteArrayConstructorTest.java b/test/jdk/java/math/BigInteger/ByteArrayConstructorTest.java new file mode 100644 index 00000000000..59784ee4c26 --- /dev/null +++ b/test/jdk/java/math/BigInteger/ByteArrayConstructorTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.test.lib.RandomFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.math.Accessor; +import java.math.BigInteger; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @test + * @bug 8319174 + * @summary Exercises minimality of BigInteger.mag field (use -Dseed=X to set PRANDOM seed) + * @library /test/lib + * @build jdk.test.lib.RandomFactory + * @build java.base/java.math.Accessor + * @key randomness + * @run junit/othervm -DmaxDurationMillis=3000 ByteArrayConstructorTest + */ +public class ByteArrayConstructorTest { + + private static final int DEFAULT_MAX_DURATION_MILLIS = 3_000; + + public static final int N = 1_024; + + private static int maxDurationMillis; + private static final Random rnd = RandomFactory.getRandom(); + private volatile boolean stop = false; + + @BeforeAll + static void setMaxDurationMillis() { + maxDurationMillis = Math.max(maxDurationMillis(), 0); + } + + @Test + public void testNonNegative() throws InterruptedException { + byte[] ba = nonNegativeBytes(); + doBigIntegers(ba, ba[0]); // a mask to flip to 0 and back to ba[0] + } + + @Test + public void testNegative() throws InterruptedException { + byte[] ba = negativeBytes(); + doBigIntegers(ba, (byte) ~ba[0]); // a mask to flip to -1 and back to ba[0] + } + + /* + * Starts a thread th that keeps flipping the "sign" byte in the array ba + * from the original value to 0 or -1 and back, depending on ba[0] being + * non-negative or negative, resp. + * (ba is "big endian", the least significant byte is the one with the + * highest index.) + * + * In the meantime, the current thread keeps creating BigInteger instances + * with ba and checks that the internal invariant holds, despite the + * attempts by thread th to racily modify ba. + * It does so at least as indicated by maxDurationMillis. + * + * Finally, this thread requests th to stop and joins with it, either + * because maxDurationMillis has expired, or because of an invalid invariant. + */ + private void doBigIntegers(byte[] ba, byte mask) throws InterruptedException { + Thread th = new Thread(() -> { + while (!stop) { + ba[0] ^= mask; + } + }); + th.start(); + + try { + createBigIntegers(maxDurationMillis, ba); + } finally { + stop = true; + th.join(1_000); + } + } + + private void createBigIntegers(int maxDurationMillis, byte[] ba) { + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < maxDurationMillis) { + BigInteger bi = new BigInteger(ba); + int[] mag = Accessor.mag(bi); + assertTrue(mag.length == 0 || mag[0] != 0, + String.format("inconsistent BigInteger: mag.length=%d, mag[0]=%d", + mag.length, mag[0])); + } + } + + private byte[] nonNegativeBytes() { + byte[] ba = new byte[1 + N]; + byte b0; + while ((b0 = (byte) rnd.nextInt()) < 0); // empty body + rnd.nextBytes(ba); + ba[0] = b0; + /* Except for ba[0], fill most significant half with zeros. */ + for (int i = 1; i <= N / 2; ++i) { + ba[i] = 0; + } + return ba; + } + + private byte[] negativeBytes() { + byte[] ba = new byte[1 + N]; + byte b0; + while ((b0 = (byte) rnd.nextInt()) >= 0); // empty body + rnd.nextBytes(ba); + ba[0] = b0; + /* Except for ba[0], fill most significant half with -1 bytes. */ + for (int i = 1; i <= N / 2; ++i) { + ba[i] = -1; + } + return ba; + } + + private static int maxDurationMillis() { + try { + return Integer.parseInt(System.getProperty("maxDurationMillis", + Integer.toString(DEFAULT_MAX_DURATION_MILLIS))); + } catch (NumberFormatException ignore) { + } + return DEFAULT_MAX_DURATION_MILLIS; + } + +} diff --git a/test/jdk/java/math/BigInteger/java.base/java/math/Accessor.java b/test/jdk/java/math/BigInteger/java.base/java/math/Accessor.java new file mode 100644 index 00000000000..45a0f944982 --- /dev/null +++ b/test/jdk/java/math/BigInteger/java.base/java/math/Accessor.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023, 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 java.math; + +public class Accessor { + + public static int[] mag(BigInteger bi) { + return bi.mag; + } + +} diff --git a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java index c7936bcb7e2..5d9eb794613 100644 --- a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java +++ b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023, 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 8266666 8281969 + * @bug 8266666 8281969 8319339 * @summary Implementation for snippets * @library /tools/lib ../../lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -259,8 +259,12 @@ public void testNegativeInlineExternalHybridTagMarkup_NextLinePutOnLastLine(Path Path srcDir = base.resolve("src"); Path outDir = base.resolve("out"); var goodFile = "good.txt"; + // use two files that differ in name but not content, to work around + // error deduplication, whereby an error related to coordinates + // (file, pos) reported before is suppressed; see: + // com.sun.tools.javac.util.Log.shouldReport(JavaFileObject, int) var badFile = "bad.txt"; - var badFile2 = "bad2.txt"; // to workaround error deduplication + var badFile2 = "bad2.txt"; new ClassBuilder(tb, "pkg.A") .setModifiers("public", "class") .addMembers( @@ -625,7 +629,7 @@ public void testPositiveInlineTagMarkup_BlankLinesFromNextLineMarkup(Path base) } @Test - public void testPositiveInlineTagMarkup_FalseMarkup(Path base) throws Exception { + public void testPositiveInlineTagMarkup_SpuriousMarkup(Path base) throws Exception { var testCases = List.of( new TestCase( """ @@ -661,6 +665,134 @@ public void testPositiveInlineTagMarkup_FalseMarkup(Path base) throws Exception """) ); testPositive(base, testCases); + checkOutput(Output.OUT, true, """ + A.java:6: warning: spurious markup + // @formatter:off + ^""",""" + A.java:9: warning: spurious markup + // @formatter:on + ^""",""" + A.java:17: warning: spurious markup + // @formatter:off + ^""",""" + A.java:22: warning: spurious markup + // @formatter:on + ^"""); + } + + /* + * If spurious markup appears in an external snippet or either side of a + * hybrid snippet, then all of the below is true: + * + * - no error is raised + * - relevant warnings are emitted + * - spurious markup is output literally + */ + @Test + public void testPositiveExternalHybridTagMarkup_SpuriousMarkup(Path base) throws Exception { + Path srcDir = base.resolve("src"); + Path outDir = base.resolve("out"); + var plain = "plain.txt"; + var withRegion = "withRegion.txt"; + new ClassBuilder(tb, "pkg.A") + .setModifiers("public", "class") + .addMembers( + ClassBuilder.MethodBuilder + .parse("public void external() { }") + .setComments(""" + {@snippet file="%s"} + """.formatted(plain))) + .addMembers( + ClassBuilder.MethodBuilder + .parse("public void hybrid1() { }") + .setComments(""" + {@snippet file="%s": + First line + // @formatter:off + Second Line + Third line + // @formatter:on + Fourth line + } + """.formatted(plain))) + .addMembers( + ClassBuilder.MethodBuilder + .parse("public void hybrid2() { }") + .setComments(""" + {@snippet file="%s" region="showThis" : + Second Line + Third line + } + """.formatted(withRegion))) + .addMembers( + ClassBuilder.MethodBuilder + .parse("public void hybrid3() { }") + .setComments(""" + {@snippet file="%s" region="showThis" : + First line + // @formatter:off + Second Line // @start region=showThis + Third line + // @end + // @formatter:on + Fourth line + } + """.formatted(withRegion))) + .write(srcDir); + + addSnippetFile(srcDir, "pkg", plain, """ + First line + // @formatter:off + Second Line + Third line + // @formatter:on + Fourth line +"""); + addSnippetFile(srcDir, "pkg", withRegion, """ + First line + // @formatter:off + Second Line // @start region=showThis + Third line + // @end + // @formatter:on + Fourth line +"""); + javadoc("-d", outDir.toString(), + "-sourcepath", srcDir.toString(), + "pkg"); + checkExit(Exit.OK); + checkNoCrashes(); + checkOutput(Output.OUT, true, """ + %s:2: warning: spurious markup + // @formatter:off + ^""".formatted(plain), """ + %s:5: warning: spurious markup + // @formatter:on + ^""".formatted(plain), """ + A.java:11: warning: spurious markup + // @formatter:off + ^""", """ + A.java:14: warning: spurious markup + // @formatter:on + ^""", """ + %s:2: warning: spurious markup + // @formatter:off + ^""".formatted(plain), """ + %s:5: warning: spurious markup + // @formatter:on + ^""".formatted(plain), """ + %s:2: warning: spurious markup + // @formatter:off + ^""".formatted(withRegion), """ + %s:6: warning: spurious markup + // @formatter:on + ^""".formatted(withRegion), """ + A.java:31: warning: spurious markup + // @formatter:off + ^""", """ + A.java:35: warning: spurious markup + // @formatter:on + ^"""); } @Test