Merge "ASM Priority Boost Tool"
am: 8ff8dd16cc
Change-Id: Ifb62ee3132e984340e4457b543c4f84f7c45995a
This commit is contained in:
15
tools/locked_region_code_injection/Android.mk
Normal file
15
tools/locked_region_code_injection/Android.mk
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_JAR_MANIFEST := manifest.txt
|
||||||
|
LOCAL_MODULE := lockedregioncodeinjection
|
||||||
|
LOCAL_SRC_FILES := $(call all-java-files-under,src)
|
||||||
|
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||||
|
asm-5.2 \
|
||||||
|
asm-commons-5.2 \
|
||||||
|
asm-tree-5.2 \
|
||||||
|
asm-analysis-5.2
|
||||||
|
|
||||||
|
|
||||||
|
include $(BUILD_HOST_JAVA_LIBRARY)
|
||||||
1
tools/locked_region_code_injection/manifest.txt
Normal file
1
tools/locked_region_code_injection/manifest.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Main-Class: lockedregioncodeinjection.Main
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(Opcodes.ASM5, 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) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
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++) {
|
||||||
|
LockTarget target = state.getTargets().get(j);
|
||||||
|
MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
|
||||||
|
target.getPostOwner(), target.getPostMethod(), "()V", false);
|
||||||
|
insertMethodCallAfter(mn, frameMap, handlersMap, s, i, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent a specific class that is used for synchronization. A pre and post method can be
|
||||||
|
* specified to by the user to be called right after monitor_enter and after monitor_exit
|
||||||
|
* respectively.
|
||||||
|
*/
|
||||||
|
public class LockTarget {
|
||||||
|
public static final LockTarget NO_TARGET = new LockTarget("", null, null);
|
||||||
|
|
||||||
|
private final String targetDesc;
|
||||||
|
private final String pre;
|
||||||
|
private final String post;
|
||||||
|
|
||||||
|
public LockTarget(String targetDesc, String pre, String post) {
|
||||||
|
this.targetDesc = targetDesc;
|
||||||
|
this.pre = pre;
|
||||||
|
this.post = post;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetDesc() {
|
||||||
|
return targetDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPre() {
|
||||||
|
return pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPreOwner() {
|
||||||
|
return pre.substring(0, pre.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPreMethod() {
|
||||||
|
return pre.substring(pre.lastIndexOf('.') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPost() {
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPostOwner() {
|
||||||
|
return post.substring(0, post.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPostMethod() {
|
||||||
|
return post.substring(post.lastIndexOf('.') + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.List;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.analysis.BasicValue;
|
||||||
|
|
||||||
|
public class LockTargetState extends BasicValue {
|
||||||
|
private final List<LockTarget> lockTargets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
public LockTargetState(Type type, List<LockTarget> lockTargets) {
|
||||||
|
super(type);
|
||||||
|
this.lockTargets = lockTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LockTarget> getTargets() {
|
||||||
|
return lockTargets;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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.List;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
|
import org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||||
|
import org.objectweb.asm.tree.analysis.BasicInterpreter;
|
||||||
|
import org.objectweb.asm.tree.analysis.BasicValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple dataflow analysis to determine if the operands on the stack must be one of target lock
|
||||||
|
* class type.
|
||||||
|
*/
|
||||||
|
public class LockTargetStateAnalysis extends BasicInterpreter {
|
||||||
|
|
||||||
|
private final List<LockTarget> targetLocks;
|
||||||
|
|
||||||
|
public LockTargetStateAnalysis(List<LockTarget> targetLocks) {
|
||||||
|
this.targetLocks = targetLocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BasicValue naryOperation(AbstractInsnNode inst, @SuppressWarnings("rawtypes") List args)
|
||||||
|
throws AnalyzerException {
|
||||||
|
// We target the return type of any invocation.
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
BasicValue base = super.naryOperation(inst, args);
|
||||||
|
if (!(inst instanceof MethodInsnNode)) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodInsnNode invoke = (MethodInsnNode) inst;
|
||||||
|
Type returnType = Type.getReturnType(invoke.desc);
|
||||||
|
if (returnType.equals(Type.VOID_TYPE)) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LockTarget> types = new ArrayList<>();
|
||||||
|
|
||||||
|
for (LockTarget target : targetLocks) {
|
||||||
|
if (returnType.getDescriptor().equals(target.getTargetDesc())) {
|
||||||
|
types.add(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LockTargetState(base.getType(), types);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BasicValue newValue(Type type) {
|
||||||
|
BasicValue base = super.newValue(type);
|
||||||
|
List<LockTarget> types = new ArrayList<>();
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
for (LockTarget target : targetLocks) {
|
||||||
|
if (type.getDescriptor().equals(target.getTargetDesc())) {
|
||||||
|
types.add(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.isEmpty()) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LockTargetState(base.getType(), types);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BasicValue merge(BasicValue v1, BasicValue v2) {
|
||||||
|
BasicValue base = super.merge(v1, v2);
|
||||||
|
|
||||||
|
if (!(v1 instanceof LockTargetState)) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
if (!(v2 instanceof LockTargetState)) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
LockTargetState state1 = (LockTargetState) v1;
|
||||||
|
LockTargetState state2 = (LockTargetState) v2;
|
||||||
|
|
||||||
|
List<LockTarget> newList = new ArrayList<>(state1.getTargets());
|
||||||
|
for (LockTarget otherTarget : state2.getTargets()) {
|
||||||
|
if (!newList.contains(otherTarget)) {
|
||||||
|
newList.add(otherTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LockTargetState(base.getType(), newList);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.io.BufferedInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
String inJar = null;
|
||||||
|
String outJar = null;
|
||||||
|
|
||||||
|
String legacyTargets = null;
|
||||||
|
String legacyPreMethods = null;
|
||||||
|
String legacyPostMethods = null;
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
if ("-i".equals(args[i].trim())) {
|
||||||
|
i++;
|
||||||
|
inJar = args[i].trim();
|
||||||
|
} else if ("-o".equals(args[i].trim())) {
|
||||||
|
i++;
|
||||||
|
outJar = args[i].trim();
|
||||||
|
} else if ("--targets".equals(args[i].trim())) {
|
||||||
|
i++;
|
||||||
|
legacyTargets = args[i].trim();
|
||||||
|
} else if ("--pre".equals(args[i].trim())) {
|
||||||
|
i++;
|
||||||
|
legacyPreMethods = args[i].trim();
|
||||||
|
} else if ("--post".equals(args[i].trim())) {
|
||||||
|
i++;
|
||||||
|
legacyPostMethods = args[i].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(acleung): Better help message than asserts.
|
||||||
|
assert inJar != null;
|
||||||
|
assert outJar != null;
|
||||||
|
assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null);
|
||||||
|
|
||||||
|
ZipFile zipSrc = new ZipFile(inJar);
|
||||||
|
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar));
|
||||||
|
List<LockTarget> targets = null;
|
||||||
|
if (legacyTargets != null) {
|
||||||
|
targets = Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
|
||||||
|
legacyPostMethods);
|
||||||
|
} else {
|
||||||
|
targets = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries();
|
||||||
|
while (srcEntries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = srcEntries.nextElement();
|
||||||
|
ZipEntry newEntry = new ZipEntry(entry.getName());
|
||||||
|
zos.putNextEntry(newEntry);
|
||||||
|
BufferedInputStream bis = new BufferedInputStream(zipSrc.getInputStream(entry));
|
||||||
|
|
||||||
|
if (entry.getName().endsWith(".class")) {
|
||||||
|
convert(bis, zos, targets);
|
||||||
|
} else {
|
||||||
|
while (bis.available() > 0) {
|
||||||
|
zos.write(bis.read());
|
||||||
|
}
|
||||||
|
zos.closeEntry();
|
||||||
|
bis.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zos.finish();
|
||||||
|
zos.close();
|
||||||
|
zipSrc.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void convert(InputStream in, OutputStream out, List<LockTarget> targets)
|
||||||
|
throws IOException {
|
||||||
|
ClassReader cr = new ClassReader(in);
|
||||||
|
ClassWriter cw = new ClassWriter(0);
|
||||||
|
LockFindingClassVisitor cv = new LockFindingClassVisitor(targets, cw);
|
||||||
|
cr.accept(cv, 0);
|
||||||
|
byte[] data = cw.toByteArray();
|
||||||
|
out.write(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.List;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
public static final int ASM_VERSION = Opcodes.ASM5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a comma separated configuration similar to the Jack definition.
|
||||||
|
*/
|
||||||
|
public static List<LockTarget> getTargetsFromLegacyJackConfig(String classList,
|
||||||
|
String requestList, String resetList) {
|
||||||
|
|
||||||
|
String[] classes = classList.split(",");
|
||||||
|
String[] requests = requestList.split(",");
|
||||||
|
String[] resets = resetList.split(",");
|
||||||
|
|
||||||
|
int total = classes.length;
|
||||||
|
assert requests.length == total;
|
||||||
|
assert resets.length == total;
|
||||||
|
|
||||||
|
List<LockTarget> config = new ArrayList<LockTarget>();
|
||||||
|
|
||||||
|
for (int i = 0; i < total; i++) {
|
||||||
|
config.add(new LockTarget(classes[i], requests[i], resets[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To run the unit tests:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* set -x
|
||||||
|
*
|
||||||
|
* # Clean
|
||||||
|
* rm -fr out/*
|
||||||
|
*
|
||||||
|
* # Make booster
|
||||||
|
* javac -cp lib/asm-all-5.2.jar src/*/*.java -d out/
|
||||||
|
* pushd out
|
||||||
|
* jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main */*.class
|
||||||
|
* popd
|
||||||
|
*
|
||||||
|
* # Make unit tests.
|
||||||
|
* javac -cp lib/junit-4.12.jar test/*/*.java -d out/
|
||||||
|
*
|
||||||
|
* pushd out
|
||||||
|
* jar cfe test_input.jar lockedregioncodeinjection.Test */*.class
|
||||||
|
* popd
|
||||||
|
*
|
||||||
|
* # Run tool on unit tests.
|
||||||
|
* java -ea -cp lib/asm-all-5.2.jar:out/lockedregioncodeinjection.jar \
|
||||||
|
* lockedregioncodeinjection.Main \
|
||||||
|
* -i out/test_input.jar -o out/test_output.jar \
|
||||||
|
* --targets 'Llockedregioncodeinjection/TestTarget;' \
|
||||||
|
* --pre 'lockedregioncodeinjection/TestTarget.boost' \
|
||||||
|
* --post 'lockedregioncodeinjection/TestTarget.unboost'
|
||||||
|
*
|
||||||
|
* # Run unit tests.
|
||||||
|
* java -ea -cp lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar:out/test_output.jar \
|
||||||
|
* org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class TestMain {
|
||||||
|
@Test
|
||||||
|
public void testSimpleSynchronizedBlock() {
|
||||||
|
TestTarget.resetCount();
|
||||||
|
TestTarget t = new TestTarget();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
|
||||||
|
synchronized (t) {
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
TestTarget.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleSynchronizedMethod() {
|
||||||
|
TestTarget.resetCount();
|
||||||
|
TestTarget t = new TestTarget();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
|
||||||
|
t.synchronizedCall();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleSynchronizedMethod2() {
|
||||||
|
TestTarget.resetCount();
|
||||||
|
TestTarget t = new TestTarget();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
|
||||||
|
t.synchronizedCallReturnInt();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleSynchronizedMethod3() {
|
||||||
|
TestTarget.resetCount();
|
||||||
|
TestTarget t = new TestTarget();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
|
||||||
|
t.synchronizedCallReturnObject();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testCaughtException() {
|
||||||
|
TestTarget.resetCount();
|
||||||
|
TestTarget t = new TestTarget();
|
||||||
|
boolean caughtException = false;
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
synchronized (t) {
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
if (true) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
TestTarget.invoke();
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
caughtException = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.invokeCount, 0); // Not called
|
||||||
|
Assert.assertTrue(caughtException);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private void testUncaughtException() {
|
||||||
|
TestTarget t = new TestTarget();
|
||||||
|
synchronized (t) {
|
||||||
|
if (true) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
TestTarget.invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testHandledFinally() {
|
||||||
|
TestTarget.resetCount();
|
||||||
|
try {
|
||||||
|
testUncaughtException();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.invokeCount, 0); // Not called
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNestedSynchronizedBlock() {
|
||||||
|
TestTarget.resetCount();
|
||||||
|
TestTarget t = new TestTarget();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
|
||||||
|
synchronized (t) {
|
||||||
|
synchronized (t) {
|
||||||
|
synchronized (t) {
|
||||||
|
synchronized (t) {
|
||||||
|
synchronized (t) {
|
||||||
|
synchronized (t) {
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 6);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
TestTarget.invoke();
|
||||||
|
}
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
|
}
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 2);
|
||||||
|
}
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 3);
|
||||||
|
}
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 4);
|
||||||
|
}
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 6);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 6);
|
||||||
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMethodWithControlFlow() {
|
||||||
|
TestTarget.resetCount();
|
||||||
|
TestTarget t = new TestTarget();
|
||||||
|
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
|
||||||
|
if ((t.hashCode() + " ").contains("1")) {
|
||||||
|
t.synchronizedCall();
|
||||||
|
} else {
|
||||||
|
t.synchronizedCall();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should only be boosted once.
|
||||||
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public class TestTarget {
|
||||||
|
public static int boostCount = 0;
|
||||||
|
public static int unboostCount = 0;
|
||||||
|
public static int invokeCount = 0;
|
||||||
|
|
||||||
|
public static void boost() {
|
||||||
|
boostCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unboost() {
|
||||||
|
unboostCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void invoke() {
|
||||||
|
invokeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetCount() {
|
||||||
|
boostCount = 0;
|
||||||
|
unboostCount = 0;
|
||||||
|
invokeCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void synchronizedCall() {
|
||||||
|
invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int synchronizedCallReturnInt() {
|
||||||
|
invoke();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Object synchronizedCallReturnObject() {
|
||||||
|
invoke();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user