org.jtsan.MethodTransformer.java Source code

Java tutorial

Introduction

Here is the source code for org.jtsan.MethodTransformer.java

Source

/* Copyright (c) 2010 Google Inc.
 *
 * 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 org.jtsan;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.LocalVariablesSorter;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Transforms all method bodies.
 *
 * @author Egor Pasko
 */
public class MethodTransformer extends AdviceAdapter {
    private final Agent agent;
    private final String fullName;
    private final Label startFinally = new Label();
    private final MethodMapping methods;
    private final String srcFile;
    private final String methodName;
    private final String className;
    private final CodePos codePos;
    private final DescrCallback lazyDescr;
    private final Set<String> volatileFields;
    private final boolean methodIsStatic;
    private final List<ExceptionTableEntry> exceptionTableTop, exceptionTableBottom;

    private static class ExceptionTableEntry {
        private final Label start;
        private final Label end;
        private final Label target;
        private final String type;

        public Label getStart() {
            return start;
        }

        public Label getEnd() {
            return end;
        }

        public Label getTarget() {
            return target;
        }

        public String getType() {
            return type;
        }

        ExceptionTableEntry(Label start, Label end, Label target, String type) {
            this.start = start;
            this.end = end;
            this.target = target;
            this.type = type;
        }
    }

    private static final int[] storeOpcodes = { IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE,
            SASTORE };

    private static final int[] loadOpcodes = { IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD };

    private LocalVariablesSorter localVarsSorter;

    private int line;

    public MethodTransformer(Agent agent, MethodVisitor mv, int acc, String name, String fullName, String desc,
            String src, String className, MethodMapping methods, CodePos codePos, Set<String> volatileFields) {
        super(mv, acc, name, desc);
        this.agent = agent;
        this.fullName = fullName;
        this.methods = methods;
        this.srcFile = src;
        this.methodName = name;
        this.className = className;
        this.codePos = codePos;
        this.volatileFields = volatileFields;
        this.methodIsStatic = ((acc & Opcodes.ACC_STATIC) != 0);
        lazyDescr = new DescrCallback();
        exceptionTableTop = new ArrayList<ExceptionTableEntry>();
        exceptionTableBottom = new ArrayList<ExceptionTableEntry>();
    }

    public void setLocalVarsSorter(LocalVariablesSorter lvs) {
        localVarsSorter = lvs;
    }

    private static boolean isArrayStore(int opcode) {
        return contains(storeOpcodes, opcode);
    }

    private static boolean isArrayLoad(int opcode) {
        return contains(loadOpcodes, opcode);
    }

    private static boolean contains(int[] list, int value) {
        for (int i = 0; i < list.length; i++) {
            if (list[i] == value) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected void onMethodEnter() {
        // TODO: this is a workaround to having not all <init>s in onMethodEnter(), needs fixing.
        if (!methodName.equals("<init>")) {
            push(codePos.incMethodEnterPC());
            captureMethodEnter();
        }
        if ((methodAccess & Opcodes.ACC_SYNCHRONIZED) != 0) {
            if (methodIsStatic) {
                Type classType = Type.getType(className);
                visitLdcInsn(classType);
            } else {
                loadThis();
            }
            push(codePos.incMethodEnterPC());
            captureMonitorEnter();
        }
    }

    @Override
    protected void onMethodExit(int opcode) {
        if (opcode != ATHROW) {
            onFinally();
        }
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        for (ExceptionTableEntry t : exceptionTableTop) {
            mv.visitTryCatchBlock(t.getStart(), t.getEnd(), t.getTarget(), t.getType());
        }
        for (ExceptionTableEntry t : exceptionTableBottom) {
            mv.visitTryCatchBlock(t.getStart(), t.getEnd(), t.getTarget(), t.getType());
        }
        Label endFinally = new Label();
        mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null);
        mv.visitLabel(endFinally);
        onFinally();
        mv.visitInsn(ATHROW);
        mv.visitMaxs(maxStack + 3, maxLocals);
    }

    @Override
    public void visitInsn(final int opcode) {
        if (opcode == MONITORENTER) {
            dup();
            super.visitInsn(opcode);
            push(genCodePosition());
            captureMonitorEnter();
            return;
        } else if (opcode == MONITOREXIT) {
            dup();
            push(genCodePosition());
            captureMonitorExit();
        } else if (isArrayStore(opcode)) {
            captureArrayStore(opcode);
        } else if (isArrayLoad(opcode)) {
            captureArrayLoad(opcode);
        }
        super.visitInsn(opcode);
    }

    @Override
    public void visitCode() {
        super.visitCode();
        mv.visitLabel(startFinally);
    }

    @Override
    public void visitLineNumber(int line, Label start) {
        super.visitLineNumber(line, start);
        this.line = line;
        codePos.line(line, lazyDescr);
    }

    private void onFinally() {
        if ((methodAccess & Opcodes.ACC_SYNCHRONIZED) != 0) {
            if (methodIsStatic) {
                Type classType = Type.getType(className);
                visitLdcInsn(classType);
            } else {
                loadThis();
            }
            push(genCodePosition());
            captureMonitorExit();
        }
        // TODO: this is a workaround to having not all <init>s in onMethodEnter(), needs fixing.
        if (!methodName.equals("<init>")) {
            push(genCodePosition());
            captureMethodExit();
        }
    }

    @Override
    public void visitTryCatchBlock(Label start, Label end, Label target, String type) {
        exceptionTableBottom.add(new ExceptionTableEntry(start, end, target, type));
    }

    private void topVisitTryCatchBlock(Label start, Label end, Label target, String type) {
        exceptionTableTop.add(new ExceptionTableEntry(start, end, target, type));
    }

    class DescrCallback {
        public String getDescr() {
            return fullName + " " + srcFile + " " + line;
        }
    }

    private long genCodePosition() {
        return codePos.incPC(lazyDescr);
    }

    private boolean isVolatileField(String fname) {
        return volatileFields.contains(fname);
    }

    private void visitObjectFieldAccess(String name, String desc, boolean isWrite, boolean isVolatile) {
        long pc = genCodePosition();
        LocalVarsSaver saver = new LocalVarsSaver(mv, localVarsSorter);
        if (isWrite) {
            saver.initFromTypeDesc(desc);
            saver.saveStack();
            dup();
            push(1);
        } else {
            dup();
            push(0);
        }
        push(name);
        push(pc);
        push(isVolatile);
        visitObjectFieldAccessCall();
        if (isWrite) {
            saver.loadStack();
        }
    }

    private void visitObjectFieldAccessCall() {
        visitListenerCall("objectFieldAccess", "(Ljava/lang/Object;ZLjava/lang/String;JZ)V");
    }

    private void visitStaticFieldAccess(String owner, String field, boolean isWrite) {
        // We pass the full name of the field access to the event listener. In presence of many
        // classloaders some distinct fields of non-related classes may appear to have the same name.
        // This may result in some false positives, but very unlikely, since classes from different
        // classloaders barely share stuff.
        push(owner);
        push(field);
        push(isWrite);
        push(genCodePosition());
        push(isVolatileField(fullName));
        visitListenerCall("staticFieldAccess", "(Ljava/lang/String;Ljava/lang/String;ZJZ)V");
    }

    @Override
    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
        String param = "";
        boolean isStatic = false;
        boolean isWrite = false;
        if (opcode == GETSTATIC) {
            isStatic = true;
            isWrite = false;
        } else if (opcode == PUTSTATIC) {
            isStatic = true;
            isWrite = true;
        } else if (opcode == GETFIELD) {
            isStatic = false;
            isWrite = false;
        } else if (opcode == PUTFIELD) {
            isStatic = false;
            isWrite = true;
        }
        if (!"<init>".equals(methodName) && !"<clinit>".equals(methodName)) {
            // The method <init> may save values to fields of an uninitialized object.
            // We cannot pass an 'ininitialized this' to an interceptor without
            // causing a VerifyError. 'Uninitialized this' can be detected using
            // StackAnalyzer with precomputed stack frame info by ClassWriter. Skip
            // this process for simplicity.
            //
            // The method <clinit> may save values to static fields of a class,
            // but JLS guarantees correctness. 
            if (isStatic) {
                visitStaticFieldAccess(owner, name, isWrite);
            } else {
                if (!methods.isBenignRaceField(owner, name)) {
                    visitObjectFieldAccess(name, desc, isWrite, isVolatileField(owner + "." + name));
                }
            }
        }
        super.visitFieldInsn(opcode, owner, name, desc);
    }

    private void captureMonitorExit() {
        visitListenerCall("monitorExit", "(Ljava/lang/Object;J)V");
    }

    private void captureMonitorEnter() {
        visitListenerCall("monitorEnter", "(Ljava/lang/Object;J)V");
    }

    private void captureMethodEnter() {
        if (methodName.equals("run") && !methodIsStatic) {
            loadThis();
            push(genCodePosition());
            visitListenerCall("runMethodEnter", "(Ljava/lang/Object;J)V");
        }
        visitListenerCall("methodEnter", "(J)V");
    }

    private void captureMethodExit() {
        visitListenerCall("methodExit", "(J)V");
        if (methodName.equals("run") && !methodIsStatic) {
            loadThis();
            push(genCodePosition());
            visitListenerCall("runMethodExit", "(Ljava/lang/Object;J)V");
        }
    }

    private void captureArrayLoad(int opcode) {
        int indexVar = localVarsSorter.newLocal(Type.INT_TYPE);
        mv.visitVarInsn(ISTORE, indexVar);
        dup();
        mv.visitVarInsn(ILOAD, indexVar);
        push(0);
        push(genCodePosition());
        visitListenerCall("arrayAccess", "(Ljava/lang/Object;IZJ)V");
        mv.visitVarInsn(ILOAD, indexVar);
    }

    private void captureArrayStore(int opcode) {
        Type slotType = getSourceSlotType(opcode);

        int valueVar = localVarsSorter.newLocal(slotType);
        int indexVar = localVarsSorter.newLocal(Type.INT_TYPE);

        mv.visitVarInsn(slotType.getOpcode(ISTORE), valueVar);
        mv.visitVarInsn(ISTORE, indexVar);
        dup();
        mv.visitVarInsn(ILOAD, indexVar);
        push(1);
        push(genCodePosition());
        visitListenerCall("arrayAccess", "(Ljava/lang/Object;IZJ)V");
        mv.visitVarInsn(ILOAD, indexVar);
        mv.visitVarInsn(slotType.getOpcode(ILOAD), valueVar);
    }

    /**
     * A simple generation interface for using in {@code InstrumentCalls}.
     */
    public class GenerationCallback {
        private final int opcode;
        private final String owner;
        private final String name;
        private final String desc;

        public GenerationCallback(int opcode, String owner, String name, String desc) {
            this.opcode = opcode;
            this.owner = owner;
            this.name = name;
            this.desc = desc;
        }

        public LocalVarsSaver createLocalVarsSaver() {
            LocalVarsSaver saver = new LocalVarsSaver(mv, localVarsSorter);
            saver.initFromMethodDesc(desc);
            return saver;
        }

        public LocalVarsSaver createObjSaver() {
            LocalVarsSaver saver = new LocalVarsSaver(mv, localVarsSorter);
            saver.initFromTypeDesc("Ljava/lang/Object;");
            return saver;
        }

        public void visitMethodInsn() {
            superVisitMethodInsn(opcode, owner, name, desc);
        }

        public long codePosition() {
            return genCodePosition();
        }

        public void listenerCall(String meth, String listenerDesc) {
            visitListenerCall(meth, listenerDesc);
        }

        public void topVisitTryCatchBlock(Label start, Label end, Label target, String type) {
            MethodTransformer.this.topVisitTryCatchBlock(start, end, target, type);
        }

        public String getMethodName() {
            return name;
        }
    }

    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
        // Capture code position on the call.
        push(genCodePosition());
        visitListenerCall("beforeCall", "(J)V");

        // Capture special (=registered) calls with their parameters.
        InstrumentCalls callsGen = new InstrumentCalls(new GenerationCallback(opcode, owner, name, desc), this,
                opcode, owner, desc);
        boolean isStatic = (opcode == Opcodes.INVOKESTATIC);
        callsGen.setBeforeTargets(methods.getTargetsFor(name + desc,
                isStatic ? MethodMapping.E_BEFORE_STATIC_METHOD : MethodMapping.E_BEFORE_METHOD));
        callsGen.setAfterTargets(methods.getTargetsFor(name + desc,
                isStatic ? MethodMapping.E_AFTER_STATIC_METHOD : MethodMapping.E_AFTER_METHOD));
        callsGen.setExceptionTargets(methods.getTargetsFor(name + desc,
                isStatic ? MethodMapping.E_STATIC_EXCEPTION : MethodMapping.E_EXCEPTION));
        callsGen.generateCall();

        // Capture code position after the call.
        push(genCodePosition());
        visitListenerCall("afterCall", "(J)V");
    }

    public void superVisitMethodInsn(int opcode, String owner, String name, String desc) {
        super.visitMethodInsn(opcode, owner, name, desc);
    }

    private void visitListenerCall(String method, String descr) {
        mv.visitMethodInsn(INVOKESTATIC, "org/jtsan/EventListener", method, descr);
    }

    private static Type getSourceSlotType(int opcode) {
        switch (opcode) {
        case IASTORE:
            return Type.INT_TYPE;
        case LASTORE:
            return Type.LONG_TYPE;
        case FASTORE:
            return Type.FLOAT_TYPE;
        case DASTORE:
            return Type.DOUBLE_TYPE;
        case AASTORE:
            return Type.getType(Object.class);
        case BASTORE:
            return Type.INT_TYPE;
        case CASTORE:
            return Type.INT_TYPE;
        case SASTORE:
            return Type.INT_TYPE;
        default:
            throw new Error("unsupported array opcode: " + opcode);
        }
    }
}