/* * Copyright (C) 2017 The Android Open Source Project * * Licensed 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 lockedregioncodeinjection; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.TryCatchBlockSorter; import org.objectweb.asm.tree.AbstractInsnNode; 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.TryCatchBlockNode; import org.objectweb.asm.tree.analysis.Analyzer; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.BasicValue; import org.objectweb.asm.tree.analysis.Frame; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; /** * This visitor does two things: * * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and insert the corresponding pre * and post methods calls should it matches one of the given target type in the Configuration. * * 2. Find all methods that are synchronized and insert pre method calls in the beginning and post * method calls just before all return instructions. */ class LockFindingClassVisitor extends ClassVisitor { private String className = null; private final List targets; public LockFindingClassVisitor(List targets, ClassVisitor chain) { super(Utils.ASM_VERSION, chain); this.targets = targets; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { assert this.className != null; MethodNode mn = new TryCatchBlockSorter(null, access, name, desc, signature, exceptions); MethodVisitor chain = super.visitMethod(access, name, desc, signature, exceptions); return new LockFindingMethodVisitor(this.className, mn, chain); } @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); } class LockFindingMethodVisitor extends MethodVisitor { private String owner; private MethodVisitor chain; public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) { super(Utils.ASM_VERSION, mn); assert owner != null; this.owner = owner; this.chain = chain; } @SuppressWarnings("unchecked") @Override public void visitEnd() { MethodNode mn = (MethodNode) mv; Analyzer a = new Analyzer(new LockTargetStateAnalysis(targets)); LockTarget ownerMonitor = null; if ((mn.access & Opcodes.ACC_SYNCHRONIZED) != 0) { for (LockTarget t : targets) { if (t.getTargetDesc().equals("L" + owner + ";")) { ownerMonitor = t; } } } try { a.analyze(owner, mn); } catch (AnalyzerException e) { throw new RuntimeException("Locked region code injection: " + e.getMessage(), e); } InsnList instructions = mn.instructions; Frame[] frames = a.getFrames(); List frameMap = new LinkedList<>(); frameMap.addAll(Arrays.asList(frames)); List> handlersMap = new LinkedList<>(); for (int i = 0; i < instructions.size(); i++) { handlersMap.add(a.getHandlers(i)); } if (ownerMonitor != null) { AbstractInsnNode s = instructions.getFirst(); MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false); insertMethodCallBefore(mn, frameMap, handlersMap, s, 0, call); } for (int i = 0; i < instructions.size(); i++) { AbstractInsnNode s = instructions.get(i); if (s.getOpcode() == Opcodes.MONITORENTER) { Frame f = frameMap.get(i); BasicValue operand = (BasicValue) f.getStack(f.getStackSize() - 1); if (operand instanceof LockTargetState) { LockTargetState state = (LockTargetState) operand; for (int j = 0; j < state.getTargets().size(); j++) { LockTarget target = state.getTargets().get(j); MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, target.getPreOwner(), target.getPreMethod(), "()V", false); insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call); } } } if (s.getOpcode() == Opcodes.MONITOREXIT) { Frame f = frameMap.get(i); BasicValue operand = (BasicValue) f.getStack(f.getStackSize() - 1); if (operand instanceof LockTargetState) { LockTargetState state = (LockTargetState) operand; for (int j = 0; j < state.getTargets().size(); j++) { // The instruction after a monitor_exit should be a label for the end of the implicit // catch block that surrounds the synchronized block to call monitor_exit when an exception // occurs. checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL, "Expected to find label after monitor exit"); int labelIndex = i + 1; checkElementIndex(labelIndex, instructions.size()); LabelNode label = (LabelNode)instructions.get(labelIndex); checkNotNull(handlersMap.get(i)); checkElementIndex(0, handlersMap.get(i).size()); checkState(handlersMap.get(i).get(0).end == label, "Expected label to be the end of monitor exit's try block"); LockTarget target = state.getTargets().get(j); MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, target.getPostOwner(), target.getPostMethod(), "()V", false); insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call); } } } if (ownerMonitor != null && (s.getOpcode() == Opcodes.RETURN || s.getOpcode() == Opcodes.ARETURN || s.getOpcode() == Opcodes.DRETURN || s.getOpcode() == Opcodes.FRETURN || s.getOpcode() == Opcodes.IRETURN)) { MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(), ownerMonitor.getPostMethod(), "()V", false); insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call); i++; // Skip ahead. Otherwise, we will revisit this instruction again. } } super.visitEnd(); mn.accept(chain); } } public static void insertMethodCallBefore(MethodNode mn, List frameMap, List> handlersMap, AbstractInsnNode node, int index, MethodInsnNode call) { List handlers = handlersMap.get(index); InsnList instructions = mn.instructions; LabelNode end = new LabelNode(); instructions.insert(node, end); frameMap.add(index, null); handlersMap.add(index, null); instructions.insertBefore(node, call); frameMap.add(index, null); handlersMap.add(index, null); LabelNode start = new LabelNode(); instructions.insert(node, start); frameMap.add(index, null); handlersMap.add(index, null); updateCatchHandler(mn, handlers, start, end, handlersMap); } public static void insertMethodCallAfter(MethodNode mn, List frameMap, List> handlersMap, AbstractInsnNode node, int index, MethodInsnNode call) { List handlers = handlersMap.get(index + 1); InsnList instructions = mn.instructions; LabelNode end = new LabelNode(); instructions.insert(node, end); frameMap.add(index + 1, null); handlersMap.add(index + 1, null); instructions.insert(node, call); frameMap.add(index + 1, null); handlersMap.add(index + 1, null); LabelNode start = new LabelNode(); instructions.insert(node, start); frameMap.add(index + 1, null); handlersMap.add(index + 1, null); updateCatchHandler(mn, handlers, start, end, handlersMap); } @SuppressWarnings("unchecked") public static void updateCatchHandler(MethodNode mn, List handlers, LabelNode start, LabelNode end, List> handlersMap) { if (handlers == null || handlers.size() == 0) { return; } InsnList instructions = mn.instructions; List newNodes = new ArrayList<>(handlers.size()); for (TryCatchBlockNode handler : handlers) { if (!(instructions.indexOf(handler.start) <= instructions.indexOf(start) && instructions.indexOf(end) <= instructions.indexOf(handler.end))) { TryCatchBlockNode newNode = new TryCatchBlockNode(start, end, handler.handler, handler.type); newNodes.add(newNode); for (int i = instructions.indexOf(start); i <= instructions.indexOf(end); i++) { if (handlersMap.get(i) == null) { handlersMap.set(i, new ArrayList<>()); } handlersMap.get(i).add(newNode); } } else { for (int i = instructions.indexOf(start); i <= instructions.indexOf(end); i++) { if (handlersMap.get(i) == null) { handlersMap.set(i, new ArrayList<>()); } handlersMap.get(i).add(handler); } } } mn.tryCatchBlocks.addAll(0, newNodes); } }