Home | History | Annotate | Download | only in filter
      1 /*******************************************************************************
      2  * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors
      3  * All rights reserved. This program and the accompanying materials
      4  * are made available under the terms of the Eclipse Public License v1.0
      5  * which accompanies this distribution, and is available at
      6  * http://www.eclipse.org/legal/epl-v10.html
      7  *
      8  * Contributors:
      9  *    Evgeny Mandrikov - initial API and implementation
     10  *
     11  *******************************************************************************/
     12 package org.jacoco.core.internal.analysis.filter;
     13 
     14 import java.util.HashMap;
     15 import java.util.Map;
     16 
     17 import org.objectweb.asm.Opcodes;
     18 import org.objectweb.asm.tree.AbstractInsnNode;
     19 import org.objectweb.asm.tree.JumpInsnNode;
     20 import org.objectweb.asm.tree.LabelNode;
     21 import org.objectweb.asm.tree.MethodInsnNode;
     22 import org.objectweb.asm.tree.MethodNode;
     23 import org.objectweb.asm.tree.TryCatchBlockNode;
     24 
     25 /**
     26  * Filters code that ECJ generates for try-with-resources statement.
     27  */
     28 public final class TryWithResourcesEcjFilter implements IFilter {
     29 
     30 	public void filter(final String className, final String superClassName,
     31 			final MethodNode methodNode, final IFilterOutput output) {
     32 		if (methodNode.tryCatchBlocks.isEmpty()) {
     33 			return;
     34 		}
     35 		final Matcher matcher = new Matcher(output);
     36 		for (TryCatchBlockNode t : methodNode.tryCatchBlocks) {
     37 			if (t.type == null) {
     38 				matcher.start(t.handler);
     39 				if (!matcher.matchEcj()) {
     40 					matcher.start(t.handler);
     41 					matcher.matchEcjNoFlowOut();
     42 				}
     43 			}
     44 		}
     45 	}
     46 
     47 	static class Matcher extends AbstractMatcher {
     48 
     49 		private final IFilterOutput output;
     50 
     51 		private final Map<String, String> owners = new HashMap<String, String>();
     52 		private final Map<String, LabelNode> labels = new HashMap<String, LabelNode>();
     53 
     54 		private AbstractInsnNode start;
     55 
     56 		Matcher(final IFilterOutput output) {
     57 			this.output = output;
     58 		}
     59 
     60 		private void start(final AbstractInsnNode start) {
     61 			this.start = start;
     62 			cursor = start.getPrevious();
     63 			vars.clear();
     64 			labels.clear();
     65 			owners.clear();
     66 		}
     67 
     68 		private boolean matchEcj() {
     69 			// "catch (any primaryExc)"
     70 			nextIsVar(Opcodes.ASTORE, "primaryExc");
     71 			nextIsEcjCloseAndThrow("r0");
     72 
     73 			AbstractInsnNode c;
     74 			int resources = 1;
     75 			String r = "r" + resources;
     76 			c = cursor;
     77 			while (nextIsEcjClose(r)) {
     78 				nextIsJump(Opcodes.GOTO, r + ".end");
     79 				nextIsEcjSuppress(r);
     80 				nextIsEcjCloseAndThrow(r);
     81 				resources++;
     82 				r = "r" + resources;
     83 				c = cursor;
     84 			}
     85 			cursor = c;
     86 			nextIsEcjSuppress("last");
     87 			// "throw primaryExc"
     88 			nextIsVar(Opcodes.ALOAD, "primaryExc");
     89 			nextIs(Opcodes.ATHROW);
     90 			if (cursor == null) {
     91 				return false;
     92 			}
     93 			final AbstractInsnNode end = cursor;
     94 
     95 			AbstractInsnNode startOnNonExceptionalPath = start.getPrevious();
     96 			cursor = startOnNonExceptionalPath;
     97 			while (!nextIsEcjClose("r0")) {
     98 				startOnNonExceptionalPath = startOnNonExceptionalPath
     99 						.getPrevious();
    100 				cursor = startOnNonExceptionalPath;
    101 				if (cursor == null) {
    102 					return false;
    103 				}
    104 			}
    105 			startOnNonExceptionalPath = startOnNonExceptionalPath.getNext();
    106 
    107 			next();
    108 			if (cursor == null || cursor.getOpcode() != Opcodes.GOTO) {
    109 				return false;
    110 			}
    111 
    112 			output.ignore(startOnNonExceptionalPath, cursor);
    113 			output.ignore(start, end);
    114 			return true;
    115 		}
    116 
    117 		private boolean matchEcjNoFlowOut() {
    118 			// "catch (any primaryExc)"
    119 			nextIsVar(Opcodes.ASTORE, "primaryExc");
    120 
    121 			AbstractInsnNode c;
    122 			int resources = 0;
    123 			String r = "r" + resources;
    124 			c = cursor;
    125 			while (nextIsEcjCloseAndThrow(r) && nextIsEcjSuppress(r)) {
    126 				resources++;
    127 				r = "r" + resources;
    128 				c = cursor;
    129 			}
    130 			cursor = c;
    131 			// "throw primaryExc"
    132 			nextIsVar(Opcodes.ALOAD, "primaryExc");
    133 			nextIs(Opcodes.ATHROW);
    134 			if (cursor == null) {
    135 				return false;
    136 			}
    137 			final AbstractInsnNode end = cursor;
    138 
    139 			AbstractInsnNode startOnNonExceptionalPath = start.getPrevious();
    140 			cursor = startOnNonExceptionalPath;
    141 			while (!nextIsEcjClose("r0")) {
    142 				startOnNonExceptionalPath = startOnNonExceptionalPath
    143 						.getPrevious();
    144 				cursor = startOnNonExceptionalPath;
    145 				if (cursor == null) {
    146 					return false;
    147 				}
    148 			}
    149 			startOnNonExceptionalPath = startOnNonExceptionalPath.getNext();
    150 			for (int i = 1; i < resources; i++) {
    151 				if (!nextIsEcjClose("r" + i)) {
    152 					return false;
    153 				}
    154 			}
    155 
    156 			output.ignore(startOnNonExceptionalPath, cursor);
    157 			output.ignore(start, end);
    158 			return true;
    159 		}
    160 
    161 		private boolean nextIsEcjClose(final String name) {
    162 			nextIsVar(Opcodes.ALOAD, name);
    163 			// "if (r != null)"
    164 			nextIsJump(Opcodes.IFNULL, name + ".end");
    165 			// "r.close()"
    166 			nextIsClose(name);
    167 			return cursor != null;
    168 		}
    169 
    170 		private boolean nextIsEcjCloseAndThrow(final String name) {
    171 			nextIsVar(Opcodes.ALOAD, name);
    172 			// "if (r != null)"
    173 			nextIsJump(Opcodes.IFNULL, name);
    174 			// "r.close()"
    175 			nextIsClose(name);
    176 			nextIsLabel(name);
    177 			nextIsVar(Opcodes.ALOAD, "primaryExc");
    178 			nextIs(Opcodes.ATHROW);
    179 			return cursor != null;
    180 		}
    181 
    182 		private boolean nextIsEcjSuppress(final String name) {
    183 			final String suppressedExc = name + ".t";
    184 			final String startLabel = name + ".suppressStart";
    185 			final String endLabel = name + ".suppressEnd";
    186 			nextIsVar(Opcodes.ASTORE, suppressedExc);
    187 			// "suppressedExc = t"
    188 			// "if (primaryExc != null)"
    189 			nextIsVar(Opcodes.ALOAD, "primaryExc");
    190 			nextIsJump(Opcodes.IFNONNULL, startLabel);
    191 			// "primaryExc = suppressedExc"
    192 			nextIsVar(Opcodes.ALOAD, suppressedExc);
    193 			nextIsVar(Opcodes.ASTORE, "primaryExc");
    194 			nextIsJump(Opcodes.GOTO, endLabel);
    195 			// "if (primaryExc == suppressedExc)"
    196 			nextIsLabel(startLabel);
    197 			nextIsVar(Opcodes.ALOAD, "primaryExc");
    198 			nextIsVar(Opcodes.ALOAD, suppressedExc);
    199 			nextIsJump(Opcodes.IF_ACMPEQ, endLabel);
    200 			// "primaryExc.addSuppressed(suppressedExc)"
    201 			nextIsVar(Opcodes.ALOAD, "primaryExc");
    202 			nextIsVar(Opcodes.ALOAD, suppressedExc);
    203 			nextIsInvokeVirtual("java/lang/Throwable", "addSuppressed");
    204 			nextIsLabel(endLabel);
    205 			return cursor != null;
    206 		}
    207 
    208 		private void nextIsClose(final String name) {
    209 			nextIsVar(Opcodes.ALOAD, name);
    210 			next();
    211 			if (cursor == null) {
    212 				return;
    213 			}
    214 			if (cursor.getOpcode() != Opcodes.INVOKEINTERFACE
    215 					&& cursor.getOpcode() != Opcodes.INVOKEVIRTUAL) {
    216 				cursor = null;
    217 				return;
    218 			}
    219 			final MethodInsnNode m = (MethodInsnNode) cursor;
    220 			if (!"close".equals(m.name) || !"()V".equals(m.desc)) {
    221 				cursor = null;
    222 				return;
    223 			}
    224 			final String actual = m.owner;
    225 			final String expected = owners.get(name);
    226 			if (expected == null) {
    227 				owners.put(name, actual);
    228 			} else if (!expected.equals(actual)) {
    229 				cursor = null;
    230 			}
    231 		}
    232 
    233 		private void nextIsJump(final int opcode, final String name) {
    234 			nextIs(opcode);
    235 			if (cursor == null) {
    236 				return;
    237 			}
    238 			final LabelNode actual = ((JumpInsnNode) cursor).label;
    239 			final LabelNode expected = labels.get(name);
    240 			if (expected == null) {
    241 				labels.put(name, actual);
    242 			} else if (expected != actual) {
    243 				cursor = null;
    244 			}
    245 		}
    246 
    247 		private void nextIsLabel(final String name) {
    248 			if (cursor == null) {
    249 				return;
    250 			}
    251 			cursor = cursor.getNext();
    252 			if (cursor.getType() != AbstractInsnNode.LABEL) {
    253 				cursor = null;
    254 				return;
    255 			}
    256 			final LabelNode actual = (LabelNode) cursor;
    257 			final LabelNode expected = labels.get(name);
    258 			if (expected != actual) {
    259 				cursor = null;
    260 			}
    261 		}
    262 
    263 	}
    264 
    265 }
    266