package org.aion.avm.core.persistence; import org.aion.avm.core.ClassToolchain; import i.RuntimeAssertionError; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.analysis.Analyzer; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.Frame; /** * This visitor is responsible for reshaping the contract code such that our "automatic graph" persistence design can be applied. * Specifically, this means the following transformations: * 1) Add a special constructor, which cannot already be present, just calling its superclass counterpart. * 2) Remove "final" from all fields (at least instance fields - we may be able to treat static fields differently). * 3) Prepend all PUTFIELD/GETFIELD instructions with a call to "lazyLoad()" on the receiver object (unless "this" in a constructor). * * Note that this transformation doesn't depend on the persistence model being applied. So long as "lazyLoad()" is a safe no-op, * there is no harm in enabling this without the corresponding persistence logic. * This should probably be put late in the pipeline since these transformations are substantial, and could change energy and stack * accounting in pretty large ways for what are essentially our own implementation details. */ public class AutomaticGraphVisitor extends ClassToolchain.ToolChainClassVisitor { private static final String CLINIT_NAME = "<clinit>"; private static final String INIT_NAME = "<init>"; // The special constructor takes (Void ignore, int readIndex). private static final String SPECIAL_CONSTRUCTOR_DESCRIPTOR = "(Ljava/lang/Void;I)V"; private boolean isInterface; private String className; private String superClassName; public AutomaticGraphVisitor() { super(Opcodes.ASM6); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { // Note that we don'tw ant to change interfaces - clearly, they have no constructors. this.isInterface = (0 != (Opcodes.ACC_INTERFACE & access)); // We need the class name for the analyzer. this.className = name; // We just want to extract the superclass name. this.superClassName = superName; super.visit(version, access, name, signature, superName, interfaces); } @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { // Filter out the "final" from all fields. // (note that we may way to skip this, for statics, and exclude them from the serialization system). int newAccess = (~Opcodes.ACC_FINAL) & access; return super.visitField(newAccess, name, descriptor, signature, value); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor downstream = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor visitor = null; // There are 3 distinct special-cases we need to handle, here. if (CLINIT_NAME.equals(name)) { // 1) If this is the <clinit>, we don't want to inject the lazyLoad calls (nothing visible there could be a stub). visitor = downstream; } else if (INIT_NAME.equals(name)) { // 2) If this is an <init> we need to ensure that we don't lazyLoad() the "this" pointer due to outer class references (issue-156). visitor = new MethodNode(Opcodes.ASM6, access, name, descriptor, signature, exceptions) { @Override public void visitEnd() { super.visitEnd(); // The MethodNode is fully populated so we can now analyze it. // We use a custom interpreter which only worries about "this" or "not this". Analyzer<ConstructorThisInterpreter.ThisValue> analyzer = new Analyzer<>(new ConstructorThisInterpreter()); try { // We want to tear apart the frames and check if the top of the stack, at each bytecode, is "this" (those cases can be // safely skipped).. Frame<ConstructorThisInterpreter.ThisValue>[] frames = analyzer.analyze(AutomaticGraphVisitor.this.className, this); StackThisTracker tracker = new StackThisTracker(frames); // Tell the LazyLoadingMethodVisitor about these locations where "lazyLoad()" can be skipped and have it process the // method. this.accept(new LazyLoadingMethodVisitor(downstream, tracker)); } catch (AnalyzerException e) { // Such an error should have been handled before we got this far. throw RuntimeAssertionError.unexpected(e); } } }; } else { // 3) Otherwise, insert lazyLoad() calls before any field access. visitor = new LazyLoadingMethodVisitor(downstream, null); } return visitor; } @Override public void visitEnd() { // If this isn't an interface, define the special constructor here. if (!this.isInterface) { // This logic is similar to StubGenerator. MethodVisitor methodVisitor = super.visitMethod(Opcodes.ACC_PUBLIC, INIT_NAME, SPECIAL_CONSTRUCTOR_DESCRIPTOR, null, null); methodVisitor.visitCode(); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); methodVisitor.visitVarInsn(Opcodes.ILOAD, 2); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, this.superClassName, INIT_NAME, SPECIAL_CONSTRUCTOR_DESCRIPTOR, false); methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitMaxs(4, 4); methodVisitor.visitEnd(); } super.visitEnd(); } }