Merge "Add a scoped lock injection tool"
This commit is contained in:
@@ -19,3 +19,20 @@ java_binary_host {
|
|||||||
"ow2-asm-tree",
|
"ow2-asm-tree",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
java_library_host {
|
||||||
|
name: "lockedregioncodeinjection_input",
|
||||||
|
manifest: "test/manifest.txt",
|
||||||
|
srcs: ["test/*/*.java"],
|
||||||
|
static_libs: [
|
||||||
|
"guava",
|
||||||
|
"ow2-asm",
|
||||||
|
"ow2-asm-analysis",
|
||||||
|
"ow2-asm-commons",
|
||||||
|
"ow2-asm-tree",
|
||||||
|
"hamcrest-library",
|
||||||
|
"hamcrest",
|
||||||
|
"platform-test-annotations",
|
||||||
|
"junit",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,37 +13,51 @@
|
|||||||
*/
|
*/
|
||||||
package lockedregioncodeinjection;
|
package lockedregioncodeinjection;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||||
import java.util.Arrays;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import java.util.LinkedList;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import java.util.List;
|
|
||||||
import org.objectweb.asm.ClassVisitor;
|
import org.objectweb.asm.ClassVisitor;
|
||||||
import org.objectweb.asm.MethodVisitor;
|
import org.objectweb.asm.MethodVisitor;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.commons.TryCatchBlockSorter;
|
import org.objectweb.asm.commons.TryCatchBlockSorter;
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
import org.objectweb.asm.tree.InsnList;
|
import org.objectweb.asm.tree.InsnList;
|
||||||
|
import org.objectweb.asm.tree.InsnNode;
|
||||||
import org.objectweb.asm.tree.LabelNode;
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
|
import org.objectweb.asm.tree.LineNumberNode;
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
import org.objectweb.asm.tree.TypeInsnNode;
|
||||||
import org.objectweb.asm.tree.TryCatchBlockNode;
|
import org.objectweb.asm.tree.TryCatchBlockNode;
|
||||||
import org.objectweb.asm.tree.analysis.Analyzer;
|
import org.objectweb.asm.tree.analysis.Analyzer;
|
||||||
import org.objectweb.asm.tree.analysis.AnalyzerException;
|
import org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||||
import org.objectweb.asm.tree.analysis.BasicValue;
|
import org.objectweb.asm.tree.analysis.BasicValue;
|
||||||
import org.objectweb.asm.tree.analysis.Frame;
|
import org.objectweb.asm.tree.analysis.Frame;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
import java.util.ArrayList;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import java.util.Arrays;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This visitor does two things:
|
* This visitor operates on two kinds of targets. For a legacy target, it does the following:
|
||||||
*
|
*
|
||||||
* 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and insert the corresponding pre
|
* 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and inserts the corresponding pre
|
||||||
* and post methods calls should it matches one of the given target type in the Configuration.
|
* 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
|
* 2. Find all methods that are synchronized and insert pre method calls in the beginning and post
|
||||||
* method calls just before all return instructions.
|
* method calls just before all return instructions.
|
||||||
|
*
|
||||||
|
* For a scoped target, it does the following:
|
||||||
|
*
|
||||||
|
* 1. Finds all the MONITOR_ENTER instructions in the byte code. If the target of the opcode is
|
||||||
|
* named in a --scope switch, then the pre method is invoked ON THE TARGET immediately after
|
||||||
|
* MONITOR_ENTER opcode completes.
|
||||||
|
*
|
||||||
|
* 2. Finds all the MONITOR_EXIT instructions in the byte code. If the target of the opcode is
|
||||||
|
* named in a --scope switch, then the post method is invoked ON THE TARGET immediately before
|
||||||
|
* MONITOR_EXIT opcode completes.
|
||||||
*/
|
*/
|
||||||
class LockFindingClassVisitor extends ClassVisitor {
|
class LockFindingClassVisitor extends ClassVisitor {
|
||||||
private String className = null;
|
private String className = null;
|
||||||
@@ -73,12 +87,16 @@ class LockFindingClassVisitor extends ClassVisitor {
|
|||||||
class LockFindingMethodVisitor extends MethodVisitor {
|
class LockFindingMethodVisitor extends MethodVisitor {
|
||||||
private String owner;
|
private String owner;
|
||||||
private MethodVisitor chain;
|
private MethodVisitor chain;
|
||||||
|
private final String className;
|
||||||
|
private final String methodName;
|
||||||
|
|
||||||
public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
|
public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
|
||||||
super(Utils.ASM_VERSION, mn);
|
super(Utils.ASM_VERSION, mn);
|
||||||
assert owner != null;
|
assert owner != null;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.chain = chain;
|
this.chain = chain;
|
||||||
|
className = owner;
|
||||||
|
methodName = mn.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -93,6 +111,12 @@ class LockFindingClassVisitor extends ClassVisitor {
|
|||||||
for (LockTarget t : targets) {
|
for (LockTarget t : targets) {
|
||||||
if (t.getTargetDesc().equals("L" + owner + ";")) {
|
if (t.getTargetDesc().equals("L" + owner + ";")) {
|
||||||
ownerMonitor = t;
|
ownerMonitor = t;
|
||||||
|
if (ownerMonitor.getScoped()) {
|
||||||
|
final String emsg = String.format(
|
||||||
|
"scoped targets do not support synchronized methods in %s.%s()",
|
||||||
|
className, methodName);
|
||||||
|
throw new RuntimeException(emsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,9 +142,11 @@ class LockFindingClassVisitor extends ClassVisitor {
|
|||||||
AbstractInsnNode s = instructions.getFirst();
|
AbstractInsnNode s = instructions.getFirst();
|
||||||
MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
|
MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
|
||||||
ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false);
|
ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false);
|
||||||
insertMethodCallBefore(mn, frameMap, handlersMap, s, 0, call);
|
insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, 0, call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean anyDup = false;
|
||||||
|
|
||||||
for (int i = 0; i < instructions.size(); i++) {
|
for (int i = 0; i < instructions.size(); i++) {
|
||||||
AbstractInsnNode s = instructions.get(i);
|
AbstractInsnNode s = instructions.get(i);
|
||||||
|
|
||||||
@@ -131,9 +157,15 @@ class LockFindingClassVisitor extends ClassVisitor {
|
|||||||
LockTargetState state = (LockTargetState) operand;
|
LockTargetState state = (LockTargetState) operand;
|
||||||
for (int j = 0; j < state.getTargets().size(); j++) {
|
for (int j = 0; j < state.getTargets().size(); j++) {
|
||||||
LockTarget target = state.getTargets().get(j);
|
LockTarget target = state.getTargets().get(j);
|
||||||
MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
|
MethodInsnNode call = methodCall(target, true);
|
||||||
target.getPreOwner(), target.getPreMethod(), "()V", false);
|
if (target.getScoped()) {
|
||||||
insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
|
TypeInsnNode cast = typeCast(target);
|
||||||
|
i += insertInvokeAcquire(mn, frameMap, handlersMap, s, i,
|
||||||
|
call, cast);
|
||||||
|
anyDup = true;
|
||||||
|
} else {
|
||||||
|
i += insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,8 +176,9 @@ class LockFindingClassVisitor extends ClassVisitor {
|
|||||||
if (operand instanceof LockTargetState) {
|
if (operand instanceof LockTargetState) {
|
||||||
LockTargetState state = (LockTargetState) operand;
|
LockTargetState state = (LockTargetState) operand;
|
||||||
for (int j = 0; j < state.getTargets().size(); j++) {
|
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
|
// The instruction after a monitor_exit should be a label for
|
||||||
// catch block that surrounds the synchronized block to call monitor_exit when an exception
|
// the end of the implicit catch block that surrounds the
|
||||||
|
// synchronized block to call monitor_exit when an exception
|
||||||
// occurs.
|
// occurs.
|
||||||
checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL,
|
checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL,
|
||||||
"Expected to find label after monitor exit");
|
"Expected to find label after monitor exit");
|
||||||
@@ -161,9 +194,16 @@ class LockFindingClassVisitor extends ClassVisitor {
|
|||||||
"Expected label to be the end of monitor exit's try block");
|
"Expected label to be the end of monitor exit's try block");
|
||||||
|
|
||||||
LockTarget target = state.getTargets().get(j);
|
LockTarget target = state.getTargets().get(j);
|
||||||
MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
|
MethodInsnNode call = methodCall(target, false);
|
||||||
target.getPostOwner(), target.getPostMethod(), "()V", false);
|
if (target.getScoped()) {
|
||||||
insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call);
|
TypeInsnNode cast = typeCast(target);
|
||||||
|
i += insertInvokeRelease(mn, frameMap, handlersMap, s, i,
|
||||||
|
call, cast);
|
||||||
|
anyDup = true;
|
||||||
|
} else {
|
||||||
|
insertMethodCallAfter(mn, frameMap, handlersMap, label,
|
||||||
|
labelIndex, call);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,16 +214,116 @@ class LockFindingClassVisitor extends ClassVisitor {
|
|||||||
MethodInsnNode call =
|
MethodInsnNode call =
|
||||||
new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(),
|
new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(),
|
||||||
ownerMonitor.getPostMethod(), "()V", false);
|
ownerMonitor.getPostMethod(), "()V", false);
|
||||||
insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call);
|
insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, i, call);
|
||||||
i++; // Skip ahead. Otherwise, we will revisit this instruction again.
|
i++; // Skip ahead. Otherwise, we will revisit this instruction again.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (anyDup) {
|
||||||
|
mn.maxStack++;
|
||||||
|
}
|
||||||
|
|
||||||
super.visitEnd();
|
super.visitEnd();
|
||||||
mn.accept(chain);
|
mn.accept(chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert a call to a monitor pre handler. The node and the index identify the
|
||||||
|
// monitorenter call itself. Insert DUP immediately prior to the MONITORENTER.
|
||||||
|
// Insert the typecast and call (in that order) after the MONITORENTER.
|
||||||
|
public int insertInvokeAcquire(MethodNode mn, List<Frame> frameMap,
|
||||||
|
List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
|
||||||
|
MethodInsnNode call, TypeInsnNode cast) {
|
||||||
|
InsnList instructions = mn.instructions;
|
||||||
|
|
||||||
|
// Insert a DUP right before MONITORENTER, to capture the object being locked.
|
||||||
|
// Note that the object will be typed as java.lang.Object.
|
||||||
|
instructions.insertBefore(node, new InsnNode(Opcodes.DUP));
|
||||||
|
frameMap.add(index, frameMap.get(index));
|
||||||
|
handlersMap.add(index, handlersMap.get(index));
|
||||||
|
|
||||||
|
// Insert the call right after the MONITORENTER. These entries are pushed after
|
||||||
|
// MONITORENTER so they are inserted in reverse order. MONITORENTER should be
|
||||||
|
// the target of a try/catch block, which means it must be immediately
|
||||||
|
// followed by a label (which is part of the try/catch block definition).
|
||||||
|
// Move forward past the label so the invocation in inside the proper block.
|
||||||
|
// Throw an error if the next instruction is not a label.
|
||||||
|
node = node.getNext();
|
||||||
|
if (!(node instanceof LabelNode)) {
|
||||||
|
throw new RuntimeException(String.format("invalid bytecode sequence in %s.%s()",
|
||||||
|
className, methodName));
|
||||||
|
}
|
||||||
|
node = node.getNext();
|
||||||
|
index = instructions.indexOf(node);
|
||||||
|
|
||||||
|
instructions.insertBefore(node, cast);
|
||||||
|
frameMap.add(index, frameMap.get(index));
|
||||||
|
handlersMap.add(index, handlersMap.get(index));
|
||||||
|
|
||||||
|
instructions.insertBefore(node, call);
|
||||||
|
frameMap.add(index, frameMap.get(index));
|
||||||
|
handlersMap.add(index, handlersMap.get(index));
|
||||||
|
|
||||||
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
|
// Insert instructions completely before the current opcode. This is slightly
|
||||||
|
// different from insertMethodCallBefore(), which inserts the call before MONITOREXIT
|
||||||
|
// but inserts the start and end labels after MONITOREXIT.
|
||||||
|
public int insertInvokeRelease(MethodNode mn, List<Frame> frameMap,
|
||||||
|
List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
|
||||||
|
MethodInsnNode call, TypeInsnNode cast) {
|
||||||
|
InsnList instructions = mn.instructions;
|
||||||
|
|
||||||
|
instructions.insertBefore(node, new InsnNode(Opcodes.DUP));
|
||||||
|
frameMap.add(index, frameMap.get(index));
|
||||||
|
handlersMap.add(index, handlersMap.get(index));
|
||||||
|
|
||||||
|
instructions.insertBefore(node, cast);
|
||||||
|
frameMap.add(index, frameMap.get(index));
|
||||||
|
handlersMap.add(index, handlersMap.get(index));
|
||||||
|
|
||||||
|
instructions.insertBefore(node, call);
|
||||||
|
frameMap.add(index, frameMap.get(index));
|
||||||
|
handlersMap.add(index, handlersMap.get(index));
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInsnNode methodCall(LockTarget target, boolean pre) {
|
||||||
|
String spec = "()V";
|
||||||
|
if (!target.getScoped()) {
|
||||||
|
if (pre) {
|
||||||
|
return new MethodInsnNode(
|
||||||
|
Opcodes.INVOKESTATIC, target.getPreOwner(), target.getPreMethod(), spec);
|
||||||
|
} else {
|
||||||
|
return new MethodInsnNode(
|
||||||
|
Opcodes.INVOKESTATIC, target.getPostOwner(), target.getPostMethod(), spec);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pre) {
|
||||||
|
return new MethodInsnNode(
|
||||||
|
Opcodes.INVOKEVIRTUAL, target.getPreOwner(), target.getPreMethod(), spec);
|
||||||
|
} else {
|
||||||
|
return new MethodInsnNode(
|
||||||
|
Opcodes.INVOKEVIRTUAL, target.getPostOwner(), target.getPostMethod(), spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TypeInsnNode typeCast(LockTarget target) {
|
||||||
|
if (!target.getScoped()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// preOwner and postOwner return the same string for scoped targets.
|
||||||
|
return new TypeInsnNode(Opcodes.CHECKCAST, target.getPreOwner());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a method call before the beginning or end of a synchronized method.
|
||||||
|
*/
|
||||||
|
public static void insertMethodCallBeforeSync(MethodNode mn, List<Frame> frameMap,
|
||||||
List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
|
List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
|
||||||
MethodInsnNode call) {
|
MethodInsnNode call) {
|
||||||
List<TryCatchBlockNode> handlers = handlersMap.get(index);
|
List<TryCatchBlockNode> handlers = handlersMap.get(index);
|
||||||
@@ -226,6 +366,22 @@ class LockFindingClassVisitor extends ClassVisitor {
|
|||||||
updateCatchHandler(mn, handlers, start, end, handlersMap);
|
updateCatchHandler(mn, handlers, start, end, handlersMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert instructions completely before the current opcode. This is slightly different from
|
||||||
|
// insertMethodCallBeforeSync(), which inserts the call before MONITOREXIT but inserts the
|
||||||
|
// start and end labels after MONITOREXIT.
|
||||||
|
public int insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
|
||||||
|
List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
|
||||||
|
MethodInsnNode call) {
|
||||||
|
InsnList instructions = mn.instructions;
|
||||||
|
|
||||||
|
instructions.insertBefore(node, call);
|
||||||
|
frameMap.add(index, frameMap.get(index));
|
||||||
|
handlersMap.add(index, handlersMap.get(index));
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers,
|
public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers,
|
||||||
LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) {
|
LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) {
|
||||||
|
|||||||
@@ -21,14 +21,28 @@ package lockedregioncodeinjection;
|
|||||||
public class LockTarget {
|
public class LockTarget {
|
||||||
public static final LockTarget NO_TARGET = new LockTarget("", null, null);
|
public static final LockTarget NO_TARGET = new LockTarget("", null, null);
|
||||||
|
|
||||||
|
// The lock which must be instrumented, in Java internal form (L<path>;).
|
||||||
private final String targetDesc;
|
private final String targetDesc;
|
||||||
|
// The methods to be called when the lock is taken (released). For non-scoped locks,
|
||||||
|
// these are fully qualified static methods. For scoped locks, these are the
|
||||||
|
// unqualified names of a member method of the target lock.
|
||||||
private final String pre;
|
private final String pre;
|
||||||
private final String post;
|
private final String post;
|
||||||
|
// If true, the pre and post methods are virtual on the target class. The pre and post methods
|
||||||
|
// are both called while the lock is held. If this field is false then the pre and post methods
|
||||||
|
// take no parameters and the post method is called after the lock is released. This is legacy
|
||||||
|
// behavior.
|
||||||
|
private final boolean scoped;
|
||||||
|
|
||||||
public LockTarget(String targetDesc, String pre, String post) {
|
public LockTarget(String targetDesc, String pre, String post, boolean scoped) {
|
||||||
this.targetDesc = targetDesc;
|
this.targetDesc = targetDesc;
|
||||||
this.pre = pre;
|
this.pre = pre;
|
||||||
this.post = post;
|
this.post = post;
|
||||||
|
this.scoped = scoped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LockTarget(String targetDesc, String pre, String post) {
|
||||||
|
this(targetDesc, pre, post, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTargetDesc() {
|
public String getTargetDesc() {
|
||||||
@@ -40,8 +54,12 @@ public class LockTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getPreOwner() {
|
public String getPreOwner() {
|
||||||
|
if (scoped) {
|
||||||
|
return targetDesc.substring(1, targetDesc.length() - 1);
|
||||||
|
} else {
|
||||||
return pre.substring(0, pre.lastIndexOf('.'));
|
return pre.substring(0, pre.lastIndexOf('.'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getPreMethod() {
|
public String getPreMethod() {
|
||||||
return pre.substring(pre.lastIndexOf('.') + 1);
|
return pre.substring(pre.lastIndexOf('.') + 1);
|
||||||
@@ -52,10 +70,23 @@ public class LockTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getPostOwner() {
|
public String getPostOwner() {
|
||||||
|
if (scoped) {
|
||||||
|
return targetDesc.substring(1, targetDesc.length() - 1);
|
||||||
|
} else {
|
||||||
return post.substring(0, post.lastIndexOf('.'));
|
return post.substring(0, post.lastIndexOf('.'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getPostMethod() {
|
public String getPostMethod() {
|
||||||
return post.substring(post.lastIndexOf('.') + 1);
|
return post.substring(post.lastIndexOf('.') + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getScoped() {
|
||||||
|
return scoped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return targetDesc + ":" + pre + ":" + post;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,11 @@
|
|||||||
*/
|
*/
|
||||||
package lockedregioncodeinjection;
|
package lockedregioncodeinjection;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.analysis.BasicValue;
|
import org.objectweb.asm.tree.analysis.BasicValue;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class LockTargetState extends BasicValue {
|
public class LockTargetState extends BasicValue {
|
||||||
private final List<LockTarget> lockTargets;
|
private final List<LockTarget> lockTargets;
|
||||||
|
|
||||||
@@ -31,4 +32,10 @@ public class LockTargetState extends BasicValue {
|
|||||||
public List<LockTarget> getTargets() {
|
public List<LockTarget> getTargets() {
|
||||||
return lockTargets;
|
return lockTargets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LockTargetState(" + getType().getDescriptor()
|
||||||
|
+ ", " + lockTargets.size() + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Collections;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
@@ -36,6 +36,7 @@ public class Main {
|
|||||||
String legacyTargets = null;
|
String legacyTargets = null;
|
||||||
String legacyPreMethods = null;
|
String legacyPreMethods = null;
|
||||||
String legacyPostMethods = null;
|
String legacyPostMethods = null;
|
||||||
|
List<LockTarget> targets = new ArrayList<>();
|
||||||
for (int i = 0; i < args.length; i++) {
|
for (int i = 0; i < args.length; i++) {
|
||||||
if ("-i".equals(args[i].trim())) {
|
if ("-i".equals(args[i].trim())) {
|
||||||
i++;
|
i++;
|
||||||
@@ -52,23 +53,25 @@ public class Main {
|
|||||||
} else if ("--post".equals(args[i].trim())) {
|
} else if ("--post".equals(args[i].trim())) {
|
||||||
i++;
|
i++;
|
||||||
legacyPostMethods = args[i].trim();
|
legacyPostMethods = args[i].trim();
|
||||||
|
} else if ("--scoped".equals(args[i].trim())) {
|
||||||
|
i++;
|
||||||
|
targets.add(Utils.getScopedTarget(args[i].trim()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inJar == null) {
|
||||||
|
throw new RuntimeException("missing input jar path");
|
||||||
|
}
|
||||||
|
if (outJar == null) {
|
||||||
|
throw new RuntimeException("missing output jar path");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(acleung): Better help message than asserts.
|
|
||||||
assert inJar != null;
|
|
||||||
assert outJar != null;
|
|
||||||
assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null);
|
assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null);
|
||||||
|
|
||||||
ZipFile zipSrc = new ZipFile(inJar);
|
ZipFile zipSrc = new ZipFile(inJar);
|
||||||
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar));
|
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar));
|
||||||
List<LockTarget> targets = null;
|
|
||||||
if (legacyTargets != null) {
|
if (legacyTargets != null) {
|
||||||
targets = Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
|
targets.addAll(Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
|
||||||
legacyPostMethods);
|
legacyPostMethods));
|
||||||
} else {
|
|
||||||
targets = Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries();
|
Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries();
|
||||||
|
|||||||
@@ -44,4 +44,27 @@ public class Utils {
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a single {@link LockTarget} from a string. The target is a comma-separated list of
|
||||||
|
* the target class, the request method, the release method, and a boolean which is true if this
|
||||||
|
* is a scoped target and false if this is a legacy target. The boolean is optional and
|
||||||
|
* defaults to true.
|
||||||
|
*/
|
||||||
|
public static LockTarget getScopedTarget(String arg) {
|
||||||
|
String[] c = arg.split(",");
|
||||||
|
if (c.length == 3) {
|
||||||
|
return new LockTarget(c[0], c[1], c[2], true);
|
||||||
|
} else if (c.length == 4) {
|
||||||
|
if (c[3].equals("true")) {
|
||||||
|
return new LockTarget(c[0], c[1], c[2], true);
|
||||||
|
} else if (c[3].equals("false")) {
|
||||||
|
return new LockTarget(c[0], c[1], c[2], false);
|
||||||
|
} else {
|
||||||
|
System.err.println("illegal target parameter \"" + c[3] + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall through
|
||||||
|
throw new RuntimeException("invalid scoped target format");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import org.junit.Assert;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To run the unit tests:
|
* To run the unit tests, first build the two necessary artifacts. Do this explicitly as they are
|
||||||
|
* not generally retained by a normal "build all". After lunching a target:
|
||||||
|
* m lockedregioncodeinjection
|
||||||
|
* m lockedregioncodeinjection_input
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* <code>
|
* <code>
|
||||||
@@ -29,31 +32,25 @@ import org.junit.Test;
|
|||||||
* mkdir -p out
|
* mkdir -p out
|
||||||
* rm -fr out/*
|
* rm -fr out/*
|
||||||
*
|
*
|
||||||
* # Make booster
|
* # Paths to the build artifacts. These assume linux-x86; YMMV.
|
||||||
* javac -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar src/*/*.java -d out/
|
* ROOT=$TOP/out/host/linux-x86
|
||||||
* pushd out
|
* EXE=$ROOT/bin/lockedregioncodeinjection
|
||||||
* jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main */*.class
|
* INPUT=$ROOT/frameworkd/lockedregioncodeinjection_input.jar
|
||||||
* 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.
|
* # Run tool on unit tests.
|
||||||
* java -ea -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \
|
* $EXE -i $INPUT -o out/test_output.jar \
|
||||||
* lockedregioncodeinjection.Main \
|
|
||||||
* -i out/test_input.jar -o out/test_output.jar \
|
|
||||||
* --targets 'Llockedregioncodeinjection/TestTarget;' \
|
* --targets 'Llockedregioncodeinjection/TestTarget;' \
|
||||||
* --pre 'lockedregioncodeinjection/TestTarget.boost' \
|
* --pre 'lockedregioncodeinjection/TestTarget.boost' \
|
||||||
* --post 'lockedregioncodeinjection/TestTarget.unboost'
|
* --post 'lockedregioncodeinjection/TestTarget.unboost'
|
||||||
*
|
*
|
||||||
* # Run unit tests.
|
* # Run unit tests.
|
||||||
* java -ea -cp lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar:out/test_output.jar \
|
* java -ea -cp out/test_output.jar \
|
||||||
* org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
|
* org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
|
||||||
* </code>
|
* </code>
|
||||||
|
* OR
|
||||||
|
* <code>
|
||||||
|
* bash test/unit-test.sh
|
||||||
|
* </code>
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public class TestMain {
|
public class TestMain {
|
||||||
@@ -64,7 +61,9 @@ public class TestMain {
|
|||||||
|
|
||||||
Assert.assertEquals(TestTarget.boostCount, 0);
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
Assert.assertEquals(TestTarget.unboostCount, 0);
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
Assert.assertEquals(TestTarget.unboostCount, 0);
|
Assert.assertEquals(TestTarget.invokeCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.boostCountLocked, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCountLocked, 0);
|
||||||
|
|
||||||
synchronized (t) {
|
synchronized (t) {
|
||||||
Assert.assertEquals(TestTarget.boostCount, 1);
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
@@ -75,6 +74,8 @@ public class TestMain {
|
|||||||
Assert.assertEquals(TestTarget.boostCount, 1);
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
Assert.assertEquals(TestTarget.unboostCount, 1);
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
Assert.assertEquals(TestTarget.invokeCount, 1);
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.boostCountLocked, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCountLocked, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -84,12 +85,16 @@ public class TestMain {
|
|||||||
|
|
||||||
Assert.assertEquals(TestTarget.boostCount, 0);
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
Assert.assertEquals(TestTarget.unboostCount, 0);
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.boostCountLocked, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCountLocked, 0);
|
||||||
|
|
||||||
t.synchronizedCall();
|
t.synchronizedCall();
|
||||||
|
|
||||||
Assert.assertEquals(TestTarget.boostCount, 1);
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
Assert.assertEquals(TestTarget.unboostCount, 1);
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
Assert.assertEquals(TestTarget.invokeCount, 1);
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.boostCountLocked, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCountLocked, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -99,12 +104,16 @@ public class TestMain {
|
|||||||
|
|
||||||
Assert.assertEquals(TestTarget.boostCount, 0);
|
Assert.assertEquals(TestTarget.boostCount, 0);
|
||||||
Assert.assertEquals(TestTarget.unboostCount, 0);
|
Assert.assertEquals(TestTarget.unboostCount, 0);
|
||||||
|
Assert.assertEquals(TestTarget.boostCountLocked, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCountLocked, 0);
|
||||||
|
|
||||||
t.synchronizedCallReturnInt();
|
t.synchronizedCallReturnInt();
|
||||||
|
|
||||||
Assert.assertEquals(TestTarget.boostCount, 1);
|
Assert.assertEquals(TestTarget.boostCount, 1);
|
||||||
Assert.assertEquals(TestTarget.unboostCount, 1);
|
Assert.assertEquals(TestTarget.unboostCount, 1);
|
||||||
Assert.assertEquals(TestTarget.invokeCount, 1);
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
|
Assert.assertEquals(TestTarget.boostCountLocked, 0);
|
||||||
|
Assert.assertEquals(TestTarget.unboostCountLocked, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -253,4 +262,125 @@ public class TestMain {
|
|||||||
Assert.assertEquals(TestTarget.invokeCount, 1);
|
Assert.assertEquals(TestTarget.invokeCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScopedTarget() {
|
||||||
|
TestScopedTarget target = new TestScopedTarget();
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
|
||||||
|
synchronized (target.scopedLock()) {
|
||||||
|
Assert.assertEquals(1, target.scopedLock().mLevel);
|
||||||
|
}
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
|
||||||
|
synchronized (target.scopedLock()) {
|
||||||
|
synchronized (target.scopedLock()) {
|
||||||
|
Assert.assertEquals(2, target.scopedLock().mLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScopedExceptionHandling() {
|
||||||
|
TestScopedTarget target = new TestScopedTarget();
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
|
||||||
|
boolean handled;
|
||||||
|
|
||||||
|
// 1: an exception inside the block properly releases the lock.
|
||||||
|
handled = false;
|
||||||
|
try {
|
||||||
|
synchronized (target.scopedLock()) {
|
||||||
|
Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
|
||||||
|
Assert.assertEquals(1, target.scopedLock().mLevel);
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
Assert.assertEquals(true, handled);
|
||||||
|
// Just verify that the lock can still be taken
|
||||||
|
Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
|
||||||
|
|
||||||
|
// 2: An exception inside the monitor enter function
|
||||||
|
handled = false;
|
||||||
|
target.throwOnEnter(true);
|
||||||
|
try {
|
||||||
|
synchronized (target.scopedLock()) {
|
||||||
|
// The exception was thrown inside monitorEnter(), so the code should
|
||||||
|
// never reach this point.
|
||||||
|
Assert.assertEquals(0, 1);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
Assert.assertEquals(true, handled);
|
||||||
|
// Just verify that the lock can still be taken
|
||||||
|
Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
|
||||||
|
|
||||||
|
// 3: An exception inside the monitor exit function
|
||||||
|
handled = false;
|
||||||
|
target.throwOnEnter(true);
|
||||||
|
try {
|
||||||
|
synchronized (target.scopedLock()) {
|
||||||
|
Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
|
||||||
|
Assert.assertEquals(1, target.scopedLock().mLevel);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
Assert.assertEquals(true, handled);
|
||||||
|
// Just verify that the lock can still be taken
|
||||||
|
Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide an in-class type conversion for the scoped target.
|
||||||
|
private Object untypedLock(TestScopedTarget target) {
|
||||||
|
return target.scopedLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScopedLockTyping() {
|
||||||
|
TestScopedTarget target = new TestScopedTarget();
|
||||||
|
Assert.assertEquals(target.scopedLock().mLevel, 0);
|
||||||
|
|
||||||
|
// Scoped lock injection works on the static type of an object. In general, it is
|
||||||
|
// a very bad idea to do type conversion on scoped locks, but the general rule is
|
||||||
|
// that conversions within a single method are recognized by the lock injection
|
||||||
|
// tool and injection occurs. Conversions outside a single method are not
|
||||||
|
// recognized and injection does not occur.
|
||||||
|
|
||||||
|
// 1. Conversion occurs outside the class. The visible type of the lock is Object
|
||||||
|
// in this block, so no injection takes place on 'untypedLock', even though the
|
||||||
|
// dynamic type is TestScopedLock.
|
||||||
|
synchronized (target.untypedLock()) {
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock);
|
||||||
|
Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Conversion occurs inside the class but in another method. The visible type
|
||||||
|
// of the lock is Object in this block, so no injection takes place on
|
||||||
|
// 'untypedLock', even though the dynamic type is TestScopedLock.
|
||||||
|
synchronized (untypedLock(target)) {
|
||||||
|
Assert.assertEquals(0, target.scopedLock().mLevel);
|
||||||
|
Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock);
|
||||||
|
Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Conversion occurs inside the method. The compiler can determine the type of
|
||||||
|
// the lock within a single function, so injection does take place here.
|
||||||
|
Object untypedLock = target.scopedLock();
|
||||||
|
synchronized (untypedLock) {
|
||||||
|
Assert.assertEquals(1, target.scopedLock().mLevel);
|
||||||
|
Assert.assertEquals(true, untypedLock instanceof TestScopedLock);
|
||||||
|
Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 TestScopedLock {
|
||||||
|
public int mEntered = 0;
|
||||||
|
public int mExited = 0;
|
||||||
|
|
||||||
|
public int mLevel = 0;
|
||||||
|
private final TestScopedTarget mTarget;
|
||||||
|
|
||||||
|
TestScopedLock(TestScopedTarget target) {
|
||||||
|
mTarget = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
void monitorEnter() {
|
||||||
|
mLevel++;
|
||||||
|
mEntered++;
|
||||||
|
mTarget.enter(mLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void monitorExit() {
|
||||||
|
mLevel--;
|
||||||
|
mExited++;
|
||||||
|
mTarget.exit(mLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 TestScopedTarget {
|
||||||
|
|
||||||
|
public final TestScopedLock mLock;;
|
||||||
|
|
||||||
|
private boolean mNextEnterThrows = false;
|
||||||
|
private boolean mNextExitThrows = false;
|
||||||
|
|
||||||
|
TestScopedTarget() {
|
||||||
|
mLock = new TestScopedLock(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
TestScopedLock scopedLock() {
|
||||||
|
return mLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object untypedLock() {
|
||||||
|
return mLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enter(int level) {
|
||||||
|
if (mNextEnterThrows) {
|
||||||
|
mNextEnterThrows = false;
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit(int level) {
|
||||||
|
if (mNextExitThrows) {
|
||||||
|
mNextExitThrows = false;
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void throwOnEnter(boolean b) {
|
||||||
|
mNextEnterThrows = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void throwOnExit(boolean b) {
|
||||||
|
mNextExitThrows = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,8 +19,17 @@ public class TestTarget {
|
|||||||
public static int invokeCount = 0;
|
public static int invokeCount = 0;
|
||||||
public static boolean nextUnboostThrows = false;
|
public static boolean nextUnboostThrows = false;
|
||||||
|
|
||||||
|
// If this is not null, then this is the lock under test. The lock must not be held when boost()
|
||||||
|
// or unboost() are called.
|
||||||
|
public static Object mLock = null;
|
||||||
|
public static int boostCountLocked = 0;
|
||||||
|
public static int unboostCountLocked = 0;
|
||||||
|
|
||||||
public static void boost() {
|
public static void boost() {
|
||||||
boostCount++;
|
boostCount++;
|
||||||
|
if (mLock != null && Thread.currentThread().holdsLock(mLock)) {
|
||||||
|
boostCountLocked++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unboost() {
|
public static void unboost() {
|
||||||
@@ -29,6 +38,9 @@ public class TestTarget {
|
|||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
unboostCount++;
|
unboostCount++;
|
||||||
|
if (mLock != null && Thread.currentThread().holdsLock(mLock)) {
|
||||||
|
unboostCountLocked++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void invoke() {
|
public static void invoke() {
|
||||||
|
|||||||
1
tools/locked_region_code_injection/test/manifest.txt
Normal file
1
tools/locked_region_code_injection/test/manifest.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Main-Class: org.junit.runner.JUnitCore
|
||||||
98
tools/locked_region_code_injection/test/unit-test.sh
Executable file
98
tools/locked_region_code_injection/test/unit-test.sh
Executable file
@@ -0,0 +1,98 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
#
|
||||||
|
|
||||||
|
# Copyright (C) 2023 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.
|
||||||
|
|
||||||
|
# This script runs the tests for the lockedregioninjectioncode. See
|
||||||
|
# TestMain.java for the invocation. The script expects that a full build has
|
||||||
|
# already been done and artifacts are in $TOP/out.
|
||||||
|
|
||||||
|
# Compute the default top of the workspace. The following code copies the
|
||||||
|
# strategy of croot. (croot cannot be usd directly because it is a function and
|
||||||
|
# functions are not carried over into subshells.) This gives the correct answer
|
||||||
|
# if run from inside a workspace. If run from outside a workspace, supply TOP
|
||||||
|
# on the command line.
|
||||||
|
TOPFILE=build/make/core/envsetup.mk
|
||||||
|
TOP=$(dirname $(realpath $0))
|
||||||
|
while [[ ! $TOP = / && ! -f $TOP/$TOPFILE ]]; do
|
||||||
|
TOP=$(dirname $TOP)
|
||||||
|
done
|
||||||
|
# TOP is "/" if this script is located outside a workspace.
|
||||||
|
|
||||||
|
# If the user supplied a top directory, use it instead
|
||||||
|
if [[ -n $1 ]]; then
|
||||||
|
TOP=$1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
if [[ -z $TOP || $TOP = / ]]; then
|
||||||
|
echo "usage: $0 <workspace-root>"
|
||||||
|
exit 1
|
||||||
|
elif [[ ! -d $TOP ]]; then
|
||||||
|
echo "$TOP is not a directory"
|
||||||
|
exit 1
|
||||||
|
elif [[ ! -d $TOP/prebuilts/misc/common ]]; then
|
||||||
|
echo "$TOP does not look like w workspace"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Using workspace $TOP"
|
||||||
|
|
||||||
|
# Pick up the current java compiler. The lunch target is not very important,
|
||||||
|
# since most, if not all, will use the same host binaries.
|
||||||
|
pushd $TOP > /dev/null
|
||||||
|
. build/envsetup.sh > /dev/null 2>&1
|
||||||
|
lunch redfin-userdebug > /dev/null 2>&1
|
||||||
|
popd > /dev/null
|
||||||
|
|
||||||
|
# Bail on any error
|
||||||
|
set -o pipefail
|
||||||
|
trap 'exit 1' ERR
|
||||||
|
|
||||||
|
# Create the two sources
|
||||||
|
pushd $TOP > /dev/null
|
||||||
|
m lockedregioncodeinjection
|
||||||
|
m lockedregioncodeinjection_input
|
||||||
|
popd > /dev/null
|
||||||
|
|
||||||
|
# Create a temporary directory outside of the workspace.
|
||||||
|
OUT=$TOP/out/host/test/lockedregioncodeinjection
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Clean the directory
|
||||||
|
if [[ -d $OUT ]]; then rm -r $OUT; fi
|
||||||
|
mkdir -p $OUT
|
||||||
|
|
||||||
|
ROOT=$TOP/out/host/linux-x86
|
||||||
|
EXE=$ROOT/bin/lockedregioncodeinjection
|
||||||
|
INP=$ROOT/framework/lockedregioncodeinjection_input.jar
|
||||||
|
|
||||||
|
# Run tool on unit tests.
|
||||||
|
$EXE \
|
||||||
|
-i $INP -o $OUT/test_output.jar \
|
||||||
|
--targets 'Llockedregioncodeinjection/TestTarget;' \
|
||||||
|
--pre 'lockedregioncodeinjection/TestTarget.boost' \
|
||||||
|
--post 'lockedregioncodeinjection/TestTarget.unboost' \
|
||||||
|
--scoped 'Llockedregioncodeinjection/TestScopedLock;,monitorEnter,monitorExit'
|
||||||
|
|
||||||
|
# Run unit tests.
|
||||||
|
java -ea -cp $OUT/test_output.jar \
|
||||||
|
org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
|
||||||
|
|
||||||
|
# Extract the class files and decompile them for possible post-analysis.
|
||||||
|
pushd $OUT > /dev/null
|
||||||
|
jar -x --file test_output.jar lockedregioncodeinjection
|
||||||
|
for class in lockedregioncodeinjection/*.class; do
|
||||||
|
javap -c -v $class > ${class%.class}.asm
|
||||||
|
done
|
||||||
|
popd > /dev/null
|
||||||
|
|
||||||
|
echo "artifacts are in $OUT"
|
||||||
Reference in New Issue
Block a user