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());
+    }
 }

Reply via email to