Skip to content

Commit a64fc48

Browse files
committed
8319174: Enhance robustness of some j.m.BigInteger constructors
Reviewed-by: rriggs, darcy
1 parent 9cce9fe commit a64fc48

File tree

3 files changed

+340
-64
lines changed

3 files changed

+340
-64
lines changed

src/java.base/share/classes/java/math/BigInteger.java

+160-64
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,18 @@ public BigInteger(byte[] val, int off, int len) {
351351
throw new NumberFormatException("Zero length BigInteger");
352352
}
353353
Objects.checkFromIndexSize(off, len, val.length);
354+
if (len == 0) {
355+
mag = ZERO.mag;
356+
signum = ZERO.signum;
357+
return;
358+
}
354359

355-
if (val[off] < 0) {
356-
mag = makePositive(val, off, len);
360+
int b = val[off];
361+
if (b < 0) {
362+
mag = makePositive(b, val, off, len);
357363
signum = -1;
358364
} else {
359-
mag = stripLeadingZeroBytes(val, off, len);
365+
mag = stripLeadingZeroBytes(b, val, off, len);
360366
signum = (mag.length == 0 ? 0 : 1);
361367
}
362368
if (mag.length >= MAX_MAG_LENGTH) {
@@ -4604,77 +4610,167 @@ private static int[] trustedStripLeadingZeroInts(int[] val) {
46044610
return keep == 0 ? val : java.util.Arrays.copyOfRange(val, keep, vlen);
46054611
}
46064612

4607-
/**
4613+
private static int[] stripLeadingZeroBytes(byte[] a, int from, int len) {
4614+
return stripLeadingZeroBytes(Integer.MIN_VALUE, a, from, len);
4615+
}
4616+
4617+
/*
46084618
* Returns a copy of the input array stripped of any leading zero bytes.
4619+
* The returned array is either empty, or its 0-th element is non-zero,
4620+
* meeting the "minimal" requirement for field mag (see comment on mag).
4621+
*
4622+
* The range [from, from + len) must be well-formed w.r.t. array a.
4623+
*
4624+
* b < -128 means that a[from] has not yet been read.
4625+
* Otherwise, b must be a[from], have been read only once before invoking
4626+
* this method, and len > 0 must hold.
46094627
*/
4610-
private static int[] stripLeadingZeroBytes(byte[] a, int off, int len) {
4611-
int indexBound = off + len;
4612-
int keep;
4613-
4614-
// Find first nonzero byte
4615-
for (keep = off; keep < indexBound && a[keep] == 0; keep++)
4616-
;
4617-
4618-
// Allocate new array and copy relevant part of input array
4619-
int intLength = ((indexBound - keep) + 3) >>> 2;
4620-
int[] result = new int[intLength];
4621-
int b = indexBound - 1;
4622-
for (int i = intLength-1; i >= 0; i--) {
4623-
result[i] = a[b--] & 0xff;
4624-
int bytesRemaining = b - keep + 1;
4625-
int bytesToTransfer = Math.min(3, bytesRemaining);
4626-
for (int j=8; j <= (bytesToTransfer << 3); j += 8)
4627-
result[i] |= ((a[b--] & 0xff) << j);
4628+
private static int[] stripLeadingZeroBytes(int b, byte[] a, int from, int len) {
4629+
/*
4630+
* Except for the first byte, each read access to the input array a
4631+
* is of the form a[from++].
4632+
* The index from is never otherwise altered, except right below,
4633+
* and only increases in steps of 1, always up to index to.
4634+
* Hence, each byte in the array is read exactly once, from lower to
4635+
* higher indices (from most to least significant byte).
4636+
*/
4637+
if (len == 0) {
4638+
return ZERO.mag;
46284639
}
4629-
return result;
4640+
int to = from + len;
4641+
if (b < -128) {
4642+
b = a[from];
4643+
}
4644+
/* Either way, a[from] has now been read exactly once, skip to next. */
4645+
++from;
4646+
/*
4647+
* Set up the shortest int[] for the sequence of the bytes
4648+
* b, a[from+1], ..., a[to-1] (len > 0)
4649+
* Shortest means first skipping leading zeros.
4650+
*/
4651+
for (; b == 0 && from < to; b = a[from++])
4652+
; //empty body
4653+
if (b == 0) {
4654+
/* Here, from == to as well. All bytes are zeros. */
4655+
return ZERO.mag;
4656+
}
4657+
/*
4658+
* Allocate just enough ints to hold (to - from + 1) bytes, that is
4659+
* ((to - from + 1) + 3) / 4 = (to - from) / 4 + 1
4660+
*/
4661+
int[] res = new int[((to - from) >> 2) + 1];
4662+
/*
4663+
* A "digit" is a group of 4 adjacent bytes aligned w.r.t. index to.
4664+
* (Implied 0 bytes are prepended as needed.)
4665+
* b is the most significant byte not 0.
4666+
* Digit d0 spans the range of indices that includes current (from - 1).
4667+
*/
4668+
int d0 = b & 0xFF;
4669+
while (((to - from) & 0x3) != 0) {
4670+
d0 = d0 << 8 | a[from++] & 0xFF;
4671+
}
4672+
res[0] = d0;
4673+
/*
4674+
* Prepare the remaining digits.
4675+
* (to - from) is a multiple of 4, so prepare an int for every 4 bytes.
4676+
* This is a candidate for Unsafe.copy[Swap]Memory().
4677+
*/
4678+
int i = 1;
4679+
while (from < to) {
4680+
res[i++] = a[from++] << 24 | (a[from++] & 0xFF) << 16
4681+
| (a[from++] & 0xFF) << 8 | (a[from++] & 0xFF);
4682+
}
4683+
return res;
46304684
}
46314685

4632-
/**
4686+
/*
46334687
* Takes an array a representing a negative 2's-complement number and
46344688
* returns the minimal (no leading zero bytes) unsigned whose value is -a.
4689+
*
4690+
* len > 0 must hold.
4691+
* The range [from, from + len) must be well-formed w.r.t. array a.
4692+
* b is assumed to be the result of reading a[from] and to meet b < 0.
46354693
*/
4636-
private static int[] makePositive(byte[] a, int off, int len) {
4637-
int keep, k;
4638-
int indexBound = off + len;
4639-
4640-
// Find first non-sign (0xff) byte of input
4641-
for (keep=off; keep < indexBound && a[keep] == -1; keep++)
4642-
;
4643-
4644-
4645-
/* Allocate output array. If all non-sign bytes are 0x00, we must
4646-
* allocate space for one extra output byte. */
4647-
for (k=keep; k < indexBound && a[k] == 0; k++)
4648-
;
4649-
4650-
int extraByte = (k == indexBound) ? 1 : 0;
4651-
int intLength = ((indexBound - keep + extraByte) + 3) >>> 2;
4652-
int result[] = new int[intLength];
4653-
4654-
/* Copy one's complement of input into output, leaving extra
4655-
* byte (if it exists) == 0x00 */
4656-
int b = indexBound - 1;
4657-
for (int i = intLength-1; i >= 0; i--) {
4658-
result[i] = a[b--] & 0xff;
4659-
int numBytesToTransfer = Math.min(3, b-keep+1);
4660-
if (numBytesToTransfer < 0)
4661-
numBytesToTransfer = 0;
4662-
for (int j=8; j <= 8*numBytesToTransfer; j += 8)
4663-
result[i] |= ((a[b--] & 0xff) << j);
4664-
4665-
// Mask indicates which bits must be complemented
4666-
int mask = -1 >>> (8*(3-numBytesToTransfer));
4667-
result[i] = ~result[i] & mask;
4694+
private static int[] makePositive(int b, byte[] a, int from, int len) {
4695+
/*
4696+
* By assumption, b == a[from] < 0 and len > 0.
4697+
*
4698+
* First collect the bytes into the resulting array res.
4699+
* Then convert res to two's complement.
4700+
*
4701+
* Except for b == a[from], each read access to the input array a
4702+
* is of the form a[from++].
4703+
* The index from is never otherwise altered, except right below,
4704+
* and only increases in steps of 1, always up to index to.
4705+
* Hence, each byte in the array is read exactly once, from lower to
4706+
* higher indices (from most to least significant byte).
4707+
*/
4708+
int to = from + len;
4709+
/* b == a[from] has been read exactly once, skip to next index. */
4710+
++from;
4711+
/* Skip leading -1 bytes. */
4712+
for (; b == -1 && from < to; b = a[from++])
4713+
; //empty body
4714+
/*
4715+
* A "digit" is a group of 4 adjacent bytes aligned w.r.t. index to.
4716+
* b is the most significant byte not -1, or -1 only if from == to.
4717+
* Digit d0 spans the range of indices that includes current (from - 1).
4718+
* (Implied -1 bytes are prepended to array a as needed.)
4719+
* It usually corresponds to res[0], except for the special case below.
4720+
*/
4721+
int d0 = -1 << 8 | b & 0xFF;
4722+
while (((to - from) & 0x3) != 0) {
4723+
d0 = d0 << 8 | (b = a[from++]) & 0xFF;
4724+
}
4725+
int f = from; // keeps the current from for sizing purposes later
4726+
/* Skip zeros adjacent to d0, if at all. */
4727+
for (; b == 0 && from < to; b = a[from++])
4728+
; //empty body
4729+
/*
4730+
* b is the most significant non-zero byte at or after (f - 1),
4731+
* or 0 only if from == to.
4732+
* Digit d spans the range of indices that includes (f - 1).
4733+
*/
4734+
int d = b & 0xFF;
4735+
while (((to - from) & 0x3) != 0) {
4736+
d = d << 8 | a[from++] & 0xFF;
46684737
}
4669-
4670-
// Add one to one's complement to generate two's complement
4671-
for (int i=result.length-1; i >= 0; i--) {
4672-
result[i] = (int)((result[i] & LONG_MASK) + 1);
4673-
if (result[i] != 0)
4674-
break;
4738+
/*
4739+
* If the situation here is like this:
4740+
* index: f to == from
4741+
* ..., -1,-1, 0,0,0,0, 0,0,0,0, ..., 0,0,0,0
4742+
* digit: d0 d
4743+
* then, as shown, the number of zeros is a positive multiple of 4.
4744+
* The array res needs a minimal length of (1 + 1 + (to - f) / 4)
4745+
* to accommodate the two's complement, including a leading 1.
4746+
* In any other case, there is at least one byte that is non-zero.
4747+
* The array for the two's complement has length (0 + 1 + (to - f) / 4).
4748+
* c is 1, resp., 0 for the two situations.
4749+
*/
4750+
int c = (to - from | d0 | d) == 0 ? 1 : 0;
4751+
int[] res = new int[c + 1 + ((to - f) >> 2)];
4752+
res[0] = c == 0 ? d0 : -1;
4753+
int i = res.length - ((to - from) >> 2);
4754+
if (i > 1) {
4755+
res[i - 1] = d;
46754756
}
4676-
4677-
return result;
4757+
/*
4758+
* Prepare the remaining digits.
4759+
* (to - from) is a multiple of 4, so prepare an int for every 4 bytes.
4760+
* This is a candidate for Unsafe.copy[Swap]Memory().
4761+
*/
4762+
while (from < to) {
4763+
res[i++] = a[from++] << 24 | (a[from++] & 0xFF) << 16
4764+
| (a[from++] & 0xFF) << 8 | (a[from++] & 0xFF);
4765+
}
4766+
/* Convert to two's complement. Here, i == res.length */
4767+
while (--i >= 0 && res[i] == 0)
4768+
; // empty body
4769+
res[i] = -res[i];
4770+
while (--i >= 0) {
4771+
res[i] = ~res[i];
4772+
}
4773+
return res;
46784774
}
46794775

46804776
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import jdk.test.lib.RandomFactory;
25+
import org.junit.jupiter.api.BeforeAll;
26+
import org.junit.jupiter.api.Test;
27+
28+
import java.math.Accessor;
29+
import java.math.BigInteger;
30+
import java.util.Random;
31+
32+
import static org.junit.jupiter.api.Assertions.assertTrue;
33+
34+
/**
35+
* @test
36+
* @bug 8319174
37+
* @summary Exercises minimality of BigInteger.mag field (use -Dseed=X to set PRANDOM seed)
38+
* @library /test/lib
39+
* @build jdk.test.lib.RandomFactory
40+
* @build java.base/java.math.Accessor
41+
* @key randomness
42+
* @run junit/othervm -DmaxDurationMillis=3000 ByteArrayConstructorTest
43+
*/
44+
public class ByteArrayConstructorTest {
45+
46+
private static final int DEFAULT_MAX_DURATION_MILLIS = 3_000;
47+
48+
public static final int N = 1_024;
49+
50+
private static int maxDurationMillis;
51+
private static final Random rnd = RandomFactory.getRandom();
52+
private volatile boolean stop = false;
53+
54+
@BeforeAll
55+
static void setMaxDurationMillis() {
56+
maxDurationMillis = Math.max(maxDurationMillis(), 0);
57+
}
58+
59+
@Test
60+
public void testNonNegative() throws InterruptedException {
61+
byte[] ba = nonNegativeBytes();
62+
doBigIntegers(ba, ba[0]); // a mask to flip to 0 and back to ba[0]
63+
}
64+
65+
@Test
66+
public void testNegative() throws InterruptedException {
67+
byte[] ba = negativeBytes();
68+
doBigIntegers(ba, (byte) ~ba[0]); // a mask to flip to -1 and back to ba[0]
69+
}
70+
71+
/*
72+
* Starts a thread th that keeps flipping the "sign" byte in the array ba
73+
* from the original value to 0 or -1 and back, depending on ba[0] being
74+
* non-negative or negative, resp.
75+
* (ba is "big endian", the least significant byte is the one with the
76+
* highest index.)
77+
*
78+
* In the meantime, the current thread keeps creating BigInteger instances
79+
* with ba and checks that the internal invariant holds, despite the
80+
* attempts by thread th to racily modify ba.
81+
* It does so at least as indicated by maxDurationMillis.
82+
*
83+
* Finally, this thread requests th to stop and joins with it, either
84+
* because maxDurationMillis has expired, or because of an invalid invariant.
85+
*/
86+
private void doBigIntegers(byte[] ba, byte mask) throws InterruptedException {
87+
Thread th = new Thread(() -> {
88+
while (!stop) {
89+
ba[0] ^= mask;
90+
}
91+
});
92+
th.start();
93+
94+
try {
95+
createBigIntegers(maxDurationMillis, ba);
96+
} finally {
97+
stop = true;
98+
th.join(1_000);
99+
}
100+
}
101+
102+
private void createBigIntegers(int maxDurationMillis, byte[] ba) {
103+
long start = System.currentTimeMillis();
104+
while (System.currentTimeMillis() - start < maxDurationMillis) {
105+
BigInteger bi = new BigInteger(ba);
106+
int[] mag = Accessor.mag(bi);
107+
assertTrue(mag.length == 0 || mag[0] != 0,
108+
String.format("inconsistent BigInteger: mag.length=%d, mag[0]=%d",
109+
mag.length, mag[0]));
110+
}
111+
}
112+
113+
private byte[] nonNegativeBytes() {
114+
byte[] ba = new byte[1 + N];
115+
byte b0;
116+
while ((b0 = (byte) rnd.nextInt()) < 0); // empty body
117+
rnd.nextBytes(ba);
118+
ba[0] = b0;
119+
/* Except for ba[0], fill most significant half with zeros. */
120+
for (int i = 1; i <= N / 2; ++i) {
121+
ba[i] = 0;
122+
}
123+
return ba;
124+
}
125+
126+
private byte[] negativeBytes() {
127+
byte[] ba = new byte[1 + N];
128+
byte b0;
129+
while ((b0 = (byte) rnd.nextInt()) >= 0); // empty body
130+
rnd.nextBytes(ba);
131+
ba[0] = b0;
132+
/* Except for ba[0], fill most significant half with -1 bytes. */
133+
for (int i = 1; i <= N / 2; ++i) {
134+
ba[i] = -1;
135+
}
136+
return ba;
137+
}
138+
139+
private static int maxDurationMillis() {
140+
try {
141+
return Integer.parseInt(System.getProperty("maxDurationMillis",
142+
Integer.toString(DEFAULT_MAX_DURATION_MILLIS)));
143+
} catch (NumberFormatException ignore) {
144+
}
145+
return DEFAULT_MAX_DURATION_MILLIS;
146+
}
147+
148+
}

0 commit comments

Comments
 (0)