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 3c8957a4 Cubic handlers × range × subroutines scan in Subroutines 
constructor hangs JustIce Pass 3b (f007).
3c8957a4 is described below

commit 3c8957a48d2f3595930c199f71adc0a3c99f9b49
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Sep 4 16:03:00 2026 -0400

    Cubic handlers × range × subroutines scan in Subroutines constructor
    hangs JustIce Pass 3b (f007).
---
 src/changes/changes.xml                            |  1 +
 .../bcel/verifier/structurals/Subroutines.java     | 68 ++++++++++++++++------
 2 files changed, 52 insertions(+), 17 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 25dcc758..4003f910 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -94,6 +94,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">JustIce Pass 2 hangs on cyclic superclass chain of a referenced 
exception class (f004).</action>
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">JustIce Pass 2 LVT check amplifies each 10-byte entry into ~131k 
hashtable operations (f005).</action>
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">Pass 3b dataflow stores frames per instruction and clones ever-growing 
execution chains (f006).</action>
+      <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">Cubic handlers × range × subroutines scan in Subroutines constructor 
hangs JustIce Pass 3b (f007).</action>
       <!-- ADD -->
       <action                  type="add" dev="ggregory" due-to="nbauma109, 
Gary Gregory">Add support for permitted subclasses #493.</action>
       <action                  type="add" dev="ggregory" due-to="nbauma109, 
Gary Gregory">Add RecordComponentInfo.getAttribute(byte tag)#494.</action>
diff --git 
a/src/main/java/org/apache/bcel/verifier/structurals/Subroutines.java 
b/src/main/java/org/apache/bcel/verifier/structurals/Subroutines.java
index 8f798b51..6addf24d 100644
--- a/src/main/java/org/apache/bcel/verifier/structurals/Subroutines.java
+++ b/src/main/java/org/apache/bcel/verifier/structurals/Subroutines.java
@@ -469,10 +469,10 @@ public class Subroutines {
         final List<InstructionHandle> qList = new ArrayList<>();
         for (final InstructionHandle actual : subLeaders) {
             // Do some BFS with "actual" as the root of the graph.
-            // Init colors
-            for (final InstructionHandle element : all) {
-                colors.put(element, ColourConstants.WHITE);
-            }
+            // Init colors: an instruction absent from the map is WHITE. 
Explicitly coloring every
+            // instruction WHITE on every round would make this initialization 
quadratic in the
+            // method size.
+            colors.clear();
             colors.put(actual, ColourConstants.GRAY);
             // Init Queue
 
@@ -496,16 +496,18 @@ public class Subroutines {
                 final InstructionHandle u = qList.remove(0);
                 final InstructionHandle[] successors = getSuccessors(u);
                 for (final InstructionHandle successor : successors) {
-                    if (colors.get(successor) == ColourConstants.WHITE) {
+                    if (successor != null && colors.get(successor) == null) { 
// absent from the map means WHITE
                         colors.put(successor, ColourConstants.GRAY);
                         qList.add(successor);
                     }
                 }
                 colors.put(u, ColourConstants.BLACK);
             }
-            // BFS ended above.
-            for (final InstructionHandle element : all) {
-                if (colors.get(element) == ColourConstants.BLACK) {
+            // BFS ended above. Only instructions visited by this BFS round 
are in the color map,
+            // so this scan is proportional to the round, not to the whole 
method.
+            for (final Map.Entry<InstructionHandle, ColourConstants> entry : 
colors.entrySet()) {
+                if (entry.getValue() == ColourConstants.BLACK) {
+                    final InstructionHandle element = entry.getKey();
                     ((SubroutineImpl) (actual == all[0] ? getTopLevel() : 
getSubroutine(actual))).addInstruction(element);
                     if (instructionsAssigned.contains(element)) {
                         throw new StructuralCodeConstraintException(
@@ -519,20 +521,52 @@ public class Subroutines {
             }
         }
 
-        if (enableJustIceCheck) {
+        if (enableJustIceCheck && handlers.length > 0) {
             // Now make sure no instruction of a Subroutine is protected by 
exception handling code
             // as is mandated by JustIces notion of subroutines.
+            // The handler coverage of every instruction is computed once, 
with a difference array
+            // over instruction list indices. Walking every handler's 
protected range and, per
+            // protected instruction, every subroutine would let a crafted 
method (thousands of
+            // handlers over large ranges) keep this constructor busy nearly 
forever.
+            final Map<InstructionHandle, Integer> instructionIndexes = new 
HashMap<>();
+            for (int i = 0; i < all.length; i++) {
+                instructionIndexes.put(all[i], Integer.valueOf(i));
+            }
+            final int[] coverageDelta = new int[all.length + 1];
             for (final CodeExceptionGen handler : handlers) {
-                InstructionHandle protectedIh = handler.getStartPC();
-                while (protectedIh != handler.getEndPC().getNext()) {
-                    // Note the inclusive/inclusive notation of "generic API" 
exception handlers!
-                    for (final Subroutine sub : subroutines.values()) {
-                        if (sub != subroutines.get(all[0]) && 
sub.contains(protectedIh)) {
-                            throw new 
StructuralCodeConstraintException("Subroutine instruction '" + protectedIh + "' 
is protected by an exception handler, '"
-                                + handler + "'. This is forbidden by the 
JustIce verifier due to its clear definition of subroutines.");
+                // Note the inclusive/inclusive notation of "generic API" 
exception handlers!
+                final Integer startIndex = 
instructionIndexes.get(handler.getStartPC());
+                final Integer endIndex = 
instructionIndexes.get(handler.getEndPC());
+                if (startIndex == null || endIndex == null || 
startIndex.intValue() > endIndex.intValue()) {
+                    throw new StructuralCodeConstraintException("Exception 
handler '" + handler + "' does not protect a valid instruction range.");
+                }
+                coverageDelta[startIndex.intValue()]++;
+                coverageDelta[endIndex.intValue() + 1]--;
+            }
+            final boolean[] isProtected = new boolean[all.length];
+            int covered = 0;
+            for (int i = 0; i < all.length; i++) {
+                covered += coverageDelta[i];
+                isProtected[i] = covered > 0;
+            }
+            for (final Subroutine sub : subroutines.values()) {
+                if (sub == subroutines.get(all[0])) {
+                    continue;
+                }
+                for (final InstructionHandle protectedIh : 
sub.getInstructions()) {
+                    final Integer index = instructionIndexes.get(protectedIh);
+                    if (index != null && isProtected[index.intValue()]) {
+                        // Only the error message needs the offending handler; 
this scan runs at most once.
+                        for (final CodeExceptionGen handler : handlers) {
+                            final int startIndex = 
instructionIndexes.get(handler.getStartPC()).intValue();
+                            final int endIndex = 
instructionIndexes.get(handler.getEndPC()).intValue();
+                            if (startIndex <= index.intValue() && 
index.intValue() <= endIndex) {
+                                throw new 
StructuralCodeConstraintException("Subroutine instruction '" + protectedIh
+                                    + "' is protected by an exception handler, 
'" + handler
+                                    + "'. This is forbidden by the JustIce 
verifier due to its clear definition of subroutines.");
+                            }
                         }
                     }
-                    protectedIh = protectedIh.getNext();
                 }
             }
         }

Reply via email to