/******************************************************************************* * 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.HashMap; import java.util.Map; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TryCatchBlockNode; /** * Filters code that ECJ generates for try-with-resources statement. */ public final class TryWithResourcesEcjFilter implements IFilter { public void filter(final String className, final String superClassName, final MethodNode methodNode, final IFilterOutput output) { if (methodNode.tryCatchBlocks.isEmpty()) { return; } final Matcher matcher = new Matcher(output); for (TryCatchBlockNode t : methodNode.tryCatchBlocks) { if (t.type == null) { matcher.start(t.handler); if (!matcher.matchEcj()) { matcher.start(t.handler); matcher.matchEcjNoFlowOut(); } } } } static class Matcher extends AbstractMatcher { private final IFilterOutput output; private final Map owners = new HashMap(); private final Map labels = new HashMap(); private AbstractInsnNode start; Matcher(final IFilterOutput output) { this.output = output; } private void start(final AbstractInsnNode start) { this.start = start; cursor = start.getPrevious(); vars.clear(); labels.clear(); owners.clear(); } private boolean matchEcj() { // "catch (any primaryExc)" nextIsVar(Opcodes.ASTORE, "primaryExc"); nextIsEcjCloseAndThrow("r0"); AbstractInsnNode c; int resources = 1; String r = "r" + resources; c = cursor; while (nextIsEcjClose(r)) { nextIsJump(Opcodes.GOTO, r + ".end"); nextIsEcjSuppress(r); nextIsEcjCloseAndThrow(r); resources++; r = "r" + resources; c = cursor; } cursor = c; nextIsEcjSuppress("last"); // "throw primaryExc" nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIs(Opcodes.ATHROW); if (cursor == null) { return false; } final AbstractInsnNode end = cursor; AbstractInsnNode startOnNonExceptionalPath = start.getPrevious(); cursor = startOnNonExceptionalPath; while (!nextIsEcjClose("r0")) { startOnNonExceptionalPath = startOnNonExceptionalPath .getPrevious(); cursor = startOnNonExceptionalPath; if (cursor == null) { return false; } } startOnNonExceptionalPath = startOnNonExceptionalPath.getNext(); next(); if (cursor == null || cursor.getOpcode() != Opcodes.GOTO) { return false; } output.ignore(startOnNonExceptionalPath, cursor); output.ignore(start, end); return true; } private boolean matchEcjNoFlowOut() { // "catch (any primaryExc)" nextIsVar(Opcodes.ASTORE, "primaryExc"); AbstractInsnNode c; int resources = 0; String r = "r" + resources; c = cursor; while (nextIsEcjCloseAndThrow(r) && nextIsEcjSuppress(r)) { resources++; r = "r" + resources; c = cursor; } cursor = c; // "throw primaryExc" nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIs(Opcodes.ATHROW); if (cursor == null) { return false; } final AbstractInsnNode end = cursor; AbstractInsnNode startOnNonExceptionalPath = start.getPrevious(); cursor = startOnNonExceptionalPath; while (!nextIsEcjClose("r0")) { startOnNonExceptionalPath = startOnNonExceptionalPath .getPrevious(); cursor = startOnNonExceptionalPath; if (cursor == null) { return false; } } startOnNonExceptionalPath = startOnNonExceptionalPath.getNext(); for (int i = 1; i < resources; i++) { if (!nextIsEcjClose("r" + i)) { return false; } } output.ignore(startOnNonExceptionalPath, cursor); output.ignore(start, end); return true; } private boolean nextIsEcjClose(final String name) { nextIsVar(Opcodes.ALOAD, name); // "if (r != null)" nextIsJump(Opcodes.IFNULL, name + ".end"); // "r.close()" nextIsClose(name); return cursor != null; } private boolean nextIsEcjCloseAndThrow(final String name) { nextIsVar(Opcodes.ALOAD, name); // "if (r != null)" nextIsJump(Opcodes.IFNULL, name); // "r.close()" nextIsClose(name); nextIsLabel(name); nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIs(Opcodes.ATHROW); return cursor != null; } private boolean nextIsEcjSuppress(final String name) { final String suppressedExc = name + ".t"; final String startLabel = name + ".suppressStart"; final String endLabel = name + ".suppressEnd"; nextIsVar(Opcodes.ASTORE, suppressedExc); // "suppressedExc = t" // "if (primaryExc != null)" nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIsJump(Opcodes.IFNONNULL, startLabel); // "primaryExc = suppressedExc" nextIsVar(Opcodes.ALOAD, suppressedExc); nextIsVar(Opcodes.ASTORE, "primaryExc"); nextIsJump(Opcodes.GOTO, endLabel); // "if (primaryExc == suppressedExc)" nextIsLabel(startLabel); nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIsVar(Opcodes.ALOAD, suppressedExc); nextIsJump(Opcodes.IF_ACMPEQ, endLabel); // "primaryExc.addSuppressed(suppressedExc)" nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIsVar(Opcodes.ALOAD, suppressedExc); nextIsAddSuppressed(); nextIsLabel(endLabel); return cursor != null; } private void nextIsClose(final String name) { nextIsVar(Opcodes.ALOAD, name); next(); if (cursor == null) { return; } if (cursor.getOpcode() != Opcodes.INVOKEINTERFACE && cursor.getOpcode() != Opcodes.INVOKEVIRTUAL) { cursor = null; return; } final MethodInsnNode m = (MethodInsnNode) cursor; if (!"close".equals(m.name) || !"()V".equals(m.desc)) { cursor = null; return; } final String actual = m.owner; final String expected = owners.get(name); if (expected == null) { owners.put(name, actual); } else if (!expected.equals(actual)) { cursor = null; } } private void nextIsJump(final int opcode, final String name) { nextIs(opcode); if (cursor == null) { return; } final LabelNode actual = ((JumpInsnNode) cursor).label; final LabelNode expected = labels.get(name); if (expected == null) { labels.put(name, actual); } else if (expected != actual) { cursor = null; } } private void nextIsLabel(final String name) { if (cursor == null) { return; } cursor = cursor.getNext(); if (cursor.getType() != AbstractInsnNode.LABEL) { cursor = null; return; } final LabelNode actual = (LabelNode) cursor; final LabelNode expected = labels.get(name); if (expected != actual) { cursor = null; } } } }