/******************************************************************************* * Copyright 2014, * Luis Pina <luis@luispina.me>, * Michael Hicks <mwh@cs.umd.edu> * * This file is part of Rubah. * * Rubah is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Rubah is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Rubah. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package rubah.bytecode.transformers; import java.io.PrintWriter; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.ListIterator; import java.util.Map.Entry; import java.util.Set; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; 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.SourceInterpreter; import org.objectweb.asm.tree.analysis.SourceValue; import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceMethodVisitor; import rubah.Rubah; import rubah.bytecode.RubahProxy; import rubah.framework.Clazz; import rubah.framework.Method; import rubah.framework.Namespace; import rubah.framework.Type; import rubah.runtime.Version; public class RedirectFieldManipulation extends RubahTransformer { private static final HashSet<MethodInvocationInfo> ensureNotProxyMethods = new HashSet<MethodInvocationInfo>(Arrays.asList(new MethodInvocationInfo[]{ new MethodInvocationInfo("getClass", Object.class, Class.class), })); private Version version; public RedirectFieldManipulation(HashMap<String, Object> objectsMap, Version version, ClassVisitor visitor) { super(objectsMap, version.getNamespace(), visitor); this.version = version; } public RedirectFieldManipulation(HashMap<String, Object> objectsMap, Namespace namespace, ClassVisitor visitor) { super(objectsMap, namespace, visitor); } public RedirectFieldManipulation(Namespace namespace, ClassVisitor visitor) { super(null, namespace, visitor); } @Override public MethodVisitor visitMethod(int access, String name, final String desc, String signature, String[] exceptions) { final MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions); if (this.namespace.isBootstrap(this.thisClass)) return methodVisitor; if (this.thisClass.getFqn().startsWith("java.io") || this.thisClass.getFqn().startsWith("sun.reflect") || this.thisClass.getFqn().startsWith("sun.misc") || this.thisClass.getFqn().startsWith("java.security") || this.thisClass.getFqn().startsWith("java.util.concurrent.locks") || this.thisClass.getFqn().startsWith("java.util.concurrent.atomic") || (this.thisClass.getFqn().startsWith("java.lang") && !this.thisClass.getFqn().equals(Class.class.getName()))) return methodVisitor; if (this.objectsMap != null) { Method m = (Method) objectsMap.get(name); if (m == null) return methodVisitor; if (m.getName().startsWith(AddGettersAndSetters.GETTER_PREFFIX) || m.getName().startsWith(AddGettersAndSetters.SETTER_PREFFIX)) return methodVisitor; } MethodVisitor ret = new MethodNode(ASM5, access, name, desc, signature, exceptions) { private Frame<SourceValue>[] sourcesFrames; private boolean isStatic = Modifier.isStatic(access); @Override public void visitEnd() { Analyzer<SourceValue> sourceAnalyzer = new Analyzer<SourceValue>( new SourceInterpreter()); try { sourceAnalyzer.analyze(thisClass.getASMType().getInternalName(), this); } catch (AnalyzerException e) { System.out.println(namespace.isBootstrap(thisClass)); System.out.println(e.getMessage()); this.sourcesFrames = sourceAnalyzer.getFrames(); this.printAnalyzerResult(); throw new Error(e); } this.sourcesFrames = sourceAnalyzer.getFrames(); ListIterator<AbstractInsnNode> iter = this.instructions.iterator(); HashMap<AbstractInsnNode, InsnList> instructionsToAddBefore = new HashMap<AbstractInsnNode, InsnList>(); HashMap<AbstractInsnNode, InsnList> instructionsToAddAfter = new HashMap<AbstractInsnNode, InsnList>(); HashMap<AbstractInsnNode, AbstractInsnNode> instructionsToReplace = new HashMap<AbstractInsnNode, AbstractInsnNode>(); while (iter.hasNext()) { AbstractInsnNode insnNode = iter.next(); int opcode; switch ((opcode = insnNode.getOpcode())) { case INVOKESPECIAL: { MethodInsnNode methodNode = (MethodInsnNode) insnNode; int receiverDepth = Type.getArgumentTypes(methodNode.desc).length; if (!this.needsRedirect(insnNode, receiverDepth)) continue; for (AbstractInsnNode source : this.getSources(insnNode, receiverDepth)) { if (source.getOpcode() == AALOAD) // Already instrumented, skip it continue; instructionsToAddAfter.put(source, this.ensureNotProxy(methodNode.owner)); } break; } case INVOKEVIRTUAL: { MethodInsnNode methodNode = (MethodInsnNode) insnNode; MethodInvocationInfo m = new MethodInvocationInfo(methodNode.name, methodNode.owner, methodNode.desc); if (ensureNotProxyMethods.contains(m)) { int receiverDepth = 0; for (Type arg : Type.getArgumentTypes(methodNode.desc)) receiverDepth += arg.getSize(); if (!this.needsRedirect(insnNode, receiverDepth)) continue; for (AbstractInsnNode source : this.getSources(insnNode, receiverDepth)) instructionsToAddAfter.put(source, this.ensureNotProxy()); } break; } case GETFIELD: { if (!this.needsRedirect(insnNode, 0)) continue; FieldInsnNode fieldNode = (FieldInsnNode) insnNode; Type fieldOwner = findActualFieldOwner(Type.getObjectType(fieldNode.owner), fieldNode.name); opcode = (opcode == GETFIELD ? INVOKEVIRTUAL : INVOKESTATIC); String methodName = AddGettersAndSetters.generateGetterName(version, fieldOwner, fieldNode.name); String methodDesc = Type.getMethodDescriptor(Type.getType(fieldNode.desc)); instructionsToReplace.put(insnNode, new MethodInsnNode(opcode, fieldNode.owner, methodName, methodDesc, false)); break; } case PUTFIELD: { if (!this.needsRedirect(insnNode, 1)) continue; FieldInsnNode fieldNode = (FieldInsnNode) insnNode; Type fieldOwner = findActualFieldOwner(Type.getObjectType(fieldNode.owner), fieldNode.name); opcode = (opcode == PUTFIELD ? INVOKEVIRTUAL : INVOKESTATIC); String methodName = AddGettersAndSetters.generateSetterName(version, fieldOwner, fieldNode.name); String methodDesc = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(fieldNode.desc)); instructionsToReplace.put(insnNode, new MethodInsnNode(opcode, fieldNode.owner, methodName, methodDesc, false)); break; } } } for (Entry<AbstractInsnNode, InsnList> entry : instructionsToAddBefore.entrySet()) this.instructions.insertBefore(entry.getKey(), entry.getValue()); for (Entry<AbstractInsnNode, InsnList> entry : instructionsToAddAfter.entrySet()) this.instructions.insert(entry.getKey(), entry.getValue()); // Destructive changes take place after constructive changes // so that the location nodes do not get destroyed too soon for (Entry<AbstractInsnNode, AbstractInsnNode> entry : instructionsToReplace.entrySet()) this.instructions.set(entry.getKey(), entry.getValue()); accept(methodVisitor); } private InsnList ensureNotProxy() { return this.ensureNotProxy(null); } private InsnList ensureNotProxy(String owner) { InsnList list = new InsnList(); LabelNode label = new LabelNode(); list.add(new InsnNode(DUP)); list.add(new TypeInsnNode(INSTANCEOF, Type.getType(RubahProxy.class).getInternalName())); list.add(new JumpInsnNode(IFEQ, label)); list.add(new TypeInsnNode(CHECKCAST, Type.getType(RubahProxy.class).getInternalName())); list.add(new MethodInsnNode( INVOKESTATIC, Type.getType(Rubah.class).getInternalName(), "getConverted", Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class)), false)); if (owner != null) list.add(new TypeInsnNode(CHECKCAST, owner)); list.add(label); return list; } /** * * @param idx * @return True if local var, false if argument */ private boolean isLocalVar(int idx) { int lastVar = (this.isStatic ? 0 : 1); for (Type arg : Type.getArgumentTypes(desc)) lastVar += arg.getSize(); return idx >= lastVar; } private Set<AbstractInsnNode> getSources(AbstractInsnNode insnNode, int depth) { return this.getSources(insnNode, depth, new HashSet<AbstractInsnNode>(), new HashSet<AbstractInsnNode>()); } private Set<AbstractInsnNode> getSources(AbstractInsnNode insnNode, int depth, HashSet<AbstractInsnNode> allSources, HashSet<AbstractInsnNode> alreadySeen) { int idx = this.instructions.indexOf(insnNode); Frame<SourceValue> sourcesFrame = this.sourcesFrames[idx]; if (sourcesFrame == null) { // Bug in the analyzer or unreachable code return new HashSet<AbstractInsnNode>(); } Set<AbstractInsnNode> sources = sourcesFrame.getStack(sourcesFrame.getStackSize() - 1 - depth).insns; for (AbstractInsnNode source : sources) { if (alreadySeen.contains(source)) continue; alreadySeen.add(source); switch (source.getOpcode()) { case CHECKCAST: case DUP: allSources.addAll(this.getSources(source, 0, allSources, alreadySeen)); break; case ALOAD: // Is this an argument? VarInsnNode n = (VarInsnNode) source; if (isLocalVar(n.var)) { // Only ASTORE can save to local variables for (AbstractInsnNode astore : sourcesFrame.getLocal(n.var).insns) { allSources.addAll(this.getSources(astore, 0, allSources, alreadySeen)); } continue; } // Explicit fall-through default: allSources.add(source); break; } } return allSources; } private void printAnalyzerResult() { Textifier t = new Textifier(); TraceMethodVisitor mv = new TraceMethodVisitor(t); PrintWriter pw = new PrintWriter(System.out); pw.println(this.name + this.desc); for (int j = 0; j < this.instructions.size(); ++j) { this.instructions.get(j).accept(mv); StringBuffer s = new StringBuffer(); Frame<SourceValue> f = this.sourcesFrames[j]; if (f == null) { s.append('?'); } else { for (int k = 0; k < f.getLocals(); ++k) { for (AbstractInsnNode insn : f.getLocal(k).insns) { s.append(this.instructions.indexOf(insn)) .append(' '); } } s.append(" : "); for (int k = 0; k < f.getStackSize(); ++k) { for (AbstractInsnNode insn : f.getStack(k).insns) { s.append(this.instructions.indexOf(insn)) .append(' '); } } } while (s.length() < this.maxStack + this.maxLocals + 1) { s.append(' '); } pw.print(Integer.toString(j + 100000).substring(1)); pw.print(" " + s + " : " + t.text.get(t.text.size() - 1)); } for (int j = 0; j < this.tryCatchBlocks.size(); ++j) { this.tryCatchBlocks.get(j).accept(mv); pw.print(" " + t.text.get(t.text.size() - 1)); } pw.println(); pw.flush(); } private boolean needsRedirect(AbstractInsnNode insnNode, int stackDepth) { boolean ret = false; for (AbstractInsnNode insn : this.getSources(insnNode, stackDepth)) { switch (insn.getOpcode()) { case NEW: continue; case ALOAD: if (((VarInsnNode)insn).var != 0 || this.isStatic) ret = true; break; default: ret = true; break; } } return ret; } }; return ret; } private Type findActualFieldOwner(Type start, String fieldName) { Clazz c = this.namespace.getClass(start); while (c != null) { for (rubah.framework.Field f : c.getFields()) { if (f.getName().equals(fieldName)) return c.getASMType(); } c = c.getParent(); } return start; } private static final class MethodInvocationInfo { private final String name; private final String owner; private final String desc; public MethodInvocationInfo(String name, Class<?> owner, Class<?> ret, Class<?> ... args) { this.name = name; this.owner = Type.getType(owner).getInternalName(); Type[] argsType = new Type[args.length]; for (int i = 0; i < args.length; i++) argsType[i] = Type.getType(args[i]); this.desc = Type.getMethodDescriptor(Type.getType(ret), argsType); } public MethodInvocationInfo(String name, String owner, String desc) { this.name = name; this.owner = owner; this.desc = desc; } @Override public int hashCode() { return this.name.hashCode() ^ this.owner.hashCode() ^ this.desc.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof MethodInvocationInfo) { MethodInvocationInfo other = (MethodInvocationInfo) obj; return other.name.equals(this.name) && other.owner.equals(this.owner) && other.desc.equals(this.desc); } return false; } } }