diff options
author | Alan Leung <acleung@google.com> | 2017-05-12 17:31:13 -0700 |
---|---|---|
committer | Colin Cross <ccross@android.com> | 2017-06-22 14:00:04 -0700 |
commit | ed36ba52bf8a7492522fea56106b1f0380ebf7b3 (patch) | |
tree | 4f7a990ab7b0acd9093501fe9c4ad0bfa032ecb5 /tools/locked_region_code_injection | |
parent | 156775fea8cccf658b0fe2a3bcf05805062e5104 (diff) |
ASM Priority Boost Tool
This tool is a replacement for the Jack plugin that allows injection
of static method calls before lock enter and exit.
A common use case would be to boost a thread's priority as soon as
it acquires a heavily contented lock and resetting the priority upon
release.
This tool is meant to be optionally invoked from /build/core/ rules
during the build process.
Test: JUnit Tests / resulting image on a bullhead.
Change-Id: If400414a0bf50f03768a1de2ebee42086a9d701f
Merged-In: If400414a0bf50f03768a1de2ebee42086a9d701f
(cherry picked from commit 93db63059ad5e9e1d59c8ce013559069d566ef4c)
Diffstat (limited to 'tools/locked_region_code_injection')
10 files changed, 894 insertions, 0 deletions
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk new file mode 100644 index 000000000000..0aed0cec27ab --- /dev/null +++ b/tools/locked_region_code_injection/Android.mk @@ -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) diff --git a/tools/locked_region_code_injection/manifest.txt b/tools/locked_region_code_injection/manifest.txt new file mode 100644 index 000000000000..4b9de003d392 --- /dev/null +++ b/tools/locked_region_code_injection/manifest.txt @@ -0,0 +1 @@ +Main-Class: lockedregioncodeinjection.Main diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java new file mode 100644 index 000000000000..9374f23c945e --- /dev/null +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java @@ -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); + } +} diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java new file mode 100644 index 000000000000..c5e59e3dc64d --- /dev/null +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java @@ -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); + } +} diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java new file mode 100644 index 000000000000..99d841829132 --- /dev/null +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java @@ -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; + } +} diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java new file mode 100644 index 000000000000..1002c88e9281 --- /dev/null +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java @@ -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); + } +} diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java new file mode 100644 index 000000000000..edb9a49f4106 --- /dev/null +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java @@ -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); + } +} diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java new file mode 100644 index 000000000000..d2a2e7b90f2b --- /dev/null +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java @@ -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; + } +} diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java new file mode 100644 index 000000000000..1d4f2d455270 --- /dev/null +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java @@ -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); + } +} diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java new file mode 100644 index 000000000000..8e7d478a0e29 --- /dev/null +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java @@ -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; + } +} |