/*
* 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.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
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 <contact@artspb.me>
*/
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
*/
TryWithResourcesMethodVisitor(final PremutationClassInfo context) {
super(Opcodes.ASM5);
this.context = context;
}
@Override
public void visitLineNumber(int line, Label start) {
prepareToStartTracking();
this.currentLineNumber = line;
super.visitLineNumber(line, start);
}
@Override
public void visitVarInsn(int opcode, int var) {
this.opcodesStack.add(opcode);
super.visitVarInsn(opcode, var);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
this.opcodesStack.add(opcode);
super.visitJumpInsn(opcode, label);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
this.opcodesStack.add(opcode);
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.ATHROW) {
this.opcodesStack.add(opcode);
finishTracking();
}
super.visitInsn(opcode);
}
private void finishTracking() {
if (JAVAC_CLASS_INS_SEQUENCE.equals(this.opcodesStack)
|| JAVAC_INTERFACE_INS_SEQUENCE.equals(this.opcodesStack)
|| ECJ_INS_SEQUENCE.equals(this.opcodesStack)) {
this.context.registerLineToAvoid(this.currentLineNumber);
}
prepareToStartTracking();
}
private void prepareToStartTracking() {
if (!this.opcodesStack.isEmpty()) {
this.opcodesStack.clear();
}
}
}