Duplicate CONSTANT_Class CP entries with the same name as this_class cause the Android verifier to fail
(Even though I'm referring to Android here I want to make it clear I'm definitely talking about asm & not
asmdex since we're invoking asm on a jar prior to the dexing processes. I'm not sure if asmdex is
affected in the same way.)
Information is lost during a direct transformation between a ClassReader & a ClassWriter that may lead
to verification errors on the Dalvik VM. This can occur when duplicate CONSTANT_Class entries exist for
a class with the same name, and at least one of those CONSTANT_Class entries is referenced by the
this_class field in the classfile header.
A minimal example of how this might occur:
# Constant Pool
(assume this_class=#1)
const #1 = class #6;
const #2 = class #6;
const #3 = class #6;
const #4 = Method #1.#5;
const #5 = NameAndType #7.#8;
const #6 = Asciz com/foo/Foo;
const #7 = Asciz bar;
const #8 = Asciz ()V;
Executing "invokespecial #4" *before* running asm will work fine, because the CONSTANT_Class
associated with the method in const #4 is the class in const #1 -- which matches this_class.
However, *after* running asm, if the constants get bucketed in the `items` hashtable in ClassWriter in
such a way that we resolve this_class as const #2 or const #3 instead of #1, "invokespecial #4" may
trigger an error in the verifier. The disconnect between this_class and const #4 is the main culprit --
because this_class != #4, the verifier thinks we're trying to call a private method on another class.
Although a fix for the immediate problem might be to simply save the this_class CP index (and maybe
super_class) & using that wherever we try to visit a class matching the name associated with "this_class",
I haven't really convinced myself that doing so won't lead to other strange behavior. Further, the CP
index resolved for a given name can *change* during bytecode emission as the items hashtable grows &
entries get rebucketed/reordered (see ClassWriter.put). I'm not sure if there are other ways that this
problem can manifest itself.
I haven't spent the time to figure out if Oracle JVMs complain about the same code. It may be that,
strictly speaking, this is a bug in the Android verifier. That said, it feels like a direct translation between
a ClassReader & a ClassWriter should "do no harm", and I don't think asm's behavior here is expected.
I can confirm that the output from javap changes drastically when there is a disconnect between
this_class and Constant_Methodref. You see a lot of things like:
invokespecial #4 // Method bar:V()
Changing to:
invokespecial #4 // Method com/foo/Foo.bar:V()
This alone seems to imply to me that we're doing something slightly funky.
Unfortunately we're seeing this in a third party library & we need to work around it. To do so, I'm
attempting to merge duplicate CONSTANT_Class entries in the constant pool during
ClassReader.copyPool(...) to avoid this issue, but I'm relatively new to asm -- so if anybody has a better
approach / thoughts on why this might not be a great idea I'd love to hear. Happy to submit a patch if
there's interest.
Ideally, I'd love to use the same CP index in the ClassWriter that we saw in the ClassReader so as not to
drastically change the classfile for what should be a NOP, but I suspect this would involve some fairly
disruptive changes to asm.