org.pitest.mutationtest.engine.gregor.TryWithResourcesMethodVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.pitest.mutationtest.engine.gregor.TryWithResourcesMethodVisitor.java

Source

/*
 * Copyright 2014 Artem Khvastunov
 *
 * 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.pitest.mutationtest.engine.gregor;

import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.IFNONNULL;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.IF_ACMPEQ;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * Example of code that was generated by 1.7 compiler for try-with-resources block:
 * <pre><code>
 * } finally {
 *   if (closeable != null) { // IFNULL
 *     if (localThrowable2 != null) { // IFNULL
 *       try {
 *         closeable.close(); // INVOKEVIRTUAL or INVOKEINTERFACE
 *       } catch (Throwable x2) {
 *         localThrowable2.addSuppressed(x2); // INVOKEVIRTUAL
 *       }
 *     } else {
 *       closeable.close(); // INVOKEVIRTUAL or INVOKEINTERFACE
 *     }
 *   }
 * } // ATHROW
 * </code></pre>
 * This class considers that only auto generated code may have such sequence without any line change.
 * Such an approach make sense only for <strong>javac compiler</strong>.
 * <p>
 * <strong>Eclipse Java Compiler</strong> as well as <strong>aspectj</strong> have its own opinion
 * how to compile try-with-resources block:
 * <pre><code>
 * } finally {
 *   if (throwable1 == null) { // IFNONNULL
 *     throwable1 = throwable2;
 *   } else {
 *     if (throwable1 != throwable2) { // IF_ACMPEQ
 *       throwable1.addSuppressed(throwable2); // INVOKEVIRTUAL
 *     }
 *   }
 * } // ATHROW
 * </code></pre>
 *
 * @author Artem Khvastunov &lt;contact@artspb.me&gt;
 */
class TryWithResourcesMethodVisitor extends MethodVisitor {

    private static final List<Integer> JAVAC_CLASS_INS_SEQUENCE = Arrays.asList(ASTORE, // store throwable
            ALOAD, IFNULL, // closeable != null
            ALOAD, IFNULL, // localThrowable2 != null
            ALOAD, INVOKEVIRTUAL, GOTO, // closeable.close()
            ASTORE, // Throwable x2
            ALOAD, ALOAD, INVOKEVIRTUAL, GOTO, // localThrowable2.addSuppressed(x2)
            ALOAD, INVOKEVIRTUAL, // closeable.close()
            ALOAD, ATHROW); // throw throwable

    private static final List<Integer> JAVAC_INTERFACE_INS_SEQUENCE = Arrays.asList(ASTORE, // store throwable
            ALOAD, IFNULL, // closeable != null
            ALOAD, IFNULL, // localThrowable2 != null
            ALOAD, INVOKEINTERFACE, GOTO, // closeable.close()
            ASTORE, // Throwable x2
            ALOAD, ALOAD, INVOKEVIRTUAL, GOTO, // localThrowable2.addSuppressed(x2)
            ALOAD, INVOKEINTERFACE, // closeable.close()
            ALOAD, ATHROW); // throw throwable

    private static final List<Integer> ECJ_INS_SEQUENCE = Arrays.asList(ASTORE, // store throwable2
            ALOAD, IFNONNULL, // if (throwable1 == null)
            ALOAD, ASTORE, GOTO, // throwable1 = throwable2;
            ALOAD, ALOAD, IF_ACMPEQ, // if (throwable1 != throwable2) {
            ALOAD, ALOAD, INVOKEVIRTUAL, // throwable1.addSuppressed(throwable2)
            ALOAD, ATHROW); // throw throwable1

    private final PremutationClassInfo context;

    private final List<Integer> opcodesStack = new ArrayList<Integer>();
    private int currentLineNumber;

    /**
     * @param context
     *          to store detected line numbers
     */
    public TryWithResourcesMethodVisitor(final PremutationClassInfo context) {
        super(Opcodes.ASM5);
        this.context = context;
    }

    @Override
    public void visitLineNumber(int line, Label start) {
        prepareToStartTracking();
        currentLineNumber = line;
        super.visitLineNumber(line, start);
    }

    @Override
    public void visitVarInsn(int opcode, int var) {
        opcodesStack.add(opcode);
        super.visitVarInsn(opcode, var);
    }

    @Override
    public void visitJumpInsn(int opcode, Label label) {
        opcodesStack.add(opcode);
        super.visitJumpInsn(opcode, label);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
        opcodesStack.add(opcode);
        super.visitMethodInsn(opcode, owner, name, desc, itf);
    }

    @Override
    public void visitInsn(int opcode) {
        if (opcode == Opcodes.ATHROW) {
            opcodesStack.add(opcode);
            finishTracking();
        }
        super.visitInsn(opcode);
    }

    private void finishTracking() {
        if (JAVAC_CLASS_INS_SEQUENCE.equals(opcodesStack) || JAVAC_INTERFACE_INS_SEQUENCE.equals(opcodesStack)
                || ECJ_INS_SEQUENCE.equals(opcodesStack)) {
            context.registerLineToAvoid(currentLineNumber);
        }
        prepareToStartTracking();
    }

    private void prepareToStartTracking() {
        if (!opcodesStack.isEmpty()) {
            opcodesStack.clear();
        }
    }
}