diff --git a/asm/src/main/java/org/objectweb/asm/signature/SignatureWriter.java b/asm/src/main/java/org/objectweb/asm/signature/SignatureWriter.java
index 2217a88aeea8dc77fa5dd80a94c6eb80d7cb8c40..c834eef0533b79a8d41a79dd537fcadeee533147 100644
--- a/asm/src/main/java/org/objectweb/asm/signature/SignatureWriter.java
+++ b/asm/src/main/java/org/objectweb/asm/signature/SignatureWriter.java
@@ -27,6 +27,8 @@
// THE POSSIBILITY OF SUCH DAMAGE.
package org.objectweb.asm.signature;
+import java.util.ArrayList;
+import java.util.List;
import org.objectweb.asm.Opcodes;
/**
@@ -52,7 +54,7 @@ public class SignatureWriter extends SignatureVisitor {
/**
* The stack used to keep track of class types that have arguments. Each element of this stack is
* a boolean encoded in one bit. The top of the stack is the least significant bit. Pushing false
- * = *2, pushing true = *2+1, popping = /2.
+ * {@code <<= 1}, pushing true {@code <<= 1; |= 1}, popping {@code >>>= 1}.
*
*
Class type arguments must be surrounded with '<' and '>' and, because
*
@@ -62,12 +64,18 @@ public class SignatureWriter extends SignatureVisitor {
* SignatureWriter instances),
*
*
- *
we need a stack to properly balance these 'parentheses'. A new element is pushed on this
+ *
we need a stack to properly balance these angle brackets. A new element is pushed on this
* stack for each new visited type, and popped when the visit of this type ends (either is
* visitEnd, or because visitInnerClassType is called).
*/
private int argumentStack;
+ /** The sum of the depths of {@link #argumentStack} and {@link #deepArgStack}. */
+ private long argumentStackDepth;
+
+ /** Used when {@link #argumentStack} can no longer fit into a single 32-bit integer. */
+ private List deepArgStack;
+
/** Constructs a new {@link SignatureWriter}. */
public SignatureWriter() {
super(/* latest api =*/ Opcodes.ASM9);
@@ -159,7 +167,7 @@ public class SignatureWriter extends SignatureVisitor {
stringBuilder.append(name);
// Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as
// we can tell at this point).
- argumentStack *= 2;
+ pushArgStack(false);
}
@Override
@@ -169,7 +177,7 @@ public class SignatureWriter extends SignatureVisitor {
stringBuilder.append(name);
// Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as
// we can tell at this point).
- argumentStack *= 2;
+ pushArgStack(false);
}
@Override
@@ -177,7 +185,7 @@ public class SignatureWriter extends SignatureVisitor {
// If the top of the stack is 'false', this means we are visiting the first type argument of the
// currently visited type. We therefore need to append a '<', and to replace the top stack
// element with 'true' (meaning that the current type does have type arguments).
- if (argumentStack % 2 == 0) {
+ if (argumentStackDepth > 0 && !peekArgStack()) {
argumentStack |= 1;
stringBuilder.append('<');
}
@@ -189,7 +197,7 @@ public class SignatureWriter extends SignatureVisitor {
// If the top of the stack is 'false', this means we are visiting the first type argument of the
// currently visited type. We therefore need to append a '<', and to replace the top stack
// element with 'true' (meaning that the current type does have type arguments).
- if (argumentStack % 2 == 0) {
+ if (argumentStackDepth > 0 && !peekArgStack()) {
argumentStack |= 1;
stringBuilder.append('<');
}
@@ -232,9 +240,40 @@ public class SignatureWriter extends SignatureVisitor {
// If the top of the stack is 'true', this means that some type arguments have been visited for
// the type whose visit is now ending. We therefore need to append a '>', and to pop one element
// from the stack.
- if (argumentStack % 2 == 1) {
+ if (argumentStackDepth > 0 && popArgStack()) {
stringBuilder.append('>');
}
- argumentStack /= 2;
+ }
+
+ private boolean peekArgStack() {
+ assert argumentStackDepth > 0 : this;
+ return (argumentStack & 1) == 1;
+ }
+
+ private void pushArgStack(final boolean state) {
+ if (argumentStackDepth > 0 && (argumentStackDepth % 32) == 0) {
+ if (deepArgStack == null) {
+ deepArgStack = new ArrayList<>(5);
+ }
+ deepArgStack.add(argumentStack);
+ argumentStack = 0;
+ } else {
+ argumentStack <<= 1;
+ }
+ argumentStackDepth++;
+ if (state) {
+ argumentStack |= 1;
+ }
+ }
+
+ private boolean popArgStack() {
+ assert argumentStackDepth > 0 : this;
+ boolean result = (argumentStack & 1) == 1;
+ argumentStack >>>= 1;
+ argumentStackDepth--;
+ if (argumentStackDepth > 0 && (argumentStackDepth % 32) == 0) {
+ argumentStack = deepArgStack.remove(deepArgStack.size() - 1);
+ }
+ return result;
}
}
diff --git a/asm/src/test/java/org/objectweb/asm/signature/SignatureWriterTest.java b/asm/src/test/java/org/objectweb/asm/signature/SignatureWriterTest.java
index be1ba012236db594b9f0dd644e22bf9f5d51094c..3717f4bdfe07f71778d5421035c1ce56b2e5bb08 100644
--- a/asm/src/test/java/org/objectweb/asm/signature/SignatureWriterTest.java
+++ b/asm/src/test/java/org/objectweb/asm/signature/SignatureWriterTest.java
@@ -29,6 +29,7 @@ package org.objectweb.asm.signature;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.util.stream.IntStream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.objectweb.asm.test.AsmTest;
@@ -40,6 +41,10 @@ import org.objectweb.asm.test.AsmTest;
*/
class SignatureWriterTest extends AsmTest {
+ private static final String TEST_CLOSE = "TestClose";
+ private static final String TEST_GENERIC = "TestGeneric";
+ private static final String TEST_OPEN = "TestOpen";
+
@ParameterizedTest
@MethodSource({
"org.objectweb.asm.signature.SignaturesProviders#classSignatures",
@@ -62,4 +67,47 @@ class SignatureWriterTest extends AsmTest {
assertEquals(signature, signatureWriter.toString());
}
+
+ static IntStream deepSignatures() {
+ return IntStream.range(0, 48);
+ }
+
+ @ParameterizedTest
+ @MethodSource("deepSignatures")
+ void testWrite_deepSignature(final int depth) {
+ SignatureWriter signatureWriter = new SignatureWriter();
+ String expected = writeDeepSignature(signatureWriter, depth);
+ assertEquals(expected, signatureWriter.toString(), "depth=" + depth);
+ }
+
+ private String writeDeepSignature(final SignatureVisitor signatureVisitor, final int maxDepth) {
+ StringBuilder expected = new StringBuilder();
+ writeDeepSignatureInner(signatureVisitor, expected, 0, maxDepth);
+ return expected.toString();
+ }
+
+ private void writeDeepSignatureInner(
+ final SignatureVisitor signatureVisitor,
+ final StringBuilder expected,
+ final int currentDepth,
+ final int maxDepth) {
+ signatureVisitor.visitClassType(TEST_GENERIC);
+ expected.append('L' + TEST_GENERIC);
+ if (currentDepth < maxDepth) {
+ expected.append("");
+ }
+ signatureVisitor.visitEnd();
+ expected.append(';');
+ }
}