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 static org.junit.Assert.assertEquals; 15 import static org.junit.Assert.fail; 16 17 import java.util.HashSet; 18 import java.util.Set; 19 20 import org.jacoco.core.internal.instr.InstrSupport; 21 import org.junit.Test; 22 import org.objectweb.asm.Label; 23 import org.objectweb.asm.Opcodes; 24 import org.objectweb.asm.tree.AbstractInsnNode; 25 import org.objectweb.asm.tree.MethodNode; 26 27 public class FinallyFilterTest implements IFilterOutput { 28 29 private final IFilter filter = new FinallyFilter(); 30 31 private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, 32 "name", "()V", null, null); 33 34 /** 35 * <pre> 36 * try { 37 * ... 38 * if (...) { 39 * ... 40 * return; 41 * } else { 42 * ... 43 * return; 44 * } 45 * } finally { 46 * ... 47 * } 48 * </pre> 49 */ 50 @Test 51 public void should_analyze_control_flow() { 52 final Label start1 = new Label(); 53 final Label end1 = new Label(); 54 final Label start2 = new Label(); 55 final Label end2 = new Label(); 56 final Label finallyStart = new Label(); 57 58 m.visitTryCatchBlock(start1, end1, finallyStart, null); 59 m.visitTryCatchBlock(start2, end2, finallyStart, null); 60 61 m.visitLabel(start1); 62 // jump to another region associated with same handler: 63 m.visitJumpInsn(Opcodes.IFEQ, start2); 64 m.visitInsn(Opcodes.NOP); 65 m.visitLabel(end1); 66 67 m.visitInsn(Opcodes.NOP); // finally block 68 shouldMergeLast(); 69 m.visitInsn(Opcodes.RETURN); 70 71 m.visitLabel(start2); 72 m.visitInsn(Opcodes.NOP); 73 m.visitLabel(end2); 74 m.visitInsn(Opcodes.NOP); // finally block 75 shouldMergeLast(); 76 m.visitInsn(Opcodes.RETURN); 77 78 m.visitLabel(finallyStart); 79 m.visitVarInsn(Opcodes.ASTORE, 1); 80 shouldIgnoreLast(); 81 m.visitInsn(Opcodes.NOP); // finally block 82 shouldMergeLast(); 83 m.visitVarInsn(Opcodes.ALOAD, 1); 84 shouldIgnoreLast(); 85 m.visitInsn(Opcodes.ATHROW); 86 shouldIgnoreLast(); 87 88 execute(); 89 } 90 91 // === try/catch/finally === 92 93 @Test 94 public void javac_try_catch_finally() { 95 final Label tryStart = new Label(); 96 final Label tryEnd = new Label(); 97 final Label catchStart = new Label(); 98 final Label catchEnd = new Label(); 99 final Label finallyStart = new Label(); 100 final Label finallyEnd = new Label(); 101 102 m.visitTryCatchBlock(tryStart, tryEnd, catchStart, 103 "java/lang/Exception"); 104 m.visitTryCatchBlock(catchStart, catchEnd, finallyStart, null); 105 m.visitTryCatchBlock(tryStart, tryEnd, finallyStart, null); 106 107 m.visitLabel(tryStart); 108 m.visitInsn(Opcodes.NOP); // try body 109 m.visitLabel(tryEnd); 110 m.visitInsn(Opcodes.NOP); // finally body 111 shouldMergeLast(); 112 m.visitJumpInsn(Opcodes.GOTO, finallyEnd); 113 shouldIgnoreLast(); 114 115 m.visitLabel(catchStart); 116 m.visitInsn(Opcodes.NOP); // catch body 117 m.visitLabel(catchEnd); 118 m.visitInsn(Opcodes.NOP); // finally body 119 shouldMergeLast(); 120 m.visitInsn(Opcodes.ATHROW); 121 122 m.visitLabel(finallyStart); 123 m.visitVarInsn(Opcodes.ASTORE, 1); 124 shouldIgnoreLast(); 125 m.visitInsn(Opcodes.NOP); // finally body 126 shouldMergeLast(); 127 m.visitVarInsn(Opcodes.ALOAD, 1); 128 shouldIgnoreLast(); 129 m.visitInsn(Opcodes.ATHROW); 130 shouldIgnoreLast(); 131 m.visitLabel(finallyEnd); 132 133 m.visitInsn(Opcodes.NOP); 134 135 execute(); 136 } 137 138 @Test 139 public void ecj_try_catch_finally() { 140 final Label tryStart = new Label(); 141 final Label tryEnd = new Label(); 142 final Label catchStart = new Label(); 143 final Label catchEnd = new Label(); 144 final Label finallyStart = new Label(); 145 final Label finallyEnd = new Label(); 146 final Label after = new Label(); 147 148 m.visitTryCatchBlock(tryStart, tryEnd, catchStart, 149 "java/lang/Exception"); 150 m.visitTryCatchBlock(tryStart, catchEnd, finallyStart, null); 151 152 m.visitLabel(tryStart); 153 m.visitInsn(Opcodes.NOP); // try body 154 m.visitLabel(tryEnd); 155 m.visitJumpInsn(Opcodes.GOTO, finallyEnd); 156 157 m.visitLabel(catchStart); 158 m.visitInsn(Opcodes.POP); 159 m.visitInsn(Opcodes.NOP); // catch body 160 m.visitLabel(catchEnd); 161 m.visitInsn(Opcodes.NOP); // finally body 162 shouldMergeLast(); 163 m.visitJumpInsn(Opcodes.GOTO, after); 164 shouldIgnoreLast(); 165 166 m.visitLabel(finallyStart); 167 m.visitVarInsn(Opcodes.ASTORE, 1); 168 shouldIgnoreLast(); 169 m.visitInsn(Opcodes.NOP); // finally body 170 shouldMergeLast(); 171 m.visitVarInsn(Opcodes.ALOAD, 1); 172 shouldIgnoreLast(); 173 m.visitInsn(Opcodes.ATHROW); 174 shouldIgnoreLast(); 175 m.visitLabel(finallyEnd); 176 177 m.visitInsn(Opcodes.NOP); // finally body 178 shouldMergeLast(); 179 m.visitLabel(after); 180 m.visitInsn(Opcodes.NOP); 181 182 execute(); 183 } 184 185 // === empty catch === 186 187 /** 188 * javac 1.5 - 1.7 189 */ 190 @Test 191 public void javac_empty_catch() { 192 final Label tryStart = new Label(); 193 final Label tryEnd = new Label(); 194 final Label catchStart = new Label(); 195 final Label catchEnd = new Label(); 196 final Label finallyStart = new Label(); 197 final Label finallyEnd = new Label(); 198 199 m.visitTryCatchBlock(tryStart, tryEnd, catchStart, 200 "java/lang/Exception"); 201 m.visitTryCatchBlock(tryStart, tryEnd, finallyStart, null); 202 m.visitTryCatchBlock(catchStart, catchEnd, finallyStart, null); 203 // actually one more useless TryCatchBlock for ASTORE in finally 204 205 m.visitLabel(tryStart); 206 m.visitInsn(Opcodes.NOP); // try body 207 m.visitLabel(tryEnd); 208 m.visitInsn(Opcodes.NOP); // finally body 209 shouldMergeLast(); 210 m.visitJumpInsn(Opcodes.GOTO, finallyEnd); 211 shouldIgnoreLast(); 212 213 m.visitLabel(catchStart); 214 m.visitVarInsn(Opcodes.ASTORE, 1); 215 m.visitLabel(catchEnd); 216 m.visitInsn(Opcodes.NOP); // finally body 217 shouldMergeLast(); 218 m.visitJumpInsn(Opcodes.GOTO, finallyEnd); 219 shouldIgnoreLast(); 220 221 m.visitLabel(finallyStart); 222 m.visitVarInsn(Opcodes.ASTORE, 1); 223 shouldIgnoreLast(); 224 m.visitInsn(Opcodes.NOP); // finally body 225 shouldMergeLast(); 226 m.visitVarInsn(Opcodes.ALOAD, 1); 227 shouldIgnoreLast(); 228 m.visitInsn(Opcodes.ATHROW); 229 shouldIgnoreLast(); 230 m.visitLabel(finallyEnd); 231 232 m.visitInsn(Opcodes.NOP); 233 234 execute(); 235 } 236 237 /** 238 * javac >= 1.8 239 * 240 * Probably related to https://bugs.openjdk.java.net/browse/JDK-7093325 241 */ 242 @Test 243 public void javac_8_empty_catch() throws Exception { 244 final Label tryStart = new Label(); 245 final Label tryEnd = new Label(); 246 final Label catchStart = new Label(); 247 final Label finallyStart = new Label(); 248 final Label finallyEnd = new Label(); 249 250 m.visitTryCatchBlock(tryStart, tryEnd, catchStart, 251 "java/lang/Exception"); 252 m.visitTryCatchBlock(tryStart, tryEnd, finallyStart, null); 253 254 m.visitLabel(tryStart); 255 m.visitInsn(Opcodes.NOP); // try body 256 m.visitLabel(tryEnd); 257 m.visitInsn(Opcodes.NOP); // finally body 258 shouldMergeLast(); 259 m.visitJumpInsn(Opcodes.GOTO, finallyEnd); 260 shouldIgnoreLast(); 261 shouldIgnoreLast(); 262 263 m.visitLabel(catchStart); 264 m.visitVarInsn(Opcodes.ASTORE, 1); 265 m.visitInsn(Opcodes.NOP); // finally body 266 shouldMergeLast(); 267 m.visitJumpInsn(Opcodes.GOTO, finallyEnd); 268 shouldIgnoreLast(); 269 270 m.visitLabel(finallyStart); 271 m.visitVarInsn(Opcodes.ASTORE, 1); 272 shouldIgnoreLast(); 273 m.visitInsn(Opcodes.NOP); // finally body 274 shouldMergeLast(); 275 m.visitVarInsn(Opcodes.ALOAD, 1); 276 shouldIgnoreLast(); 277 m.visitInsn(Opcodes.ATHROW); 278 shouldIgnoreLast(); 279 m.visitLabel(finallyEnd); 280 281 execute(); 282 } 283 284 @Test 285 public void ecj_empty_catch() { 286 final Label tryStart = new Label(); 287 final Label tryEnd = new Label(); 288 final Label catchStart = new Label(); 289 final Label catchEnd = new Label(); 290 final Label finallyStart = new Label(); 291 final Label finallyEnd = new Label(); 292 final Label after = new Label(); 293 294 m.visitTryCatchBlock(tryStart, tryEnd, catchStart, 295 "java/lang/Exception"); 296 m.visitTryCatchBlock(tryStart, catchEnd, finallyStart, null); 297 298 m.visitLabel(tryStart); 299 m.visitInsn(Opcodes.NOP); // try body 300 m.visitLabel(tryEnd); 301 m.visitJumpInsn(Opcodes.GOTO, finallyEnd); 302 303 m.visitLabel(catchStart); 304 m.visitInsn(Opcodes.POP); 305 m.visitLabel(catchEnd); 306 m.visitInsn(Opcodes.NOP); // finally body 307 shouldMergeLast(); 308 m.visitJumpInsn(Opcodes.GOTO, after); 309 shouldIgnoreLast(); 310 311 m.visitLabel(finallyStart); 312 m.visitVarInsn(Opcodes.ASTORE, 1); 313 shouldIgnoreLast(); 314 m.visitInsn(Opcodes.NOP); // finally body 315 shouldMergeLast(); 316 m.visitVarInsn(Opcodes.ALOAD, 1); 317 shouldIgnoreLast(); 318 m.visitInsn(Opcodes.ATHROW); 319 shouldIgnoreLast(); 320 m.visitLabel(finallyEnd); 321 322 m.visitInsn(Opcodes.NOP); // finally body 323 shouldMergeLast(); 324 m.visitLabel(after); 325 m.visitInsn(Opcodes.NOP); 326 327 execute(); 328 } 329 330 // === always completes abruptly === 331 332 @Test 333 public void javac_always_completes_abruptly() { 334 final Label tryStart = new Label(); 335 final Label tryEnd = new Label(); 336 final Label finallyStart = new Label(); 337 338 m.visitTryCatchBlock(tryStart, tryEnd, finallyStart, null); 339 340 m.visitLabel(tryStart); 341 m.visitInsn(Opcodes.NOP); // try body 342 m.visitLabel(tryEnd); 343 m.visitInsn(Opcodes.RETURN); // finally body 344 345 m.visitLabel(finallyStart); 346 m.visitVarInsn(Opcodes.ASTORE, 1); 347 m.visitInsn(Opcodes.RETURN); // finally body 348 349 execute(); 350 } 351 352 @Test 353 public void ecj_always_completes_abruptly() { 354 final Label tryStart = new Label(); 355 final Label tryEnd = new Label(); 356 final Label finallyStart = new Label(); 357 358 m.visitTryCatchBlock(tryStart, tryEnd, tryEnd, null); 359 360 m.visitLabel(tryStart); 361 m.visitInsn(Opcodes.NOP); // try body 362 m.visitJumpInsn(Opcodes.GOTO, finallyStart); 363 m.visitLabel(tryEnd); 364 365 m.visitInsn(Opcodes.POP); 366 m.visitLabel(finallyStart); 367 m.visitInsn(Opcodes.RETURN); // finally body 368 369 execute(); 370 } 371 372 private final Set<AbstractInsnNode> expectedIgnored = new HashSet<AbstractInsnNode>(); 373 private final Set<AbstractInsnNode> actualIgnored = new HashSet<AbstractInsnNode>(); 374 private final Set<AbstractInsnNode> expectedMerged = new HashSet<AbstractInsnNode>(); 375 private final Set<AbstractInsnNode> actualMerged = new HashSet<AbstractInsnNode>(); 376 377 private void shouldMergeLast() { 378 expectedMerged.add(m.instructions.getLast()); 379 } 380 381 private void shouldIgnoreLast() { 382 expectedIgnored.add(m.instructions.getLast()); 383 } 384 385 private void execute() { 386 filter.filter("", "", m, this); 387 assertEquals("ignored", toIndexes(expectedIgnored), 388 toIndexes(actualIgnored)); 389 assertEquals("merged", toIndexes(expectedMerged), 390 toIndexes(actualMerged)); 391 } 392 393 @SuppressWarnings("boxing") 394 private Set<Integer> toIndexes(Set<AbstractInsnNode> set) { 395 final Set<Integer> result = new HashSet<Integer>(); 396 for (final AbstractInsnNode i : set) { 397 result.add(m.instructions.indexOf(i)); 398 } 399 return result; 400 } 401 402 public void ignore(final AbstractInsnNode fromInclusive, 403 final AbstractInsnNode toInclusive) { 404 for (AbstractInsnNode i = fromInclusive; i != toInclusive; i = i 405 .getNext()) { 406 actualIgnored.add(i); 407 } 408 actualIgnored.add(toInclusive); 409 } 410 411 public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { 412 if (actualMerged.isEmpty() || actualMerged.contains(i1) 413 || actualMerged.contains(i2)) { 414 actualMerged.add(i1); 415 actualMerged.add(i2); 416 } else { 417 fail(); 418 } 419 } 420 421 } 422