/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dragome.callbackevictor.serverside.bytecode.transformation.asm; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import org.objectweb.asm.tree.analysis.Analyzer; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.Frame; import org.objectweb.asm.tree.analysis.SimpleVerifier; import org.objectweb.asm.tree.analysis.SourceInterpreter; import org.objectweb.asm.tree.analysis.SourceValue; import com.dragome.commons.ContinueReflection; public class ContinuationMethodAnalyzer extends MethodNode implements Opcodes { public class MyVerifier extends SimpleVerifier { MyVerifier() { if (_useLoader != null) { setClassLoader(_useLoader); //Thread.currentThread().setContextClassLoader(_useLoader); } } protected Class<?> getClass(Type t) { try { if (t.getSort() == Type.ARRAY) { return Class.forName(t.getDescriptor().replace('/', '.'), true, Thread.currentThread().getContextClassLoader()); } return Class.forName(t.getClassName(), true, Thread.currentThread().getContextClassLoader()); //return Class.forName(t.getClassName()); //for now try this. It has to use the current classloader } catch (ClassNotFoundException e) { throw new RuntimeException(e.toString()); } } /* * protected Class<?> getClass(Type t) { try { System.out.println("Resolving " + t.getClassName() + " using classloader: " + Thread.currentThread().getContextClassLoader()); if (t.getSort() == Type.ARRAY) { return Class.forName(t.getDescriptor().replace('/', '.'), true, Thread.currentThread().getContextClassLoader()); } //return Class.forName(t.getClassName(), true, Thread.currentThread().getContextClassLoader()); return Class.forName(t.getClassName(), true, Thread.currentThread().getContextClassLoader()); //for now try this. It has to use the current classloader } catch (ClassNotFoundException e) { throw new RuntimeException(e.toString()); } }*/ } public class MyVariables { private int _methodObject; private int _object; private int _args; MyVariables(int m, int o, int a) { _methodObject= m; _object= o; _args= a; } public int methodVar() { return _methodObject; } public int objectVar() { return _object; } public int argsVar() { return _args; } } protected final String className; protected final ClassVisitor cv; protected final MethodVisitor mv; protected final List<Label> labels= new ArrayList<Label>(); protected final List<MethodInsnNode> nodes= new ArrayList<MethodInsnNode>(); protected final List<MethodInsnNode> methods= new ArrayList<MethodInsnNode>(); //RS: private List _variables; protected final Map<MethodInsnNode, MyVariables> _reflectMapping= new HashMap<MethodInsnNode, MyVariables>(); protected boolean _continueReflection; //RS: protected Analyzer analyzer; public int stackRecorderVar; private ClassLoader _useLoader; public ContinuationMethodAnalyzer(String className, ClassVisitor cv, MethodVisitor mv, int access, String name, String desc, String signature, String[] exceptions) { this(className, cv, mv, access, name, desc, signature, exceptions, null); } public ContinuationMethodAnalyzer(String className, ClassVisitor cv, MethodVisitor mv, int access, String name, String desc, String signature, String[] exceptions, ClassLoader loader) { super(Opcodes.ASM5, access, name, desc, signature, exceptions); this.className= className; this.cv= cv; this.mv= mv; _useLoader= loader; //RS: this._variables= new ArrayList(); _continueReflection= false; } public int getIndex(AbstractInsnNode node) { return instructions.indexOf(node); } //RS: public void visitVarInsn(int opcode, int var) { super.visitVarInsn(opcode, var); if (opcode == ALOAD) { //store it for reuse for Method.invoke _variables.add(var); } } public AnnotationVisitor visitAnnotation(String desc, boolean visible) { String chk= Type.getDescriptor(ContinueReflection.class); if (desc.startsWith(chk)) _continueReflection= true; return super.visitAnnotation(desc, visible); } public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { MethodInsnNode mnode= new MethodInsnNode(opcode, owner, name, desc, itf); if (opcode == INVOKESPECIAL || "<init>".equals(name)) { methods.add(mnode); } if (needsFrameGuard(opcode, owner, name, desc) /* && transformer.inScope(owner, name)*/) { Label label= new Label(); super.visitLabel(label); LabelNode labelNode= new LabelNode(label); instructions.add(labelNode); labels.add(label); nodes.add(mnode); //RS: if (_continueReflection && name.equals("invoke") && owner.startsWith("java/lang/reflect/Method")) { int mthd= ((Integer) _variables.get(_variables.size() - 3)).intValue(); int obj= ((Integer) _variables.get(_variables.size() - 2)).intValue(); int args= ((Integer) _variables.get(_variables.size() - 1)).intValue(); MyVariables vars= new MyVariables(mthd, obj, args); _reflectMapping.put(mnode, vars); } //RS: } instructions.add(mnode); } public void visitEnd() { if (instructions.size() == 0 || labels.size() == 0) { accept(mv); return; } /* { TraceMethodVisitor mv = new TraceMethodVisitor(); System.err.println(name + desc); for (int j = 0; j < instructions.size(); ++j) { ((AbstractInsnNode) instructions.get(j)).accept(mv); System.err.print(" " + mv.text.get(j)); // mv.text.get(j)); } System.err.println(); } */ this.stackRecorderVar= maxLocals; try { moveNew(); // analyzer = new Analyzer(new BasicVerifier()); /* * analyzer = new Analyzer(new SimpleVerifier() { protected Class<?> getClass(Type t) { try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (_useLoader != null) loader = _useLoader; if (t.getSort() == Type.ARRAY) { return Class.forName(t.getDescriptor().replace('/', '.'), true, loader); } //return Class.forName(t.getClassName(), true, Thread.currentThread().getContextClassLoader()); return Class.forName(t.getClassName(), loader); //for now try this. It has to use the current classloader } catch (ClassNotFoundException e) { throw new RuntimeException(e.toString()); } } })*/ analyzer= new Analyzer(new MyVerifier()) { protected Frame newFrame(final int nLocals, final int nStack) { return new MonitoringFrame(nLocals, nStack); } protected Frame newFrame(final Frame src) { return new MonitoringFrame(src); } public Frame[] analyze(final String owner, final MethodNode m) throws AnalyzerException { // System.out.println("Analyze: "+owner+"|"+m.name+"|"+m.signature+"|"+m.tryCatchBlocks); final Frame[] frames= super.analyze(owner, m); for (int i= 0; i < m.instructions.size(); i++) { int opcode= m.instructions.get(i).getOpcode(); if (opcode == MONITORENTER || opcode == MONITOREXIT) { // System.out.println(i); } } return frames; } }; analyzer.analyze(className, this); accept(new ContinuationMethodAdapter(this)); /* { TraceMethodVisitor mv = new TraceMethodVisitor(); System.err.println("================="); System.err.println(name + desc); for (int j = 0; j < instructions.size(); ++j) { ((AbstractInsnNode) instructions.get(j)).accept(mv); System.err.print(" " + mv.text.get(j)); // mv.text.get(j)); } System.err.println(); } */ } catch (AnalyzerException ex) { // TODO log the error or fail? ex.printStackTrace(); accept(mv); } } @SuppressWarnings("unchecked") void moveNew() throws AnalyzerException { SourceInterpreter i= new SourceInterpreter(); Analyzer a= new Analyzer(i); a.analyze(className, this); final HashMap<AbstractInsnNode, MethodInsnNode> movable= new HashMap<AbstractInsnNode, MethodInsnNode>(); Frame[] frames= a.getFrames(); for (int j= 0; j < methods.size(); j++) { MethodInsnNode mnode= methods.get(j); // require to move NEW instruction int n= instructions.indexOf(mnode); Frame f= frames[n]; Type[] args= Type.getArgumentTypes(mnode.desc); SourceValue v= (SourceValue) f.getStack(f.getStackSize() - args.length - 1); Set<AbstractInsnNode> insns= v.insns; for (final AbstractInsnNode ins : insns) { if (ins.getOpcode() == NEW) { movable.put(ins, mnode); } else { // other known patterns int n1= instructions.indexOf(ins); if (ins.getOpcode() == DUP) { // <init> with params AbstractInsnNode ins1= instructions.get(n1 - 1); if (ins1.getOpcode() == NEW) { movable.put(ins1, mnode); } } else if (ins.getOpcode() == SWAP) { // in exception handler AbstractInsnNode ins1= instructions.get(n1 - 1); AbstractInsnNode ins2= instructions.get(n1 - 2); if (ins1.getOpcode() == DUP_X1 && ins2.getOpcode() == NEW) { movable.put(ins2, mnode); } } } } } int updateMaxStack= 0; for (final Map.Entry<AbstractInsnNode, MethodInsnNode> e : movable.entrySet()) { AbstractInsnNode node1= e.getKey(); int n1= instructions.indexOf(node1); AbstractInsnNode node2= instructions.get(n1 + 1); AbstractInsnNode node3= instructions.get(n1 + 2); int producer= node2.getOpcode(); instructions.remove(node1); // NEW boolean requireDup= false; if (producer == DUP) { instructions.remove(node2); // DUP requireDup= true; } else if (producer == DUP_X1) { instructions.remove(node2); // DUP_X1 instructions.remove(node3); // SWAP requireDup= true; } MethodInsnNode mnode= (MethodInsnNode) e.getValue(); AbstractInsnNode nm= mnode; int varOffset= stackRecorderVar + 1; Type[] args= Type.getArgumentTypes(mnode.desc); // optimizations for some common cases if (args.length == 0) { final InsnList doNew= new InsnList(); doNew.add(node1); // NEW if (requireDup) doNew.add(new InsnNode(DUP)); instructions.insertBefore(nm, doNew); nm= doNew.getLast(); continue; } if (args.length == 1 && args[0].getSize() == 1) { final InsnList doNew= new InsnList(); doNew.add(node1); // NEW if (requireDup) { doNew.add(new InsnNode(DUP)); doNew.add(new InsnNode(DUP2_X1)); doNew.add(new InsnNode(POP2)); updateMaxStack= updateMaxStack < 2 ? 2 : updateMaxStack; // a two extra slots for temp values } else doNew.add(new InsnNode(SWAP)); instructions.insertBefore(nm, doNew); nm= doNew.getLast(); continue; } // TODO this one untested! if ((args.length == 1 && args[0].getSize() == 2) || (args.length == 2 && args[0].getSize() == 1 && args[1].getSize() == 1)) { final InsnList doNew= new InsnList(); doNew.add(node1); // NEW if (requireDup) { doNew.add(new InsnNode(DUP)); doNew.add(new InsnNode(DUP2_X2)); doNew.add(new InsnNode(POP2)); updateMaxStack= updateMaxStack < 2 ? 2 : updateMaxStack; // a two extra slots for temp values } else { doNew.add(new InsnNode(DUP_X2)); doNew.add(new InsnNode(POP)); updateMaxStack= updateMaxStack < 1 ? 1 : updateMaxStack; // an extra slot for temp value } instructions.insertBefore(nm, doNew); nm= doNew.getLast(); continue; } final InsnList doNew= new InsnList(); // generic code using temporary locals // save stack for (int j= args.length - 1; j >= 0; j--) { Type type= args[j]; doNew.add(new VarInsnNode(type.getOpcode(ISTORE), varOffset)); varOffset+= type.getSize(); } if (varOffset > maxLocals) { maxLocals= varOffset; } doNew.add(node1); // NEW if (requireDup) doNew.add(new InsnNode(DUP)); // restore stack for (int j= 0; j < args.length; j++) { Type type= args[j]; varOffset-= type.getSize(); doNew.add(new VarInsnNode(type.getOpcode(ILOAD), varOffset)); // clean up store to avoid memory leak? if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { updateMaxStack= updateMaxStack < 1 ? 1 : updateMaxStack; // an extra slot for ACONST_NULL doNew.add(new InsnNode(ACONST_NULL)); doNew.add(new VarInsnNode(type.getOpcode(ISTORE), varOffset)); } } instructions.insertBefore(nm, doNew); nm= doNew.getLast(); } maxStack+= updateMaxStack; } boolean needsFrameGuard(int opcode, String owner, String name, String desc) { /* TODO: need to customize a way enchancer skips classes/methods if (owner.startsWith("java/")) { System.out.println("SKIP:: " + owner + "." + name + desc); return false; } */ if (opcode == Opcodes.INVOKEINTERFACE || (opcode == Opcodes.INVOKESPECIAL && !"<init>".equals(name)) || opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKEVIRTUAL) { return true; } return false; } }