| /******************************************************************************* |
| * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Evgeny Mandrikov - initial API and implementation |
| * |
| *******************************************************************************/ |
| package org.jacoco.core.internal.analysis.filter; |
| |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.tree.AbstractInsnNode; |
| import org.objectweb.asm.tree.JumpInsnNode; |
| import org.objectweb.asm.tree.MethodNode; |
| import org.objectweb.asm.tree.TryCatchBlockNode; |
| import org.objectweb.asm.tree.VarInsnNode; |
| |
| /** |
| * Filters duplicates of finally blocks that compiler generates. |
| * |
| * To understand algorithm of filtering, consider following example: |
| * |
| * <pre> |
| * try { |
| * if (x) { |
| * a(); |
| * return; // 1 |
| * } |
| * b(); // 2 |
| * } catch (Exception e) { |
| * c(); // 3 |
| * } finally { |
| * d(); // 4 |
| * } |
| * </pre> |
| * |
| * There are 4 <b>distinct</b> points of exit out of these "try/catch/finally" |
| * blocks - three without exception, and one with Throwable if it is thrown |
| * prior to reaching first three points of exit. |
| * |
| * "finally" block must be executed just before these points, so there must be 4 |
| * copies of its bytecode instructions. |
| * |
| * One of them handles Throwable ("catch-any") and must cover all instructions |
| * of "try/catch" blocks. But must not cover instructions of other duplicates, |
| * because instructions of "finally" block also can cause Throwable to be |
| * thrown. |
| * |
| * Therefore there will be multiple {@link MethodNode#tryCatchBlocks} with |
| * {@link TryCatchBlockNode#type} null with same |
| * {@link TryCatchBlockNode#handler} for different non-intersecting bytecode |
| * regions ({@link TryCatchBlockNode#start}, {@link TryCatchBlockNode#end}). |
| * |
| * And each exit out of these regions, except one that handles Throwable, will |
| * contain duplicate of "finally" block. |
| * |
| * To determine exits out of these regions, they all must be processed together |
| * at once, because execution can branch from one region to another (like it is |
| * in given example due to "if" statement). |
| */ |
| public final class FinallyFilter implements IFilter { |
| |
| public void filter(final String className, final String superClassName, |
| final MethodNode methodNode, final IFilterOutput output) { |
| for (final TryCatchBlockNode tryCatchBlock : methodNode.tryCatchBlocks) { |
| if (tryCatchBlock.type == null) { |
| filter(output, methodNode.tryCatchBlocks, tryCatchBlock); |
| } |
| } |
| } |
| |
| private static void filter(final IFilterOutput output, |
| final List<TryCatchBlockNode> tryCatchBlocks, |
| final TryCatchBlockNode catchAnyBlock) { |
| final AbstractInsnNode e = next(catchAnyBlock.handler); |
| final int size = size(e); |
| if (size <= 0) { |
| return; |
| } |
| |
| // Determine instructions inside regions |
| final Set<AbstractInsnNode> inside = new HashSet<AbstractInsnNode>(); |
| for (final TryCatchBlockNode t : tryCatchBlocks) { |
| if (t.handler == catchAnyBlock.handler) { |
| AbstractInsnNode i = t.start; |
| while (i != t.end) { |
| inside.add(i); |
| i = i.getNext(); |
| } |
| } |
| } |
| |
| // Find and merge duplicates at exits of regions |
| for (final TryCatchBlockNode t : tryCatchBlocks) { |
| if (t.handler == catchAnyBlock.handler) { |
| boolean continues = false; |
| AbstractInsnNode i = t.start; |
| |
| while (i != t.end) { |
| switch (i.getType()) { |
| case AbstractInsnNode.FRAME: |
| case AbstractInsnNode.LINE: |
| case AbstractInsnNode.LABEL: |
| break; |
| case AbstractInsnNode.JUMP_INSN: |
| final AbstractInsnNode jumpTarget = next( |
| ((JumpInsnNode) i).label); |
| if (!inside.contains(jumpTarget)) { |
| merge(output, size, e, jumpTarget); |
| } |
| continues = i.getOpcode() != Opcodes.GOTO; |
| break; |
| default: |
| switch (i.getOpcode()) { |
| case Opcodes.IRETURN: |
| case Opcodes.LRETURN: |
| case Opcodes.FRETURN: |
| case Opcodes.DRETURN: |
| case Opcodes.ARETURN: |
| case Opcodes.RETURN: |
| case Opcodes.ATHROW: |
| continues = false; |
| break; |
| default: |
| continues = true; |
| break; |
| } |
| break; |
| } |
| i = i.getNext(); |
| } |
| |
| i = next(i); |
| if (continues && !inside.contains(i)) { |
| merge(output, size, e, i); |
| } |
| } |
| |
| if (t != catchAnyBlock && t.start == catchAnyBlock.start |
| && t.end == catchAnyBlock.end) { |
| final AbstractInsnNode i = next(next(t.handler)); |
| if (!inside.contains(i)) { |
| // javac's empty catch - merge after ASTORE |
| merge(output, size, e, i); |
| } |
| } |
| } |
| } |
| |
| private static void merge(final IFilterOutput output, final int size, |
| AbstractInsnNode e, AbstractInsnNode n) { |
| if (!isSame(size, e, n)) { |
| return; |
| } |
| output.ignore(e, e); |
| e = next(e); |
| for (int i = 0; i < size; i++) { |
| output.merge(e, n); |
| e = next(e); |
| n = next(n); |
| } |
| output.ignore(e, next(e)); |
| |
| if (n != null && n.getOpcode() == Opcodes.GOTO) { |
| // goto instructions at the end of non-executed duplicates |
| // cause partial coverage of last line of finally block, |
| // so should be ignored |
| output.ignore(n, n); |
| } |
| } |
| |
| private static boolean isSame(final int size, AbstractInsnNode e, |
| AbstractInsnNode n) { |
| e = next(e); |
| for (int i = 0; i < size; i++) { |
| if (n == null || e.getOpcode() != n.getOpcode()) { |
| return false; |
| } |
| e = next(e); |
| n = next(n); |
| } |
| return true; |
| } |
| |
| /** |
| * @return number of instructions inside given "catch-any" handler |
| */ |
| private static int size(AbstractInsnNode i) { |
| if (Opcodes.ASTORE != i.getOpcode()) { |
| // when always completes abruptly |
| return 0; |
| } |
| final int var = ((VarInsnNode) i).var; |
| int size = -1; |
| do { |
| size++; |
| i = next(i); |
| if (i == null) { |
| // when always completes abruptly |
| return 0; |
| } |
| } while (!(Opcodes.ALOAD == i.getOpcode() |
| && var == ((VarInsnNode) i).var)); |
| i = next(i); |
| if (Opcodes.ATHROW != i.getOpcode()) { |
| return 0; |
| } |
| return size; |
| } |
| |
| private static AbstractInsnNode next(AbstractInsnNode i) { |
| do { |
| i = i.getNext(); |
| } while (i != null && (AbstractInsnNode.FRAME == i.getType() |
| || AbstractInsnNode.LABEL == i.getType() |
| || AbstractInsnNode.LINE == i.getType())); |
| return i; |
| } |
| |
| } |