In ASM 5.0.3:
TypePath.fromString() and TypePath.toString() both describe path elements for navigating type arguments
of a parameterized type as a single decimal digit. This doesn't adequately support types that have > 10
type arguments.
Looking at the javadoc and even the source doesn't reveal anything obvious about how values > 10th (e.g.
> '9') should be handled.
The source for TypePath.fromString() looks like it reads a sequence of decimal digits as a single "type
argument" path element. But then there's no obvious separator between multiple type argument path
elements (though the code looks like it would accept anything other than a decimal digit or ".", "*", or "]").
The source for TypePath.toString(), on the other hand, looks like it will not correctly handle values for type
argument indices > '9'. It simply concatenates adjacent path elements. So a path of:
type_argument(1), type_argument(0)
of a nested generic type is indistinguishable from the path:
type_argument(10)
of a generic type with >10 type arguments.
When using ASMifier to generate source code for a class, it looks like it is trying to use adjacent single
decimal digits for each path step (which won't actually work since fromString() will combine them into a
single step). If any single step has value > '9', then it inserts period separators (e.g. ".10.1" is the type
argument at index 11 and then 1 where as "101" is the type argument at index 1 then 0 then 1). However,
periods are defined in the toString() doc (and in the actual implementation code) to mean
nested/enclosed type path elements, not type arguments.
We can see the above ambiguity in action with the following sample code:
----
import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.ASMifier;
import org.objectweb.asm.util.TraceClassVisitor;
public class TypePathBug {
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface A {
}
class T<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12> {
}
class U<X, Y, Z> extends T<X, Map<@A Y, Z>, X, Y, Z, X, Y, Z, X, Y, @A Z, X> {
}
public static void main(String args[]) throws Exception {
new ClassReader(U.class.getName().replace('.', '/')).accept(
new TraceClassVisitor(null, new ASMifier(),
new PrintWriter(System.out)), 0);
}
}
----
The resulting output generates code for the two @A annotations that is identical and thus
indistinguishable -- which means it cannot be correct since they are distinct/different type paths. Here is
the code generated for both type annotations:
{
av0 = cw.visitTypeAnnotation(285212416, TypePath.fromString(".10"), "LTypePathBug$A;", true);
av0.visitEnd();
}
----
Were I propose a fix: decimal path elements that indicate type parameter indices are terminated with a
semicolon. All of TypePath.toString(), TypePath.fromString(), and ASMifier would need to be updated. The
above ambiguous type path, ".10" would instead be represented as "1;0;" for the first occurrence of @A
and "10;" for the second. TypePath.fromString() can be lenient and not require ";" termination if it instead
encounters end-of-string, ".", "[", or "*".