Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a2170a3d authored by Lee Shombert's avatar Lee Shombert Committed by Gerrit Code Review
Browse files

Merge "Add a scoped lock injection tool"

parents 6846c495 00394fc0
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -19,3 +19,20 @@ java_binary_host {
        "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",
    ],
}
+176 −20
Original line number Diff line number Diff line
@@ -13,37 +13,51 @@
 */
package lockedregioncodeinjection;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

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.InsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;

import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.ArrayList;
import java.util.Arrays;
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.
 *
 * 2. Find all methods that are synchronized and insert pre method calls in the beginning and post
 * 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 {
    private String className = null;
@@ -73,12 +87,16 @@ class LockFindingClassVisitor extends ClassVisitor {
    class LockFindingMethodVisitor extends MethodVisitor {
        private String owner;
        private MethodVisitor chain;
        private final String className;
        private final String methodName;

        public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
            super(Utils.ASM_VERSION, mn);
            assert owner != null;
            this.owner = owner;
            this.chain = chain;
            className = owner;
            methodName = mn.name;
        }

        @SuppressWarnings("unchecked")
@@ -93,6 +111,12 @@ class LockFindingClassVisitor extends ClassVisitor {
                for (LockTarget t : targets) {
                    if (t.getTargetDesc().equals("L" + owner + ";")) {
                        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();
                MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
                        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++) {
                AbstractInsnNode s = instructions.get(i);

@@ -131,9 +157,15 @@ class LockFindingClassVisitor extends ClassVisitor {
                        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);
                            MethodInsnNode call = methodCall(target, true);
                            if (target.getScoped()) {
                                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) {
                        LockTargetState state = (LockTargetState) operand;
                        for (int j = 0; j < state.getTargets().size(); j++) {
                            // The instruction after a monitor_exit should be a label for the end of the implicit
                            // catch block that surrounds the synchronized block to call monitor_exit when an exception
                            // The instruction after a monitor_exit should be a label for
                            // the end of the implicit catch block that surrounds the
                            // synchronized block to call monitor_exit when an exception
                            // occurs.
                            checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL,
                                "Expected to find label after monitor exit");
@@ -161,9 +194,16 @@ class LockFindingClassVisitor extends ClassVisitor {
                                "Expected label to be the end of monitor exit's try block");

                            LockTarget target = state.getTargets().get(j);
                            MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    target.getPostOwner(), target.getPostMethod(), "()V", false);
                            insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call);
                            MethodInsnNode call = methodCall(target, false);
                            if (target.getScoped()) {
                                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 =
                            new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(),
                                    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.
                }
            }

            if (anyDup) {
                mn.maxStack++;
            }

            super.visitEnd();
            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));

    public static void insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
            return 3;
        }

        // 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,
            MethodInsnNode call) {
        List<TryCatchBlockNode> handlers = handlersMap.get(index);
@@ -226,6 +366,22 @@ class LockFindingClassVisitor extends ClassVisitor {
        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")
    public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers,
            LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) {
+34 −3
Original line number Diff line number Diff line
@@ -21,14 +21,28 @@ package lockedregioncodeinjection;
public class LockTarget {
    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;
    // 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 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.pre = pre;
        this.post = post;
        this.scoped = scoped;
    }

    public LockTarget(String targetDesc, String pre, String post) {
        this(targetDesc, pre, post, false);
    }

    public String getTargetDesc() {
@@ -40,8 +54,12 @@ public class LockTarget {
    }

    public String getPreOwner() {
        if (scoped) {
            return targetDesc.substring(1, targetDesc.length() - 1);
        } else {
            return pre.substring(0, pre.lastIndexOf('.'));
        }
    }

    public String getPreMethod() {
        return pre.substring(pre.lastIndexOf('.') + 1);
@@ -52,10 +70,23 @@ public class LockTarget {
    }

    public String getPostOwner() {
        if (scoped) {
            return targetDesc.substring(1, targetDesc.length() - 1);
        } else {
            return post.substring(0, post.lastIndexOf('.'));
        }
    }

    public String getPostMethod() {
        return post.substring(post.lastIndexOf('.') + 1);
    }

    public boolean getScoped() {
        return scoped;
    }

    @Override
    public String toString() {
        return targetDesc + ":" + pre + ":" + post;
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -13,10 +13,11 @@
 */
package lockedregioncodeinjection;

import java.util.List;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.analysis.BasicValue;

import java.util.List;

public class LockTargetState extends BasicValue {
    private final List<LockTarget> lockTargets;

@@ -31,4 +32,10 @@ public class LockTargetState extends BasicValue {
    public List<LockTarget> getTargets() {
        return lockTargets;
    }

    @Override
    public String toString() {
        return "LockTargetState(" + getType().getDescriptor()
                + ", " + lockTargets.size() + ")";
    }
}
+13 −10
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
@@ -36,6 +36,7 @@ public class Main {
        String legacyTargets = null;
        String legacyPreMethods = null;
        String legacyPostMethods = null;
        List<LockTarget> targets = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            if ("-i".equals(args[i].trim())) {
                i++;
@@ -52,23 +53,25 @@ public class Main {
            } else if ("--post".equals(args[i].trim())) {
                i++;
                legacyPostMethods = args[i].trim();
            } else if ("--scoped".equals(args[i].trim())) {
                i++;
                targets.add(Utils.getScopedTarget(args[i].trim()));
            }

        }

        // TODO(acleung): Better help message than asserts.
        assert inJar != null;
        assert outJar != null;
        if (inJar == null) {
            throw new RuntimeException("missing input jar path");
        }
        if (outJar == null) {
            throw new RuntimeException("missing output jar path");
        }
        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();
            targets.addAll(Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
                                                                legacyPostMethods));
        }

        Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries();
Loading