bytecode - instrucciones y parámetros inesperados para invokevirtual en el cuerpo del método en línea
java-bytecode-asm bytecode-manipulation (1)
El problema se produce cuando la tubería (el lector de clase lee de un archivo de clase) es visitada por una tubería de
MethodVisitor
.
La etiqueta tiene un campo
int [] srcAndRefPositions
.
Dos de sus posiciones consecutivas (cfr. El final de mi publicación original) se actualizan una vez que un Visor de métodos accede a la etiqueta.
En mi caso, la etiqueta en la
ifeq label
contiene 2 MethodVisitors.
Parece que la posición incorrecta en
srcAndRefPositions
se usa al generar el archivo de clase (usando el último MethodVisitor).
No investigué la causa raíz. En cambio, mi solución fue clonar la etiqueta y luego usar la nueva etiqueta cuando es visitada por un MethodVisitor.
Seguí el código de muestra en el "Método en línea 3.2.6" en http://asm.ow2.org/current/asm-transformations.pdf , para incorporar un MethodNode a un sitio de llamada.
Mi problema es que hay algunas instrucciones inesperadas que se muestran en el bytecode generado después de la inclusión (estos bytecodes son inconsistentes con mi código), y el problema solo existe cuando un
ifeq
está después del cuerpo del método en línea y xLoad carga la variable en la pila.
Todavía no he encontrado la causa raíz del problema. Ahora estoy comenzando a eliminar todos los códigos innecesarios, con el objetivo de reproducirlo con el mínimo código. Cualquiera tiene buenas sugerencias son bienvenidas.
Aquí está uno de mis fundamentos existentes: el problema no está relacionado con el Marco, porque el problema sigue ahí cuando la Configuración para ClassRewiter es
COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS
y la Configuración para ClassReader
ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
Para simplificar el problema, el cuerpo del llamado es:
public invokeExact(Ljava/lang/String;)Z
ICONST_0
IRETURN
Y la persona que llama es:
public String invokeExact(String a, String b){
boolean flag = _guard.invokeExact(a);
if(flag)
{
return a;
}
return b;
}
. La traza de manipulación de bytecode correspondiente de la persona que llama en MethodWriter es:
public java.lang.String invokeExact(java.lang.String, java.lang.String)
....
4: aload_1
5: astore_3
6: astore 4
8: iconst_0
visitJumpInsn goto L1029004533
//visitmax() empty implementation.
//visitEnd() Empty implementation.
visitlabel L1029004533 // This label is newly created once inlining starts, but is visited until the end of inlining as the target of all xReturn instructions in the Callee''s method body.
visitVarInsn istore 5
visitVarInsn iload 5
visitJumpInsn ifeq L980604133
visitVarInsn aload 1
visitInsn areturn
visitLabel L980604133
visitVarInsn aload 2
visitInsn areturn
Finalmente, el archivo de clase generado es:
public java.lang.String invokeExact(java.lang.String, java.lang.String);
stack=2, locals=6, args_size=3
0: aload_0
1: getfield #17 // Field _guard:Ltest/code/jit/asm/simple/MHGuard;
4: aload_1
5: astore_3
6: astore 4
8: iconst_0
**9: goto 9
12: fconst_0
13: iconst_2**
14: iload 5
16: ifeq 21
19: aload_1
20: areturn
21: aload_2
22: areturn
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class test/code/jit/asm/simple/GWTSample, class java/lang/String, class java/lang/String, class java/lang/String, class test/code/jit/asm/simple/MHGuard ]
stack = [ int ]
frame_type = 252 /* append */
offset_delta = 8
locals = [ int ]
donde # 9, # 12 y # 13 están mal.
Algunas partes de mi código son (continuaré simplificando mi código el fin de semana):
public class MethodCallInliner extends LocalVariablesSorter {
protected MethodContext _context;
private IPlugin _plugin;
public MethodCallInliner(int access, String desc, MethodContext context){
// context.getRawMV() return a Class MethodWriter.
super(Opcodes.ASM5, access, desc, context.getRawMV());
_context = context;
//_fieldVisitor = new FieldManipulationVisitor(mv, context);
_plugin = NameMappingService.get().getPlugin();
//removed some unncessary codes..
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
if(opcode != Opcodes.INVOKEVIRTUAL){
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
MethodNode mn = _plugin.map(owner, name, desc, _context, this);
if(mn == null){
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
//ASMUtil.debug(mn); //to double confirm the mn content is correct.
performInline(ASMUtil.isStaticMethod(mn)?Opcodes.INVOKESTATIC:Opcodes.INVOKEVIRTUAL, owner, desc, mn);
_plugin.postProcess(mn, this, _context);
}
protected void performInline(int opcode, String owner, String desc, MethodNode mn){
Remapper remapper = Mapper.getMapper(_context, _context.getReceiverFieldName());
mn.instructions.resetLabels();
Label end = new Label();
System.out.println("++"+end.toString());
_context.beginInline();
mn.accept(new InliningAdapter(this,
opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,
remapper, end, _context));
_context.endInline();
super.visitLabel(end);
}
public void visitJumpInsn(int opcode, Label label) {
super.visitJumpInsn(opcode, label);
}
@Override
public void visitVarInsn(final int opcode, final int var){
super.visitVarInsn(opcode, var);;
}
...
}
[Nuevos hallazgos]
Creo que ahora estoy mucho más cerca del problema.
-
El visitante en línea
MethodCallInliner
debe ser correcto ya que otra prueba independiente de este visitante con las mismas clases tiene éxito. -
El problema es cómo construir la cadena MethodVisitor.
a) Quiero solo un pase visitando las instrucciones del Método.
2) El
MethodCallInliner
se organiza al final de la cadena. Antes de eso, se insertan algunos visitantes más en la información del tipo de inferencia, que posiblemente se usaría durante laMethodCallInliner
métodos enMethodCallInliner
.
Mi constructor de cadenas es:
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
.....
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
return new TransformationChain(Opcodes.ASM5, access, name, desc, signature, mv, _context);
//return new MethodCallInliner(access, desc, context); //This is OK.
}
public class TransformationChain extends BaseMethodTransform {
public TransformationChain(int api, int access, String name, String desc, String signature, MethodVisitor mv, ClassContext classContext) {
super(api, mv, classContext.getClassName(), name, desc);
....
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
_visitors.add(new AnalyzerAdapter(Opcodes.ASM5, owner, access, name,desc, cw.visitMethod(access, name, desc, owner, null)){
@Override
public void visitJumpInsn(final int opcode, final Label label){
super.visitJumpInsn(opcode, label);
}
});
MethodNode node = new MethodNode(access, name, desc, signature, null);
_visitors.add(node);
//cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
//MethodNode node = context.getClassContext().getMethodNode(name, desc);
//_visitors.add(new TypeInferencer(Opcodes.ASM5, cw.visitMethod(access, name, desc, null, null), node, context));
_visitors.add(name.equals(Constants.CONSTRUCTOR)?new ConstructorMerge(access, desc, context):
new MethodCallInliner(access, desc, context));
}
}
abstract class BaseMethodTransform extends MethodVisitor {
protected final List<MethodVisitor> _visitors = new LinkedList<MethodVisitor>();
public BaseMethodTransform(int api, MethodVisitor mv, String className, String methodName, String methodDesc) {
super(api, mv);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
for (MethodVisitor mv : _visitors) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
@Override
public void visitIntInsn(int opcode, int operand) {
for (MethodVisitor mv : _visitors) {
mv.visitIntInsn(opcode, operand);
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
for (MethodVisitor mv : _visitors) {
if (mv!= _visitors.get(_visitors.size()-1) || mv instanceof TraceMethodVisitor) {
continue;
}
mv.visitMaxs(maxStack, maxLocals);
}
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
for (MethodVisitor mv : _visitors) {
mv.visitJumpInsn(opcode, label);
}
}
......
}
Mi
hallazgo
aquí es que la clase generada es correcta si comento
_visitors.add(new AnalyzerAdapter..);
en
TransformationChain
, cuyo MethodVisitor se acaba de crear aquí.
Parece que algunos elementos de un método tienen estado, que puede ser modificado por MethodWriters (incluso todos son independientes) y la modificación anterior tiene un impacto en los visitantes posteriores
.
También noté que es la etiqueta:
/**
* Informations about forward references. Each forward reference is
* described by two consecutive integers in this array: the first one is the
* position of the first byte of the bytecode instruction that contains the
* forward reference, while the second is the position of the first byte of
* the forward reference itself. In fact the sign of the first integer
* indicates if this reference uses 2 or 4 bytes, and its absolute value
* gives the position of the bytecode instruction. This array is also used
* as a bitset to store the subroutines to which a basic block belongs. This
* information is needed in {@linked MethodWriter#visitMaxs}, after all
* forward references have been resolved. Hence the same array can be used
* for both purposes without problems.
*/
private int[] srcAndRefPositions;
Cuando lo visita AnalyzerAdapter :: visitJmpAdadpter por primera vez, se insertan dos entradas, por ejemplo, 10 y 11, al comienzo de la matriz. Luego, en la siguiente iteración `` MethodCallInliner :: visitJmpInsn`, se agregan otros dos nuevos ints en las posiciones 2 y 3. Ahora el contenido de la matriz es:
[10, 11, 16, 17, 0, 0] en el que el par (10,11) es para AnalyzerAdapter y el par (16,17) es para Method
MethodCallInliner
.
Pero lo que me desconcierta aquí es: ¿el ASM debería ser capaz de distinguir diferentes pares para el MethodVisitor correcto al generar la clase bytcode (o el bloque, el cálculo del marco de la pila, lo que sea)?
Se puede acceder al código en https://github.com/xushijie/InlineMethod/tree/typeinference