bytecode java-bytecode-asm bytecode-manipulation

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 la MethodCallInliner métodos en MethodCallInliner .

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