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