This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-bcel.git
The following commit(s) were added to refs/heads/master by this push:
new 6f764382 Make wide flag thread-local in Utility.codeToString (#502)
6f764382 is described below
commit 6f764382b56a9e695ac204f984469a62c77a9f0a
Author: Dexter.k <[email protected]>
AuthorDate: Tue Jun 16 10:21:51 2026 +0000
Make wide flag thread-local in Utility.codeToString (#502)
* make wide flag thread-local in Utility.codeToString
shared static wide bleeds between threads disassembling code, making one
thread decode the next local-variable index as 2 bytes instead of 1; keep it
per-thread like the adjacent CONSUMER_CHARS.
* use Assertions.fail instead of throwing AssertionError in test
---
.../java/org/apache/bcel/classfile/Utility.java | 14 +++++-----
.../org/apache/bcel/classfile/UtilityTest.java | 30 ++++++++++++++++++++++
2 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/src/main/java/org/apache/bcel/classfile/Utility.java
b/src/main/java/org/apache/bcel/classfile/Utility.java
index 7b8e7fa9..43e714b7 100644
--- a/src/main/java/org/apache/bcel/classfile/Utility.java
+++ b/src/main/java/org/apache/bcel/classfile/Utility.java
@@ -141,9 +141,9 @@ public abstract class Utility {
/*
* The 'WIDE' instruction is used in the byte code to allow 16-bit wide
indices for local variables. This opcode
* precedes an 'ILOAD', for example. The opcode immediately following
takes an extra byte which is combined with the following
- * byte to form a 16-bit value.
+ * byte to form a 16-bit value. Read across consecutive codeToString()
calls, so kept per-thread like CONSUMER_CHARS.
*/
- private static boolean wide;
+ private static final ThreadLocal<Boolean> WIDE =
ThreadLocal.withInitial(() -> Boolean.FALSE);
// A-Z, g-z, _, $
private static final int FREE_CHARS = 48;
@@ -419,9 +419,9 @@ public abstract class Utility {
case Const.LLOAD:
case Const.LSTORE:
case Const.RET:
- if (wide) {
+ if (WIDE.get().booleanValue()) {
vindex = bytes.readUnsignedShort();
- wide = false; // Clear flag
+ WIDE.set(Boolean.FALSE); // Clear flag
} else {
vindex = bytes.readUnsignedByte();
}
@@ -432,7 +432,7 @@ public abstract class Utility {
* called again with the following opcode.
*/
case Const.WIDE:
- wide = true;
+ WIDE.set(Boolean.TRUE);
buf.append("\t(wide)");
break;
/*
@@ -527,10 +527,10 @@ public abstract class Utility {
* Increment local variable.
*/
case Const.IINC:
- if (wide) {
+ if (WIDE.get().booleanValue()) {
vindex = bytes.readUnsignedShort();
constant = bytes.readShort();
- wide = false;
+ WIDE.set(Boolean.FALSE);
} else {
vindex = bytes.readUnsignedByte();
constant = bytes.readByte();
diff --git a/src/test/java/org/apache/bcel/classfile/UtilityTest.java
b/src/test/java/org/apache/bcel/classfile/UtilityTest.java
index 769c75c1..130f46fe 100644
--- a/src/test/java/org/apache/bcel/classfile/UtilityTest.java
+++ b/src/test/java/org/apache/bcel/classfile/UtilityTest.java
@@ -24,11 +24,14 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.bcel.Const;
import org.apache.bcel.Repository;
+import org.apache.bcel.util.ByteSequence;
import org.junit.jupiter.api.Test;
class UtilityTest {
@@ -178,4 +181,31 @@ class UtilityTest {
assertEquals("<K extends Object, V extends Object> extends Object",
Utility.signatureToString("<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/lang/Object;"),
"class signature");
}
+
+ @Test
+ void testCodeToStringWideIsThreadLocal() throws Exception {
+ // A WIDE opcode disassembled on one thread must not change how the
next
+ // local-variable instruction is decoded on another thread.
+ final Thread setter = new Thread(() -> {
+ try {
+ Utility.codeToString(new ByteSequence(new byte[] {(byte)
Const.WIDE}), new ConstantPool(), false);
+ } catch (final Exception e) {
+ fail("WIDE disassembly failed", e);
+ }
+ });
+ setter.start();
+ setter.join();
+ final AtomicReference<String> reader = new AtomicReference<>();
+ final Thread thread = new Thread(() -> {
+ try {
+ // iload with a single index byte; the trailing 0x02 must stay
unread.
+ reader.set(Utility.codeToString(new ByteSequence(new byte[]
{(byte) Const.ILOAD, 1, 2}), new ConstantPool(), false));
+ } catch (final Exception e) {
+ fail("iload disassembly failed", e);
+ }
+ });
+ thread.start();
+ thread.join();
+ assertEquals("iload\t\t%1", reader.get());
+ }
}