diff options
Diffstat (limited to 'org.jacoco.core/src/org/jacoco/core/internal/analysis')
-rw-r--r-- | org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java | 2 | ||||
-rw-r--r-- | org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/FinallyFilter.java | 227 |
2 files changed, 228 insertions, 1 deletions
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java index 4b2a64a9..8696151a 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java @@ -29,7 +29,7 @@ public final class Filters implements IFilter { public static final IFilter ALL = new Filters(new EnumFilter(), new SyntheticFilter(), new SynchronizedFilter(), new TryWithResourcesJavacFilter(), new TryWithResourcesEcjFilter(), - new PrivateEmptyNoArgConstructorFilter(), + new FinallyFilter(), new PrivateEmptyNoArgConstructorFilter(), new StringSwitchJavacFilter(), new LombokGeneratedFilter(), new GroovyGeneratedFilter()); diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/FinallyFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/FinallyFilter.java new file mode 100644 index 00000000..0ab6ca2f --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/FinallyFilter.java @@ -0,0 +1,227 @@ +/******************************************************************************* + * Copyright (c) 2009, 2017 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; + } + +} |