I'm currently generating some ASM code in a maven post compilation task. In Java 6 a StackMapTable was introduced representing the data types on the stack, which is mandatory in later versions. So I'm automatically determining the most specific class which can be on the stack. Now I ran into the problem that in my VM ThaiBuddhistDate and HijrahDate inherit from ChronoLocalDateImpl, so it would create this type in the StackMapTable, which would obviously crash in other VMs (maybe even versions). So I thought, maybe I should change the calculation to the minimum mandatory, which can cause (theoretically) similar problems for classes and interfaces. Now I'm trying to find a solution for my problem, so I have to figure out, which differences might occur.
Can an additional class only occur everywhere in the inheritance hierarchy? Assuming the JavaDoc has an inheritance hierarchy like:
Object - Foo - Bar - FooBar
Can I have additional classes in the inheritance structure everywhere?
Object - Baz - Foo - Bar - FooBar
Object - Foo - Baz - Bar - FooBar
Object - Foo - Bar - Baz - FooBar
Analogously for interfaces: Can interfaces also inherit from other interfaces, which are not defined in the documentation or can a class "only" have additional independent interfaces or interfaces, which are based on the defined ones or not even any?
It seems you are using the
COMPUTE_FRAMES
option which will cause the ASM library to merge the types encountered through the possible code paths viagetCommonSuperClass
similar to what the old verifier did and somewhat perverting the concept of stack map tables.As you have already noted, ASM’s implementation of
getCommonSuperClass
may return an actually inaccessible type, like a JRE internal base class, and ignores interface relationships. The bigger problem is that you can’t fix this with a different implementation of this method, as the information passed to this method is not sufficient to determine the right type.The right type is what will be subsequently needed, which, of course, should also be compatible with what is provided via all possible code paths, which is what the verifier will/ought to check. If your code generator is designed in a way that it produces valid code, specifying the subsequently required types should be sufficient to create a valid stack map table entry, but the incoming types passed to
getCommonSuperClass
are not sufficient to tell you what will be the required type.To illustrate the issue, consider the following example class
and the following code analyzing the compiled (e.g. by
javac
) class and what ASM would generate by default when being told to recalculate stack map frames from scratch:This will print
This shows the same problem you have explained in your question, ASM will generate a frame referring to an implementation specific class. The code generated by
javac
refers to the required type, which is compatible with the method’s return type. You could studyStringBuilder
andStringBuffer
ingetCommonSuperClass
and find out that both implementCharSequence
, but this is not sufficient to understand thatCharSequence
is the right type here, as we could simply change the example toand get
Since the incoming classes implement both interfaces, you can’t find out whether
CharSequence
orAppendable
is the right merge type, just by looking at the incomingStringBuilder
andStringBuffer
types.To evaluate this issue further, look at
which produces
Here, ASM’s result is a
public
type, but this common base class doesn’t implement the requiredComparable
, so this code is actually broken.It’s a great luck for all code generators using ASM’s
COMPUTE_FRAMES
option, that HotSpot’s verifier has a great tolerance towards interface types or, in other words, that it doesn’t verify the correctness of assignments at all (this includes the receivers of method invocations) when at least one of the two types is an interface.If you want to generate code that survives verifiers strictly doing their job even for interfaces, you shouldn’t use that option and start to generate stack map frames yourself, by not using the
COMPUTE_FRAMES
option and emitting the rightvisitFrame
calls (or inserting the appropriate nodes if you’re using the tree API).There seem to be a widespread fear of doing that, but it’s not that complicated. As said before, it basically implies stating what your code generator already knows. It’s actually not about trying to find a common type, it’s about specifying what you will use afterwards and if your code generator is correct, that’s already it, but if not, ASM’s calculation can’t fix the code either.
To stay at your specific example, when dealing with
ThaiBuddhistDate
andHijrahDate
you already know that you are processing them asChronoLocalDate
after the branch merge point (I suppose), whereas ASM ends up at an implementation specific non-public
type, but if that type didn’t exist, ASM just usedjava.lang.Object
as it doesn’t consider interfaces. If ASM considered interfaces, it had to decide betweenChronoLocalDate
andSerializable
, neither being more specific than the other. It’s simply not solvable with this design.To illustrate further, how different the outcome between “merge the incoming types” and “what will be used” can be, look at
Here, ASM wasted resources to find out the common base class in a deep class hierarchy tree, whereas just stating “drop the variable” would be sufficient…