Técnicas Avanzadas de Transformación de Clases¶
Esta guía cubre técnicas avanzadas para transformar clases Java usando ASM en el contexto de plugins de Hytale. Aprenderás diferentes estrategias, patrones de diseño y casos de uso reales.
Requisitos Previos¶
Antes de continuar, asegúrate de entender:
- Creating Plugin: Conceptos básicos de plugins
- Bytecode Manipulation: Fundamentos de ASM
- Conocimientos de POO y patrones de diseño
- Experiencia con ASM ClassVisitor y MethodVisitor
Estrategias de Transformación¶
1. Visitor Pattern¶
El patrón más común en ASM. Permite visitar y modificar elementos de una clase de forma estructurada.
package com.ejemplo.transformers.patterns;
import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Transformador que usa el patrón Visitor para modificar métodos.
*/
public class VisitorPatternTransformer implements ClassTransformer {
@Override
public int priority() {
return 50;
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
// Solo transformar clases específicas
if (!className.startsWith("com.hypixel.hytale.server")) {
return null;
}
try {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(
reader,
ClassWriter.COMPUTE_FRAMES
);
// Aplicar visitor
ClassVisitor visitor = new MethodLoggingVisitor(
Opcodes.ASM9,
writer
);
reader.accept(visitor, 0);
return writer.toByteArray();
} catch (Exception e) {
System.err.println("[Transformer] Error: " + e.getMessage());
return null;
}
}
/**
* ClassVisitor que agrega logging a todos los métodos.
*/
private static class MethodLoggingVisitor extends ClassVisitor {
private String className;
public MethodLoggingVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public void visit(int version, int access, String name,
String signature, String superName,
String[] interfaces) {
this.className = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor, String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(
access, name, descriptor, signature, exceptions
);
// No modificar constructores ni métodos sintéticos
if (name.equals("<init>") || name.equals("<clinit>") ||
(access & Opcodes.ACC_SYNTHETIC) != 0) {
return mv;
}
// Agregar logging
return new LoggingMethodVisitor(
Opcodes.ASM9,
mv,
className,
name
);
}
}
/**
* MethodVisitor que inyecta código de logging.
*/
private static class LoggingMethodVisitor extends MethodVisitor {
private final String className;
private final String methodName;
public LoggingMethodVisitor(int api, MethodVisitor mv,
String className, String methodName) {
super(api, mv);
this.className = className;
this.methodName = methodName;
}
@Override
public void visitCode() {
super.visitCode();
// Inyectar al inicio: System.out.println("Entering: Class.method")
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;"
);
mv.visitLdcInsn("Entering: " + className + "." + methodName);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
}
@Override
public void visitInsn(int opcode) {
// Interceptar RETURN para logging de salida
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;"
);
mv.visitLdcInsn("Exiting: " + className + "." + methodName);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
}
super.visitInsn(opcode);
}
}
}
2. Delegation Pattern¶
Delega la ejecución original a un nuevo método, permitiendo interceptar y modificar el comportamiento.
package com.ejemplo.transformers.patterns;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Transforma métodos usando delegación.
*
* Ejemplo: method() -> method$original() + custom logic
*/
public class DelegationTransformer implements ClassTransformer {
private static final String TARGET_CLASS =
"com/hypixel/hytale/server/core/entity/entities/Player";
private static final String TARGET_METHOD = "takeDamage";
@Override
public int priority() {
return 100;
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
if (!className.equals(TARGET_CLASS.replace('/', '.'))) {
return null;
}
try {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(
reader,
ClassWriter.COMPUTE_FRAMES
);
ClassVisitor visitor = new DelegationVisitor(
Opcodes.ASM9,
writer
);
reader.accept(visitor, 0);
return writer.toByteArray();
} catch (Exception e) {
System.err.println("[DelegationTransformer] Error: " + e.getMessage());
return null;
}
}
private static class DelegationVisitor extends ClassVisitor {
private String className;
private boolean methodFound = false;
public DelegationVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public void visit(int version, int access, String name,
String signature, String superName,
String[] interfaces) {
this.className = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor, String signature,
String[] exceptions) {
if (name.equals(TARGET_METHOD)) {
methodFound = true;
// Crear método delegado (original)
String delegateName = name + "$original";
MethodVisitor delegateMV = super.visitMethod(
access | Opcodes.ACC_PRIVATE,
delegateName,
descriptor,
signature,
exceptions
);
// Crear método interceptor (nuevo)
MethodVisitor interceptorMV = super.visitMethod(
access,
name,
descriptor,
signature,
exceptions
);
return new DelegateMethodCopier(
Opcodes.ASM9,
delegateMV,
interceptorMV,
access,
name,
descriptor,
className,
delegateName
);
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public void visitEnd() {
if (methodFound) {
System.out.println("[DelegationTransformer] Successfully delegated " +
TARGET_METHOD);
}
super.visitEnd();
}
}
/**
* Copia el método original y crea un interceptor.
*/
private static class DelegateMethodCopier extends MethodVisitor {
private final MethodVisitor interceptorMV;
private final int access;
private final String methodName;
private final String descriptor;
private final String className;
private final String delegateName;
public DelegateMethodCopier(int api, MethodVisitor delegateMV,
MethodVisitor interceptorMV,
int access, String methodName,
String descriptor, String className,
String delegateName) {
super(api, delegateMV);
this.interceptorMV = interceptorMV;
this.access = access;
this.methodName = methodName;
this.descriptor = descriptor;
this.className = className;
this.delegateName = delegateName;
}
@Override
public void visitEnd() {
super.visitEnd();
// Generar método interceptor
generateInterceptor();
}
private void generateInterceptor() {
interceptorMV.visitCode();
// Custom logic antes de llamar al original
interceptorMV.visitFieldInsn(
Opcodes.GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;"
);
interceptorMV.visitLdcInsn(
"[Custom] Player taking damage - intercepted!"
);
interceptorMV.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
// Cargar 'this' y parámetros
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
interceptorMV.visitVarInsn(Opcodes.ALOAD, 0); // this
int localIndex = 1;
for (Type argType : argumentTypes) {
interceptorMV.visitVarInsn(
argType.getOpcode(Opcodes.ILOAD),
localIndex
);
localIndex += argType.getSize();
}
// Llamar al método original
interceptorMV.visitMethodInsn(
Opcodes.INVOKESPECIAL,
className,
delegateName,
descriptor,
false
);
// Custom logic después de llamar al original
interceptorMV.visitFieldInsn(
Opcodes.GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;"
);
interceptorMV.visitLdcInsn(
"[Custom] Damage processed!"
);
interceptorMV.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
// Retornar
Type returnType = Type.getReturnType(descriptor);
interceptorMV.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
interceptorMV.visitMaxs(0, 0);
interceptorMV.visitEnd();
}
}
}
3. Wrapping Pattern¶
Envuelve llamadas a métodos con lógica adicional sin modificar el método original.
package com.ejemplo.transformers.patterns;
import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Envuelve llamadas a métodos específicos con lógica custom.
*/
public class WrappingTransformer implements ClassTransformer {
private static final String TARGET_METHOD_OWNER =
"com/hypixel/hytale/server/core/modules/interaction/interaction/config/Interaction";
private static final String TARGET_METHOD = "getAssetStore";
@Override
public int priority() {
return 75;
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
try {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(
reader,
ClassWriter.COMPUTE_FRAMES
);
ClassVisitor visitor = new MethodCallWrapperVisitor(
Opcodes.ASM9,
writer
);
reader.accept(visitor, 0);
return writer.toByteArray();
} catch (Exception e) {
return null;
}
}
/**
* Envuelve llamadas a métodos específicos.
*/
private static class MethodCallWrapperVisitor extends ClassVisitor {
public MethodCallWrapperVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor, String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(
access, name, descriptor, signature, exceptions
);
return new MethodCallInterceptor(Opcodes.ASM9, mv);
}
}
/**
* Intercepta llamadas a métodos y agrega lógica.
*/
private static class MethodCallInterceptor extends MethodVisitor {
public MethodCallInterceptor(int api, MethodVisitor mv) {
super(api, mv);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String descriptor, boolean isInterface) {
// Detectar llamada al método objetivo
if (owner.equals(TARGET_METHOD_OWNER) &&
name.equals(TARGET_METHOD)) {
// Lógica ANTES de la llamada
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;"
);
mv.visitLdcInsn("[Wrapper] Before getAssetStore()");
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
// Llamada original
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
// Lógica DESPUÉS de la llamada
// Nota: el resultado está en el stack
mv.visitInsn(Opcodes.DUP); // Duplicar resultado
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;"
);
mv.visitInsn(Opcodes.SWAP); // Intercambiar PrintStream y resultado
mv.visitLdcInsn("[Wrapper] After getAssetStore(): ");
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"print",
"(Ljava/lang/String;)V",
false
);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/Object;)V",
false
);
} else {
// No modificar otras llamadas
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
}
}
Ejemplos Avanzados con ASM¶
Agregar Nuevos Métodos¶
package com.ejemplo.transformers.advanced;
import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Agrega métodos nuevos a clases existentes.
*/
public class AddMethodTransformer implements ClassTransformer {
@Override
public int priority() {
return 50;
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
if (!className.equals("com.hypixel.hytale.server.core.entity.entities.Player")) {
return null;
}
try {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(
reader,
ClassWriter.COMPUTE_FRAMES
);
ClassVisitor visitor = new AddMethodVisitor(Opcodes.ASM9, writer);
reader.accept(visitor, 0);
return writer.toByteArray();
} catch (Exception e) {
System.err.println("[AddMethod] Error: " + e.getMessage());
return null;
}
}
private static class AddMethodVisitor extends ClassVisitor {
private boolean methodExists = false;
public AddMethodVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor, String signature,
String[] exceptions) {
if (name.equals("getCustomData")) {
methodExists = true;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public void visitEnd() {
if (!methodExists) {
// Agregar método público: String getCustomData()
MethodVisitor mv = cv.visitMethod(
Opcodes.ACC_PUBLIC,
"getCustomData",
"()Ljava/lang/String;",
null,
null
);
mv.visitCode();
// return "Custom data from plugin!";
mv.visitLdcInsn("Custom data from plugin!");
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
System.out.println("[AddMethod] Added getCustomData() to Player");
}
super.visitEnd();
}
}
}
Modificar Campos Existentes¶
package com.ejemplo.transformers.advanced;
import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Modifica campos de una clase (cambiar tipo, access, etc.)
*/
public class ModifyFieldTransformer implements ClassTransformer {
@Override
public int priority() {
return 60;
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
if (!className.equals("com.hypixel.hytale.server.core.HytaleServer")) {
return null;
}
try {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(reader, 0);
ClassVisitor visitor = new FieldModifierVisitor(Opcodes.ASM9, writer);
reader.accept(visitor, 0);
return writer.toByteArray();
} catch (Exception e) {
return null;
}
}
private static class FieldModifierVisitor extends ClassVisitor {
public FieldModifierVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor,
String signature, Object value) {
// Cambiar campos privados a públicos (ejemplo didáctico)
if ((access & Opcodes.ACC_PRIVATE) != 0) {
System.out.println("[ModifyField] Changed field " + name +
" from private to public");
access = (access & ~Opcodes.ACC_PRIVATE) | Opcodes.ACC_PUBLIC;
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public void visitEnd() {
// Agregar nuevo campo
FieldVisitor fv = cv.visitField(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
"PLUGIN_VERSION",
"Ljava/lang/String;",
null,
"1.0.0"
);
fv.visitEnd();
System.out.println("[ModifyField] Added PLUGIN_VERSION field");
super.visitEnd();
}
}
}
Cambiar Comportamiento Completo de Métodos¶
package com.ejemplo.transformers.advanced;
import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Reemplaza completamente la implementación de un método.
*/
public class ReplaceMethodTransformer implements ClassTransformer {
private static final String TARGET_CLASS =
"com.hypixel.hytale.server.core.modules.interaction.interaction.UnarmedInteractions";
private static final String TARGET_METHOD = "canUseItem";
@Override
public int priority() {
return 90;
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
if (!className.equals(TARGET_CLASS)) {
return null;
}
try {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(
reader,
ClassWriter.COMPUTE_FRAMES
);
ClassVisitor visitor = new MethodReplacerVisitor(
Opcodes.ASM9,
writer
);
reader.accept(visitor, 0);
return writer.toByteArray();
} catch (Exception e) {
System.err.println("[ReplaceMethod] Error: " + e.getMessage());
return null;
}
}
private static class MethodReplacerVisitor extends ClassVisitor {
public MethodReplacerVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor, String signature,
String[] exceptions) {
if (name.equals(TARGET_METHOD)) {
// Crear nuevo método visitor
MethodVisitor mv = super.visitMethod(
access, name, descriptor, signature, exceptions
);
return new MethodReplacer(Opcodes.ASM9, mv, descriptor);
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
/**
* Reemplaza el código del método completamente.
*/
private static class MethodReplacer extends MethodVisitor {
private final String descriptor;
private boolean firstVisit = true;
public MethodReplacer(int api, MethodVisitor mv, String descriptor) {
super(api, mv);
this.descriptor = descriptor;
}
@Override
public void visitCode() {
if (firstVisit) {
super.visitCode();
// Nueva implementación: siempre retornar true
System.out.println("[ReplaceMethod] Replacing " +
TARGET_METHOD + " implementation");
// return true;
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IRETURN);
mv.visitMaxs(1, 1);
firstVisit = false;
}
}
@Override
public void visitInsn(int opcode) {
// Ignorar instrucciones del método original
}
@Override
public void visitIntInsn(int opcode, int operand) {
// Ignorar
}
@Override
public void visitVarInsn(int opcode, int var) {
// Ignorar
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String descriptor, boolean isInterface) {
// Ignorar
}
@Override
public void visitFieldInsn(int opcode, String owner, String name,
String descriptor) {
// Ignorar
}
// Ignorar resto de instrucciones...
}
}
Casos de Uso Reales¶
1. Sistema de Hooks¶
Implementar un sistema de hooks para permitir que otros plugins intercepten eventos.
package com.ejemplo.transformers.hooks;
import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Sistema de hooks que permite a otros plugins ejecutar código
* en puntos específicos del juego.
*/
public class HookSystemTransformer implements ClassTransformer {
// Registro de hooks (compartido entre plugins)
public static final List<Consumer<Object>> DAMAGE_HOOKS = new ArrayList<>();
public static final List<Consumer<Object>> SPAWN_HOOKS = new ArrayList<>();
@Override
public int priority() {
return 100;
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
if (!className.equals("com.hypixel.hytale.server.core.entity.entities.Player")) {
return null;
}
try {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(
reader,
ClassWriter.COMPUTE_FRAMES
);
ClassVisitor visitor = new HookInjectorVisitor(
Opcodes.ASM9,
writer
);
reader.accept(visitor, 0);
return writer.toByteArray();
} catch (Exception e) {
System.err.println("[HookSystem] Error: " + e.getMessage());
return null;
}
}
private static class HookInjectorVisitor extends ClassVisitor {
public HookInjectorVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor, String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(
access, name, descriptor, signature, exceptions
);
// Inyectar hook en takeDamage
if (name.equals("takeDamage")) {
return new DamageHookInjector(Opcodes.ASM9, mv);
}
return mv;
}
}
/**
* Inyecta llamadas al sistema de hooks al inicio del método.
*/
private static class DamageHookInjector extends MethodVisitor {
public DamageHookInjector(int api, MethodVisitor mv) {
super(api, mv);
}
@Override
public void visitCode() {
super.visitCode();
// Inyectar: HookSystemTransformer.callDamageHooks(this);
mv.visitVarInsn(Opcodes.ALOAD, 0); // this
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"com/ejemplo/transformers/hooks/HookSystemTransformer",
"callDamageHooks",
"(Ljava/lang/Object;)V",
false
);
}
}
/**
* Método estático llamado desde bytecode inyectado.
* Ejecuta todos los hooks registrados.
*/
public static void callDamageHooks(Object player) {
for (Consumer<Object> hook : DAMAGE_HOOKS) {
try {
hook.accept(player);
} catch (Exception e) {
System.err.println("[HookSystem] Error in hook: " + e.getMessage());
}
}
}
/**
* API pública para registrar hooks desde otros plugins.
*/
public static void registerDamageHook(Consumer<Object> hook) {
DAMAGE_HOOKS.add(hook);
System.out.println("[HookSystem] Registered damage hook");
}
}
Uso desde otro plugin:
// Otro plugin puede registrar un hook
HookSystemTransformer.registerDamageHook(player -> {
System.out.println("Player took damage: " + player);
// Lógica custom
});
2. Profiler de Rendimiento¶
package com.ejemplo.transformers.profiling;
import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
/**
* Profiler automático que mide el tiempo de ejecución de métodos.
*/
public class PerformanceProfiler implements ClassTransformer {
private static final Map<String, MethodStats> STATS = new ConcurrentHashMap<>();
@Override
public int priority() {
return 25;
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
// Solo perfilar clases de Hytale
if (!className.startsWith("com.hypixel.hytale")) {
return null;
}
try {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(reader, 0);
ClassVisitor visitor = new ProfilerVisitor(
Opcodes.ASM9,
writer,
className
);
reader.accept(visitor, 0);
return writer.toByteArray();
} catch (Exception e) {
return null;
}
}
private static class ProfilerVisitor extends ClassVisitor {
private final String className;
public ProfilerVisitor(int api, ClassVisitor cv, String className) {
super(api, cv);
this.className = className;
}
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor, String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(
access, name, descriptor, signature, exceptions
);
// No perfilar constructores ni sintéticos
if (name.equals("<init>") || name.equals("<clinit>") ||
(access & Opcodes.ACC_SYNTHETIC) != 0) {
return mv;
}
return new TimingMethodVisitor(
Opcodes.ASM9,
mv,
className,
name
);
}
}
private static class TimingMethodVisitor extends MethodVisitor {
private final String className;
private final String methodName;
private final String fullName;
public TimingMethodVisitor(int api, MethodVisitor mv,
String className, String methodName) {
super(api, mv);
this.className = className;
this.methodName = methodName;
this.fullName = className + "." + methodName;
}
@Override
public void visitCode() {
super.visitCode();
// long startTime = System.nanoTime();
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"java/lang/System",
"nanoTime",
"()J",
false
);
mv.visitVarInsn(Opcodes.LSTORE, getStartTimeVar());
}
@Override
public void visitInsn(int opcode) {
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
// Antes de retornar, registrar tiempo
recordTime();
}
super.visitInsn(opcode);
}
private void recordTime() {
// PerformanceProfiler.recordExecution(fullName, startTime);
mv.visitLdcInsn(fullName);
mv.visitVarInsn(Opcodes.LLOAD, getStartTimeVar());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"com/ejemplo/transformers/profiling/PerformanceProfiler",
"recordExecution",
"(Ljava/lang/String;J)V",
false
);
}
private int getStartTimeVar() {
// Variable local para startTime (aproximación)
return 100;
}
}
/**
* Registra la ejecución de un método.
*/
public static void recordExecution(String methodName, long startTime) {
long duration = System.nanoTime() - startTime;
STATS.computeIfAbsent(methodName, k -> new MethodStats())
.record(duration);
}
/**
* Imprime estadísticas de rendimiento.
*/
public static void printStats() {
System.out.println("\n=== Performance Statistics ===");
STATS.entrySet().stream()
.sorted((a, b) -> Long.compare(b.getValue().totalTime, a.getValue().totalTime))
.limit(20)
.forEach(entry -> {
MethodStats stats = entry.getValue();
System.out.printf("%s: %d calls, %.2fms total, %.4fms avg%n",
entry.getKey(),
stats.callCount,
stats.totalTime / 1_000_000.0,
stats.getAverageMs()
);
});
}
private static class MethodStats {
long callCount = 0;
long totalTime = 0;
synchronized void record(long duration) {
callCount++;
totalTime += duration;
}
double getAverageMs() {
return callCount == 0 ? 0 : (totalTime / callCount) / 1_000_000.0;
}
}
}
Mejores Prácticas
- Usa
ClassWriter.COMPUTE_FRAMEScuando modifiques el flujo de control - Verifica siempre que los tipos en el stack sean correctos
- Usa
CheckClassAdapterdurante desarrollo para detectar errores - Mantén estadísticas y logs para debugging
- Considera el impacto en rendimiento de tus transformaciones
Advertencias
- Las transformaciones incorrectas pueden corromper el bytecode
- No modifiques clases de Java core (java., javax.)
- Ten cuidado con el orden de prioridad de transformadores
- Prueba exhaustivamente en servidor de desarrollo
Debugging Avanzado¶
Guardar Bytecode Transformado¶
private void saveTransformedClass(String className, byte[] bytecode) {
try {
Path output = Paths.get("debug", "transformed",
className.replace('.', '/') + ".class");
Files.createDirectories(output.getParent());
Files.write(output, bytecode);
System.out.println("[Debug] Saved: " + output);
} catch (IOException e) {
e.printStackTrace();
}
}
Comparar Bytecode Original vs Transformado¶
# Descompilar ambos archivos
javap -c -p original.class > original.txt
javap -c -p transformed.class > transformed.txt
# Comparar
diff -u original.txt transformed.txt
Recursos Adicionales¶
- Bytecode Manipulation: Guía completa de ASM
- Best Practices: Mejores prácticas para plugins
- ASM Guide: Guía oficial de ASM
- JVM Specification: Especificación de bytecode
¿Necesitas ayuda? Consulta Troubleshooting