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.
*/