ASM – Uses the strange localVar index of newLocal from LocalVariableSorter
I’m adding a new local variable via newLocal
in LocalVariableSorter
. The method I want to add the local variable to is an instance method with long parameters. I’m going to add two natives; One long and one thing. There are no other local variables in the sample code.
So I expect the following slots/indexes:
0 - this
1 - the long param
3 - my 1st local added via `newLocal` - using two slots as it is a long
5 - my 2nd local added via `newLocal`
However, the return values I get from newLocal
are 3 and 7. Why is there such a big gap?
To make things weirder, when I add the xSTORE
directive with these indexes and check the results using javap, it shows me:
LSTORE 5
ASTORE 8
Note: Not only is the value different from the one I passed to the xSTORE directive, but the gap between them is now 3 instead of the previous 4.
Although the generated code works. I just want to understand what’s going on here, magic and why.
Thanks
Solution
The LocalVariableSorter
class has a design that is easy to misuse.
Local variables are experienced when a method defined by the MethodVisitor
API is called renumbering as mentioned in class documentation
Therefore, when used with ClassReader
, the old code that is accessed is converted. Since you don’t want the new code injected to go through this transformation, but want to use the newly defined variable, you must bypass the LocalVariableSorter
and call the method MethodVisitor
. on the underlying target
When you call visitVarInsn(LSTORE
, 3) on LocalVariableSorter
, it is processed like the old instruction referencing index 3, and because you injected a new variable that occupies indexes 3
and 4
, The “old variable” at index
3
is remapped to the next free index, which is 5
(and 6
). Then, when you define the next new variable, it gets index 7 and calls visitVarInsn(astore
, 7
) on LocalVariableSorter
like dealing with the old variable that conflicts with the new variable, so it is remapped to 8
.
This behavior exactly matches the first sentence of the class document:
LocalVariablesSorter
A MethodVisitor that renumbers local variables in their order of appearance.
Therefore, when you must call
newLocal
on LocalVariableSorter
to create a new variable that will not be remapped, you must call the visit...
original method, wrapping MethodVisitor
to use it. When you use the subclass GeneratorAdapter
, you can use its newly defined methods (those that don’t start with visit...
) to create new directives that won’t be converted, but for me this makes things worse, there are conversion directives and methods that create unconverted instructions on the same class, and you always need to remember visit....
The prefix will vary. For some methods, you still need to access the original method visitors, as described in this answer. It processes visitLocalVariable
to create debug information for the created variable.