blob: 81a077324e6c9298fd14704e0258b040353675e4 [file] [log] [blame]
/*
* 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<LockTarget> targets;
public LockFindingClassVisitor(List<LockTarget> 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<Frame> frameMap = new LinkedList<>();
frameMap.addAll(Arrays.asList(frames));
List<List<TryCatchBlockNode>> 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<Frame> frameMap,
List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
MethodInsnNode call) {
List<TryCatchBlockNode> 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<Frame> frameMap,
List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
MethodInsnNode call) {
List<TryCatchBlockNode> 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<TryCatchBlockNode> handlers,
LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) {
if (handlers == null || handlers.size() == 0) {
return;
}
InsnList instructions = mn.instructions;
List<TryCatchBlockNode> 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);
}
}