java-topology/test/jdk/java/lang/StringBuilder/StressSBTest.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

337 lines
17 KiB
Java

/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8351443
* @summary Randomized test of StringBuilder
* @run main StressSBTest
*/
import java.lang.System.Logger;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.CharBuffer;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.IntStream;
public class StressSBTest {
// Logger for info and errors
static final Logger LOG = System.getLogger("StressSBTest");
// Number of concurrent platform threads
private final static int N_THREADS = 11;
// Base length of test run
private final static Duration N_Duration = Duration.ofSeconds(2);
// Cache jtreg timeout factor to allow test to be run as a standalone main()
private static final double TIMEOUT_FACTOR =
Double.parseDouble(System.getProperty("test.timeout.factor", "1.0"));
/**
* Run the stress test with a fixed set of parameters.
*
* @param ignored
*/
public static void main(String[] ignored) {
Duration duration = Duration.ofMillis((long) (N_Duration.toMillis() * TIMEOUT_FACTOR));
new StressSBTest().stress(N_THREADS, duration);
}
/**
* Stress test using a number of platform threads for a duration.
* Success is marked by a lack of exceptions.
* Logging output indicates the number of operations performed by each thread
*
* @param numThreads the number of threads
* @param duration a Duration, typically a few seconds
*/
public void stress(int numThreads, Duration duration) {
StressOps stressOps = new StressOps();
final StringBuilder sb = new StringBuilder("abcdefghijklmnopqrstuvwxyz");
// A single StringBuilder is used to exercise the API in the face of racy behavior
List<Thread> threads = IntStream.range(0, numThreads).mapToObj(i -> {
try {
Thread t = Thread.ofPlatform().start(() -> stressOps.randomStress(sb, duration));
LOG.log(Logger.Level.DEBUG, t);
return t;
} catch (Throwable t) {
LOG.log(Logger.Level.ERROR, "Thread.ofPlatform", t);
}
return null;
})
.filter(t -> t != null)
.toList();
threads.forEach(t -> {
try {
t.join();
} catch (InterruptedException ie) {
// ignore
}
});
LOG.log(Logger.Level.INFO, "Completed: threads: %d, duration: %s".formatted(numThreads, duration));
}
/**
* Methods and support for stress testing StringBuilder.
*/
private static class StressOps {
private static final Random randomGen = new Random();
private static final StringBuilder stringBuilder = new StringBuilder("wxyz");
private static final StringBuffer stringBuffer = new StringBuffer("wxyz");
private static final CharBuffer charBuffer = CharBuffer.allocate(10).append("charBuffer");
private static final char[] charArray = "xyz".toCharArray();
private static final CharSequence charSequence = "now is the time";
// Cache of mappings of all StringBuilder public instand methods mh -> Method
private static Map<MethodHandle, Method> testMethodsCache;
// Active map of SB methods mh -> Method
private final Map<MethodHandle, Method> testMethods;
public StressOps() {
if (testMethodsCache == null) {
testMethodsCache = findTestMethods();
}
testMethods = testMethodsCache;
}
/**
* Stress test randomizes methods acting on a StringBuilder.
* Exceptions are logged but do no affect success/failure result.
*
* @param sb a StringBuilder
* @param duration a Duration of time to stress
*/
public void randomStress(StringBuilder sb, Duration duration) {
MethodHandle[] ops = testMethods.keySet().toArray(new MethodHandle[0]);
int count = 0;
int worked = 0;
Instant end = Instant.now().plus(duration);
while (Instant.now().isBefore(end)) {
int j = randomGen.nextInt(ops.length);
var mh = ops[j];
String name = testMethods.get(mh).getName();
MethodType mt = mh.type();
try {
count++;
if (invokeByMethodNameAndType(name, mt, sb)) {
worked++;
}
} catch (Throwable t) {
LOG.log(Logger.Level.DEBUG, "Exception testing " + name + mt, t);
}
}
LOG.log(Logger.Level.INFO, "StringBuilder method calls: %d, successful: %d".formatted(count, worked));
}
/**
* {@return Return a map of all the public instance methods of java.lang.StringBuilder
* mapping MethodHandle to the Method}
*/
private Map<MethodHandle, Method> findTestMethods() {
MethodHandles.Lookup lookup = MethodHandles.lookup();
Method[] methods = StringBuilder.class.getDeclaredMethods();
Map<MethodHandle, Method> map = new HashMap<>(methods.length);
Arrays.stream(methods)
.filter(m -> Modifier.isPublic(m.getModifiers()) &&
!Modifier.isStatic(m.getModifiers()))
.forEach(m -> {
try {
MethodHandle mh = lookup.unreflect(m);
map.put(mh, m);
} catch (IllegalAccessException iae) {
LOG.log(Logger.Level.DEBUG, "AsbTest annotated method not accessible", iae);
}
});
LOG.log(Logger.Level.DEBUG, "%d StringBuilder test methods".formatted(map.size()));
return map;
}
/**
* Invoke the indicated method on the StringBuilder
*
* @param name the method name
* @param mt the method type
* @param sb a StringBuilder
* @return true if the method returned a non-null result
*/
private boolean invokeByMethodNameAndType(String name, MethodType mt, StringBuilder sb) {
String s = name + mt.toString();
Object o = switch (s) {
case "length(StringBuilder)int" -> sb.length();
case "toString(StringBuilder)String" -> sb.toString();
case "append(StringBuilder,StringBuffer)AbstractStringBuilder" ->
sb.append(stringBuffer);
case "append(StringBuilder,StringBuffer)StringBuilder" -> sb.append(stringBuffer);
case "append(StringBuilder,CharSequence)AbstractStringBuilder" ->
sb.append(charBuffer);
case "append(StringBuilder,CharSequence)StringBuilder" -> sb.append(charBuffer);
case "append(StringBuilder,CharSequence)Appendable" -> sb.append(charBuffer);
case "append(StringBuilder,CharSequence,int,int)AbstractStringBuilder" ->
sb.append("abc", 1, 2);
case "append(StringBuilder,CharSequence,int,int)StringBuilder" ->
sb.append("abc", 1, 2);
case "append(StringBuilder,CharSequence,int,int)Appendable" ->
sb.append("abc", 1, 2);
case "append(StringBuilder,char[])AbstractStringBuilder" -> sb.append(charArray);
case "append(StringBuilder,char[])StringBuilder" -> sb.append(charArray);
case "append(StringBuilder,String)AbstractStringBuilder" -> sb.append("Abcdefg");
case "append(StringBuilder,String)StringBuilder" -> sb.append("abcdefg");
case "append(StringBuilder,Object)AbstractStringBuilder" -> new Object();
case "append(StringBuilder,Object)StringBuilder" -> sb.append(new Object());
case "append(StringBuilder,char)Appendable" -> sb.append('\uff21');
case "append(StringBuilder,char)AbstractStringBuilder" -> sb.append('A');
case "append(StringBuilder,char)StringBuilder" -> sb.append('B');
case "append(StringBuilder,int)AbstractStringBuilder" -> sb.append(987654321);
case "append(StringBuilder,int)StringBuilder" -> sb.append(987654321);
case "append(StringBuilder,long)AbstractStringBuilder" ->
sb.append(987654321987654321L);
case "append(StringBuilder,long)StringBuilder" -> sb.append(987654321987654321L);
case "append(StringBuilder,float)AbstractStringBuilder" -> sb.append(Math.PI);
case "append(StringBuilder,float)StringBuilder" -> sb.append(Math.PI);
case "append(StringBuilder,double)AbstractStringBuilder" -> sb.append(Math.TAU);
case "append(StringBuilder,double)StringBuilder" -> sb.append(Math.TAU);
case "append(StringBuilder,boolean)AbstractStringBuilder" -> sb.append(true);
case "append(StringBuilder,boolean)StringBuilder" -> sb.append(false);
case "append(StringBuilder,char[],int,int)AbstractStringBuilder" ->
sb.append(charArray, 3, 4);
case "append(StringBuilder,char[],int,int)StringBuilder" ->
sb.append(charArray, 4, 3);
case "reverse(StringBuilder)StringBuilder" -> sb.reverse();
case "reverse(StringBuilder)AbstractStringBuilder" -> sb.reverse();
case "getChars(StringBuilder,int,int,char[],int)void" -> {
sb.getChars(2, 4, charArray, 3);
yield sb;
}
case "compareTo(StringBuilder,Object)int" -> sb.compareTo(stringBuilder);
case "compareTo(StringBuilder,StringBuilder)int" -> sb.compareTo(stringBuilder);
case "indexOf(StringBuilder,String,int)int" -> sb.indexOf("A", 2);
case "indexOf(StringBuilder,String)int" -> sb.indexOf("B");
case "insert(StringBuilder,int,CharSequence)StringBuilder" ->
sb.insert(2, charSequence);
case "insert(StringBuilder,int,CharSequence)AbstractStringBuilder" ->
sb.insert(2, charSequence);
case "insert(StringBuilder,int,String)StringBuilder" -> sb.insert(2, "Now");
case "insert(StringBuilder,int,String)AbstractStringBuilder" ->
sb.insert(2, "Then");
case "insert(StringBuilder,int,char[])StringBuilder" -> sb.insert(4, charArray);
case "insert(StringBuilder,int,char[])AbstractStringBuilder" ->
sb.insert(4, charArray);
case "insert(StringBuilder,int,Object)AbstractStringBuilder" ->
sb.insert(3, new Object());
case "insert(StringBuilder,int,Object)StringBuilder" -> sb.insert(4, "RUST");
case "insert(StringBuilder,int,char[],int,int)AbstractStringBuilder" ->
sb.insert(2, charArray, 0, 4);
case "insert(StringBuilder,int,char[],int,int)StringBuilder" ->
sb.insert(2, charArray, 0, 4);
case "insert(StringBuilder,int,int)StringBuilder" -> sb.insert(2, 46000);
case "insert(StringBuilder,int,int)AbstractStringBuilder" -> sb.insert(3, 47000);
case "insert(StringBuilder,int,double)StringBuilder" -> sb.insert(2, Math.PI);
case "insert(StringBuilder,int,double)AbstractStringBuilder" ->
sb.insert(2, Math.PI);
case "insert(StringBuilder,int,float)StringBuilder" -> sb.insert(2, Math.TAU);
case "insert(StringBuilder,int,float)AbstractStringBuilder" ->
sb.insert(2, Math.TAU);
case "insert(StringBuilder,int,long)StringBuilder" -> sb.insert(2, 42L);
case "insert(StringBuilder,int,long)AbstractStringBuilder" -> sb.insert(2, 42L);
case "insert(StringBuilder,int,char)StringBuilder" -> sb.insert(2, 'Z');
case "insert(StringBuilder,int,char)AbstractStringBuilder" -> sb.insert(2, 'Z');
case "insert(StringBuilder,int,boolean)StringBuilder" -> sb.insert(2, true);
case "insert(StringBuilder,int,boolean)AbstractStringBuilder" ->
sb.insert(3, false);
case "insert(StringBuilder,int,CharSequence,int,int)StringBuilder" ->
sb.insert(4, charSequence, 4, 5);
case "insert(StringBuilder,int,CharSequence,int,int)AbstractStringBuilder" ->
sb.insert(4, charSequence, 5, 4);
case "charAt(StringBuilder,int)char" -> sb.charAt(5);
case "codePointAt(StringBuilder,int)int" -> sb.codePointAt(4);
case "codePointBefore(StringBuilder,int)int" -> sb.codePointBefore(3);
case "codePointCount(StringBuilder,int,int)int" -> sb.codePointCount(3, 9);
case "offsetByCodePoints(StringBuilder,int,int)int" -> sb.offsetByCodePoints(3, 7);
case "lastIndexOf(StringBuilder,String,int)int" -> sb.lastIndexOf("A", 45);
case "lastIndexOf(StringBuilder,String)int" -> sb.lastIndexOf("B");
case "substring(StringBuilder,int)String" -> sb.substring(6);
case "substring(StringBuilder,int,int)String" -> sb.substring(6, 9);
case "replace(StringBuilder,int,int,String)StringBuilder" ->
sb.replace(2, 5, "xyz");
case "replace(StringBuilder,int,int,String)AbstractStringBuilder" ->
sb.replace(2, 5, "XYZ");
case "repeat(StringBuilder,CharSequence,int)StringBuilder" -> sb.repeat('Z', 6);
case "repeat(StringBuilder,CharSequence,int)AbstractStringBuilder" ->
sb.repeat('X', 42);
case "repeat(StringBuilder,int,int)AbstractStringBuilder" -> sb.repeat(101, 25);
case "repeat(StringBuilder,int,int)StringBuilder" -> sb.repeat(102, 24);
case "codePoints(StringBuilder)IntStream" -> sb.codePoints().count();
case "subSequence(StringBuilder,int,int)CharSequence" -> sb.subSequence(3, 9);
case "chars(StringBuilder)IntStream" -> sb.chars().count();
case "setLength(StringBuilder,int)void" -> {
sb.setLength(45);
yield 45;
}
case "capacity(StringBuilder)int" -> sb.capacity();
case "ensureCapacity(StringBuilder,int)void" -> {
sb.ensureCapacity(150);
yield 150;
}
case "trimToSize(StringBuilder)void" -> {
sb.trimToSize();
yield sb.length();
}
case "setCharAt(StringBuilder,int,char)void" -> {
sb.setCharAt(6, 'T');
yield 'T';
}
case "appendCodePoint(StringBuilder,int)StringBuilder" ->
sb.appendCodePoint(0xff21);
case "appendCodePoint(StringBuilder,int)AbstractStringBuilder" ->
sb.appendCodePoint(0xff21);
case "delete(StringBuilder,int,int)StringBuilder" -> sb.delete(5, 20);
case "delete(StringBuilder,int,int)AbstractStringBuilder" -> sb.delete(6, 21);
case "deleteCharAt(StringBuilder,int)AbstractStringBuilder" -> sb.deleteCharAt(7);
case "deleteCharAt(StringBuilder,int)StringBuilder" -> sb.deleteCharAt(8);
case "equals(Object,Object)boolean" -> false;
case "hashCode(Object)int" -> sb.hashCode();
case "getClass(Object)Class" -> sb.getClass();
case "isEmpty(CharSequence)boolean" -> null;
default -> {
System.out.println("not executing: " + s);
yield null;
}
};
return o != null;
}
}
}