OSDN Git Service

ASM Priority Boost Tool
authorAlan Leung <acleung@google.com>
Sat, 13 May 2017 00:31:13 +0000 (17:31 -0700)
committerAlan Leung <acleung@google.com>
Wed, 7 Jun 2017 21:22:22 +0000 (14:22 -0700)
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

tools/locked_region_code_injection/Android.mk [new file with mode: 0644]
tools/locked_region_code_injection/manifest.txt [new file with mode: 0644]
tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java [new file with mode: 0644]
tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java [new file with mode: 0644]
tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java [new file with mode: 0644]
tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java [new file with mode: 0644]
tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java [new file with mode: 0644]
tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java [new file with mode: 0644]
tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java [new file with mode: 0644]
tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java [new file with mode: 0644]

diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
new file mode 100644 (file)
index 0000000..0aed0ce
--- /dev/null
@@ -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 (file)
index 0000000..4b9de00
--- /dev/null
@@ -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 (file)
index 0000000..9374f23
--- /dev/null
@@ -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 (file)
index 0000000..c5e59e3
--- /dev/null
@@ -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 (file)
index 0000000..99d8418
--- /dev/null
@@ -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 (file)
index 0000000..1002c88
--- /dev/null
@@ -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 (file)
index 0000000..edb9a49
--- /dev/null
@@ -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 (file)
index 0000000..d2a2e7b
--- /dev/null
@@ -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 (file)
index 0000000..1d4f2d4
--- /dev/null
@@ -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&#47;*&#47;*.java -d out/
+ * pushd out
+ * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main *&#47;*.class
+ * popd
+ *
+ * # Make unit tests.
+ * javac -cp lib/junit-4.12.jar test&#47;*&#47;*.java -d out/
+ *
+ * pushd out
+ * jar cfe test_input.jar lockedregioncodeinjection.Test *&#47;*.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 (file)
index 0000000..8e7d478
--- /dev/null
@@ -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;
+  }
+}