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