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 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