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 dc3e8989 JustIce Pass 2 hangs on cyclic superclass chain of a 
referenced exception class (f004).
dc3e8989 is described below

commit dc3e8989a2e6ce614a36e633a9f404eeba6c7e2f
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Sep 4 15:39:59 2026 -0400

    JustIce Pass 2 hangs on cyclic superclass chain of a referenced
    exception class (f004).
---
 src/changes/changes.xml                            |  1 +
 .../bcel/verifier/statics/Pass2Verifier.java       | 11 +++++
 .../bcel/verifier/statics/Pass2VerifierTest.java   | 47 ++++++++++++++++++++++
 3 files changed, 59 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index e76b7f18..4bad4357 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -91,6 +91,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">Nested Code/Record attributes drive unbounded parse-time recursion in 
ClassParser (f001).</action>
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">Nested annotation element values recurse unboundedly; 
MAX_ARRAY_DIMENSIONS cap bypassed (f002).</action>
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">Opcodes tableswitch and lookupswitch add boundary checks 
(f003).</action>
+      <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">JustIce Pass 2 hangs on cyclic superclass chain of a referenced 
exception class (f004).</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/statics/Pass2Verifier.java 
b/src/main/java/org/apache/bcel/verifier/statics/Pass2Verifier.java
index a362ce09..1826a5bb 100644
--- a/src/main/java/org/apache/bcel/verifier/statics/Pass2Verifier.java
+++ b/src/main/java/org/apache/bcel/verifier/statics/Pass2Verifier.java
@@ -199,10 +199,16 @@ public final class Pass2Verifier extends PassVerifier 
implements Constants {
                         JavaClass e = Repository.lookupClass(cname);
                         final JavaClass t = 
Repository.lookupClass(Type.THROWABLE.getClassName());
                         final JavaClass o = 
Repository.lookupClass(Type.OBJECT.getClassName());
+                        final Set<String> ancestors = new HashSet<>(); // save 
class names to detect circular inheritance
                         while (e != o) {
                             if (e == t) {
                                 break; // It's a subclass of Throwable, OKAY, 
leave.
                             }
+                            if (!ancestors.add(e.getClassName())) {
+                                throw new ClassConstraintException("Code 
attribute '" + tostring(obj) + "' (method '" + m + "') has an exception_table 
entry '"
+                                        + tostring(element) + "' that 
references '" + cname
+                                        + "' as an Exception but its 
superclass hierarchy is circular at '" + e.getClassName() + "'.");
+                            }
 
                             v = 
VerifierFactory.getVerifier(e.getSuperclassName());
                             vr = v.doPass1();
@@ -528,10 +534,15 @@ public final class Pass2Verifier extends PassVerifier 
implements Constants {
                     JavaClass e = Repository.lookupClass(cname);
                     final JavaClass t = 
Repository.lookupClass(Type.THROWABLE.getClassName());
                     final JavaClass o = 
Repository.lookupClass(Type.OBJECT.getClassName());
+                    final Set<String> ancestors = new HashSet<>(); // save 
class names to detect circular inheritance
                     while (e != o) {
                         if (e == t) {
                             break; // It's a subclass of Throwable, OKAY, 
leave.
                         }
+                        if (!ancestors.add(e.getClassName())) {
+                            throw new ClassConstraintException("Exceptions 
attribute '" + tostring(obj) + "' references '" + cname
+                                + "' as an Exception but its superclass 
hierarchy is circular at '" + e.getClassName() + "'.");
+                        }
 
                         v = VerifierFactory.getVerifier(e.getSuperclassName());
                         vr = v.doPass1();
diff --git 
a/src/test/java/org/apache/bcel/verifier/statics/Pass2VerifierTest.java 
b/src/test/java/org/apache/bcel/verifier/statics/Pass2VerifierTest.java
index 98950f81..10325334 100644
--- a/src/test/java/org/apache/bcel/verifier/statics/Pass2VerifierTest.java
+++ b/src/test/java/org/apache/bcel/verifier/statics/Pass2VerifierTest.java
@@ -19,11 +19,58 @@
 
 package org.apache.bcel.verifier.statics;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
+
+import java.time.Duration;
+
+import org.apache.bcel.Const;
 import org.apache.bcel.Constants;
+import org.apache.bcel.Repository;
+import org.apache.bcel.generic.ClassGen;
+import org.apache.bcel.generic.InstructionFactory;
+import org.apache.bcel.generic.InstructionList;
+import org.apache.bcel.generic.MethodGen;
+import org.apache.bcel.generic.Type;
+import org.apache.bcel.verifier.VerificationResult;
+import org.apache.bcel.verifier.Verifier;
+import org.apache.bcel.verifier.VerifierFactory;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 
 class Pass2VerifierTest {
 
+    @AfterEach
+    void afterEach() {
+        VerifierFactory.clear();
+        Repository.clearCache();
+    }
+
+    /**
+     * Tests that a referenced exception class with a circular superclass 
hierarchy is rejected instead of looping forever.
+     */
+    @Test
+    void testCyclicExceptionSuperclassChainRejected() {
+        final ClassGen cgA = new ClassGen("Pass2CyclicA", "Pass2CyclicB", 
"Pass2CyclicA.java", Const.ACC_PUBLIC | Const.ACC_SUPER, new String[0]);
+        final ClassGen cgB = new ClassGen("Pass2CyclicB", "Pass2CyclicA", 
"Pass2CyclicB.java", Const.ACC_PUBLIC | Const.ACC_SUPER, new String[0]);
+        Repository.addClass(cgA.getJavaClass());
+        Repository.addClass(cgB.getJavaClass());
+        final String className = "Pass2CyclicX";
+        final ClassGen cg = new ClassGen(className, "java.lang.Object", 
"Pass2CyclicX.java", Const.ACC_PUBLIC | Const.ACC_SUPER, new String[0]);
+        final InstructionList il = new InstructionList();
+        il.append(InstructionFactory.createReturn(Type.VOID));
+        final MethodGen mg = new MethodGen(Const.ACC_PUBLIC | 
Const.ACC_STATIC, Type.VOID, Type.NO_ARGS, new String[0], "test", className, il,
+                cg.getConstantPool());
+        mg.addException("Pass2CyclicA");
+        mg.setMaxStack();
+        mg.setMaxLocals();
+        cg.addMethod(mg.getMethod());
+        Repository.addClass(cg.getJavaClass());
+        final Verifier verifier = VerifierFactory.getVerifier(className);
+        assertEquals(VerificationResult.VR_OK, verifier.doPass1());
+        assertTimeoutPreemptively(Duration.ofSeconds(30), () -> 
assertEquals(VerificationResult.VERIFIED_REJECTED, 
verifier.doPass2().getStatus()));
+    }
+
     /**
      * Tests that we do not break binary compatibility with BCEL-330.
      */

Reply via email to