aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--org.jacoco.core.test/pom.xml33
-rw-r--r--org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/TryWithResourcesTest.java186
-rw-r--r--org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/targets/TryWithResources.java204
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilterTest.java624
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilterTest.java814
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java3
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java83
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java39
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java265
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java253
-rw-r--r--org.jacoco.doc/docroot/doc/changes.html3
11 files changed, 2479 insertions, 28 deletions
diff --git a/org.jacoco.core.test/pom.xml b/org.jacoco.core.test/pom.xml
index 8ee218a5..31ef2e37 100644
--- a/org.jacoco.core.test/pom.xml
+++ b/org.jacoco.core.test/pom.xml
@@ -40,6 +40,37 @@
<profiles>
<profile>
+ <id>java7-validation</id>
+ <activation>
+ <property>
+ <name>bytecode.version</name>
+ <value>1.7</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>src-java7</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
<id>java8-validation</id>
<activation>
<property>
@@ -61,6 +92,7 @@
</goals>
<configuration>
<sources>
+ <source>src-java7</source>
<source>src-java8</source>
</sources>
</configuration>
@@ -96,6 +128,7 @@
</goals>
<configuration>
<sources>
+ <source>src-java7</source>
<source>src-java8</source>
</sources>
</configuration>
diff --git a/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/TryWithResourcesTest.java b/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/TryWithResourcesTest.java
new file mode 100644
index 00000000..1530505c
--- /dev/null
+++ b/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/TryWithResourcesTest.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * 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.test.filter;
+
+import org.jacoco.core.analysis.ICounter;
+import org.jacoco.core.test.filter.targets.TryWithResources;
+import org.jacoco.core.test.validation.ValidationTestBase;
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Test of filtering of a bytecode that is generated for a try-with-resources
+ * statement.
+ */
+public class TryWithResourcesTest extends ValidationTestBase {
+
+ public TryWithResourcesTest() {
+ super("src-java7", TryWithResources.class);
+ }
+
+ /**
+ * {@link TryWithResources#test()}
+ */
+ @Test
+ public void test() {
+ assertLine("test.before", ICounter.FULLY_COVERED);
+ // without filter next line covered partly:
+ assertLine("test.try", ICounter.FULLY_COVERED);
+ assertLine("test.open1", ICounter.FULLY_COVERED);
+ assertLine("test.open2", ICounter.FULLY_COVERED);
+ assertLine("test.open3", ICounter.FULLY_COVERED);
+ assertLine("test.body", ICounter.FULLY_COVERED);
+ // without filter next line has branches:
+ assertLine("test.close", ICounter.EMPTY);
+ assertLine("test.catch", ICounter.NOT_COVERED);
+ assertLine("test.finally", ICounter.PARTLY_COVERED);
+ }
+
+ /**
+ * {@link TryWithResources#test2()}
+ */
+ @Test
+ public void test2() {
+ assertLine("test2.before", ICounter.FULLY_COVERED);
+ // without filter next line covered partly:
+ assertLine("test2.try", ICounter.FULLY_COVERED);
+ assertLine("test2.open1", ICounter.FULLY_COVERED);
+ assertLine("test2.open2", ICounter.FULLY_COVERED);
+ assertLine("test2.open3", ICounter.FULLY_COVERED);
+ assertLine("test2.body", ICounter.FULLY_COVERED);
+ // without filter next line has branches:
+ assertLine("test2.close", ICounter.EMPTY);
+ assertLine("test2.catch", ICounter.NOT_COVERED);
+ assertLine("test2.finally", ICounter.PARTLY_COVERED);
+ assertLine("test2.after", ICounter.FULLY_COVERED);
+ }
+
+ /**
+ * {@link TryWithResources#returnInBody()}
+ */
+ @Test
+ public void returnInBody() {
+ // without filter next line covered partly:
+ assertLine("returnInBody.try", ICounter.FULLY_COVERED);
+ assertLine("returnInBody.open", ICounter.FULLY_COVERED);
+
+ // without filter next line has branches:
+ if (isJDKCompiler) {
+ // https://bugs.openjdk.java.net/browse/JDK-8134759
+ // javac 7 and 8 up to 8u92 are affected
+ final String jdkVersion = System.getProperty("java.version");
+ final Matcher m = Pattern.compile("1\\.8\\.0_(\\d++)(-ea)?")
+ .matcher(jdkVersion);
+ if (jdkVersion.startsWith("1.7.0_")
+ || (m.matches() && Integer.parseInt(m.group(1)) < 92)) {
+ assertLine("returnInBody.close", ICounter.FULLY_COVERED, 0, 0);
+ } else {
+ assertLine("returnInBody.close", ICounter.EMPTY);
+ }
+ } else {
+ assertLine("returnInBody.close", ICounter.EMPTY);
+ }
+
+ assertLine("returnInBody.return", ICounter.FULLY_COVERED);
+ }
+
+ /**
+ * {@link TryWithResources#nested()}
+ */
+ @Test
+ public void nested() {
+ // without filter next line covered partly:
+ assertLine("nested.try1", ICounter.FULLY_COVERED);
+ assertLine("nested.open1", ICounter.FULLY_COVERED);
+ assertLine("nested.catch1", ICounter.NOT_COVERED);
+
+ // without filter next line covered partly:
+ assertLine("nested.try2", ICounter.FULLY_COVERED);
+ assertLine("nested.body", ICounter.FULLY_COVERED);
+ assertLine("nested.catch2", ICounter.NOT_COVERED);
+ assertLine("nested.finally2", ICounter.PARTLY_COVERED);
+
+ // next lines not covered on exceptional path:
+ assertLine("nested.try3", ICounter.PARTLY_COVERED, 0, 0);
+ assertLine("nested.open3", ICounter.PARTLY_COVERED, 0, 0);
+ assertLine("nested.body3", ICounter.PARTLY_COVERED, 0, 0);
+ assertLine("nested.catch3", ICounter.NOT_COVERED);
+ assertLine("nested.finally3", ICounter.PARTLY_COVERED, 0, 0);
+
+ // without filter next lines have branches:
+ assertLine("nested.close3", ICounter.EMPTY);
+ assertLine("nested.close2", ICounter.EMPTY);
+ assertLine("nested.close1", ICounter.EMPTY);
+ }
+
+ /**
+ * {@link TryWithResources#returnInCatch()}
+ */
+ @Test
+ public void returnInCatch() {
+ // without filter next line covered partly:
+ assertLine("returnInCatch.try1", ICounter.FULLY_COVERED);
+ assertLine("returnInCatch.open", ICounter.FULLY_COVERED);
+ assertLine("returnInCatch.finally1", ICounter.PARTLY_COVERED, 5, 1);
+ // without filter next line has branches:
+ assertLine("returnInCatch.close", ICounter.EMPTY);
+
+ assertLine("returnInCatch.try2", ICounter.EMPTY);
+ assertLine("returnInCatch.finally2", ICounter.PARTLY_COVERED, 5, 1);
+ }
+
+ /*
+ * Corner cases
+ */
+
+ /**
+ * {@link TryWithResources#handwritten()}
+ */
+ @Test
+ public void handwritten() {
+ if (isJDKCompiler) {
+ assertLine("handwritten", /* partly when ECJ: */ICounter.EMPTY);
+ }
+ }
+
+ /**
+ * {@link TryWithResources#empty()}
+ */
+ @Test
+ public void empty() {
+ assertLine("empty.try", ICounter.FULLY_COVERED, 0, 0);
+ assertLine("empty.open", ICounter.FULLY_COVERED);
+ // empty when EJC:
+ if (isJDKCompiler) {
+ final String jdkVersion = System.getProperty("java.version");
+ if (jdkVersion.startsWith("9-")) {
+ assertLine("empty.close", ICounter.FULLY_COVERED, 0, 0);
+ } else {
+ // branches with javac 7 and 8
+ assertLine("empty.close", ICounter.PARTLY_COVERED);
+ }
+ }
+ }
+
+ /**
+ * {@link TryWithResources#throwInBody()}
+ */
+ @Test
+ public void throwInBody() {
+ // not filtered
+ assertLine("throwInBody.try", ICounter.NOT_COVERED);
+ assertLine("throwInBody.close", ICounter.NOT_COVERED);
+ }
+
+}
diff --git a/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/targets/TryWithResources.java b/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/targets/TryWithResources.java
new file mode 100644
index 00000000..19a4651d
--- /dev/null
+++ b/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/targets/TryWithResources.java
@@ -0,0 +1,204 @@
+/*******************************************************************************
+ * 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.test.filter.targets;
+
+import static org.jacoco.core.test.validation.targets.Stubs.f;
+import static org.jacoco.core.test.validation.targets.Stubs.nop;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * This test target is a try-with-resources statement.
+ */
+public class TryWithResources {
+
+ private static class Resource implements Closeable {
+ @Override
+ public void close() {
+ }
+ }
+
+ /**
+ * Closing performed using {@link org.objectweb.asm.Opcodes#INVOKEVIRTUAL}
+ * or {@link org.objectweb.asm.Opcodes#INVOKEINTERFACE} depending on a class
+ * of resource.
+ */
+ private static Object test() throws Exception {
+ nop(); // $line-test.before$
+ try ( // $line-test.try$
+ Resource r1 = new Resource(); // $line-test.open1$
+ Closeable r2 = new Resource(); // $line-test.open2$
+ AutoCloseable r3 = new Resource() // $line-test.open3$
+ ) {
+ return read(r1, r2, r3); // $line-test.body$
+ } // $line-test.close$
+ catch (Exception e) {
+ nop(); // $line-test.catch$
+ throw e;
+ } finally {
+ nop(); // $line-test.finally$
+ }
+ }
+
+ private static void test2() throws Exception {
+ nop(); // $line-test2.before$
+ try ( // $line-test2.try$
+ Resource r1 = new Resource(); // $line-test2.open1$
+ Closeable r2 = new Resource(); // $line-test2.open2$
+ AutoCloseable r3 = new Resource() // $line-test2.open3$
+ ) {
+ read(r1, r2, r3); // $line-test2.body$
+ } // $line-test2.close$
+ catch (Exception e) {
+ nop(); // $line-test2.catch$
+ } finally {
+ nop(); // $line-test2.finally$
+ }
+ nop(); // $line-test2.after$
+ }
+
+ private static Object returnInBody() throws IOException {
+ try ( // $line-returnInBody.try$
+ Closeable r = new Resource() // $line-returnInBody.open$
+ ) {
+ return read(r); // $line-returnInBody.return$
+ } // $line-returnInBody.close$
+ }
+
+ private static void nested() {
+ try ( // $line-nested.try1$
+ Resource r1 = new Resource() // $line-nested.open1$
+ ) {
+
+ try ( // $line-nested.try2$
+ Resource r2 = new Resource() // $line-nested.open2$
+ ) {
+ nop(r1.toString() + r2.toString()); // $line-nested.body$
+ } // $line-nested.close2$
+ catch (Exception e) {
+ nop(); // $line-nested.catch2$
+ } finally {
+ nop(); // $line-nested.finally2$
+ }
+
+ } // $line-nested.close1$
+ catch (Exception e) {
+ nop(); // $line-nested.catch1$
+ } finally {
+
+ try ( // $line-nested.try3$
+ Resource r2 = new Resource() // $line-nested.open3$
+ ) {
+ nop(r2); // $line-nested.body3$
+ } // $line-nested.close3$
+ catch (Exception e) {
+ nop(); // $line-nested.catch3$
+ } finally {
+ nop(); // $line-nested.finally3$
+ }
+
+ }
+ }
+
+ /**
+ * In this case bytecode will contain 3 copies of <code>finally</code>
+ * block, each containing 2 branches, resulting in 6 branches in total. One
+ * could think that this is artifact of try-with-resources, but the same
+ * happens without it.
+ */
+ private static Object returnInCatch() {
+ try ( // $line-returnInCatch.try1$
+ Resource r = new Resource() // $line-returnInCatch.open$
+ ) {
+ read(r);
+ } // $line-returnInCatch.close$
+ catch (Exception e) {
+ return null;
+ } finally {
+ nop(!f()); // $line-returnInCatch.finally1$
+ }
+
+ try { // $line-returnInCatch.try2$
+ read(new Resource());
+ } catch (Exception e) {
+ return null;
+ } finally {
+ nop(!f()); // $line-returnInCatch.finally2$
+ }
+
+ return null;
+ }
+
+ private static Object read(Object r1, Object r2, Object r3) {
+ return r1.toString() + r2.toString() + r3.toString();
+ }
+
+ private static Object read(Object r1) {
+ return r1.toString();
+ }
+
+ public static void main(String[] args) throws Exception {
+ test();
+ test2();
+ returnInBody();
+ nested();
+
+ returnInCatch();
+
+ empty();
+ handwritten();
+ }
+
+ /*
+ * Corner cases
+ */
+
+ private static void empty() throws Exception {
+ try ( // $line-empty.try$
+ Closeable r = new Resource() // $line-empty.open$
+ ) {
+ } // $line-empty.close$
+ }
+
+ private static void handwritten() throws IOException {
+ Closeable r = new Resource();
+ Throwable primaryExc = null;
+ try {
+ nop(r);
+ } catch (Throwable t) {
+ primaryExc = t;
+ throw t;
+ } finally {
+ if (r != null) { // $line-handwritten$
+ if (primaryExc != null) {
+ try {
+ r.close();
+ } catch (Throwable suppressedExc) {
+ primaryExc.addSuppressed(suppressedExc);
+ }
+ } else {
+ r.close();
+ }
+ }
+ }
+ }
+
+ private static void throwInBody() throws IOException {
+ try ( // $line-throwInBody.try$
+ Closeable r = new Resource()) {
+ nop(r);
+ throw new RuntimeException();
+ } // $line-throwInBody.close$
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilterTest.java
new file mode 100644
index 00000000..d3720505
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilterTest.java
@@ -0,0 +1,624 @@
+/*******************************************************************************
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jacoco.core.internal.instr.InstrSupport;
+import org.junit.Test;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+public class TryWithResourcesEcjFilterTest implements IFilterOutput {
+
+ private final TryWithResourcesEcjFilter filter = new TryWithResourcesEcjFilter();
+
+ private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
+ "name", "()V", null, null);
+
+ /**
+ * ECJ for
+ *
+ * <pre>
+ * try (r0 = ...; r1 = ...; r2= ...) {
+ * ...
+ * } finally (...) {
+ * ...
+ * }
+ * ...
+ * </pre>
+ *
+ * generates
+ *
+ * <pre>
+ * ACONST_NULL
+ * ASTORE primaryExc
+ * ACONST_NULL
+ * ASTORE suppressedExc
+ * ...
+ * ASTORE r1
+ * ...
+ * ASTORE r2
+ * ...
+ * ASTORE r3
+ *
+ * ... // body
+ *
+ * ALOAD r3
+ * IFNULL r2_close
+ * ALOAD r3
+ * INVOKEVIRTUAL close:()V
+ * GOTO r2_close
+ *
+ * ASTORE primaryExc
+ * ALOAD r3
+ * IFNULL n
+ * ALOAD r3
+ * INVOKEVIRTUAL close:()V
+ * n:
+ * ALOAD primaryExc
+ * ATHROW
+ *
+ * r2_close:
+ * ALOAD r2
+ * IFNULL r1_close
+ * ALOAD r2
+ * INVOKEVIRTUAL close:()V
+ * GOTO r1_close
+ *
+ * ASTORE suppressedExc
+ * ALOAD primaryExc
+ * IFNONNULL s
+ * ALOAD suppressedExc
+ * ASTORE primaryExc
+ * GOTO e
+ * s:
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * IF_ACMPEQ e
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * e:
+ *
+ * ALOAD r2
+ * IFNULL n
+ * ALOAD r2
+ * INVOKEVIRTUAL close:()V
+ * n:
+ * ALOAD primaryExc
+ * ATHROW
+ *
+ * r1_close:
+ * ALOAD r1
+ * IFNULL after
+ * ALOAD r1
+ * INVOKEVIRTUAL close:()V
+ * GOTO after
+ *
+ * ASTORE suppressedExc
+ * ALOAD primaryExc
+ * IFNONNULL s
+ * ALOAD suppressedExc
+ * ASTORE primaryExc
+ * GOTO e
+ * s:
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * IF_ACMPEQ e
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * e:
+ *
+ * ALOAD r1
+ * IFNULL n
+ * ALOAD r1
+ * INVOKEVIRTUAL close:()V
+ * n:
+ * ALOAD primaryExc
+ * ATHROW
+ *
+ * ASTORE suppressedExc
+ * ALOAD primaryExc
+ * IFNONNULL s
+ * ALOAD suppressedExc
+ * ASTORE primaryExc
+ * GOTO e
+ * s:
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * IF_ACMPEQ e
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * e:
+ *
+ * ALOAD primaryExc
+ * ATHROW
+ *
+ * ... // additional handlers for catch blocks and finally on exceptional path
+ *
+ * after:
+ * ... // finally on normal path
+ * ...
+ * </pre>
+ */
+ @Test
+ public void ecj() {
+ final Range range0 = new Range();
+ final Range range1 = new Range();
+
+ final Label handler = new Label();
+ m.visitTryCatchBlock(handler, handler, handler, null);
+
+ // primaryExc = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ // suppressedExc = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+
+ // body
+ m.visitInsn(Opcodes.NOP);
+
+ final Label l4 = new Label();
+ final Label l7 = new Label();
+ final Label end = new Label();
+ { // nextIsEcjClose("r0")
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ range0.fromInclusive = m.instructions.getLast();
+ m.visitJumpInsn(Opcodes.IFNULL, l4);
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun2$Resource", "close",
+ "()V", false);
+ }
+ m.visitJumpInsn(Opcodes.GOTO, l4);
+ range0.toInclusive = m.instructions.getLast();
+ // catch (any primaryExc)
+ m.visitLabel(handler);
+ range1.fromInclusive = m.instructions.getLast();
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ { // nextIsEcjCloseAndThrow("r0")
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ Label l11 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l11);
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun2$Resource", "close",
+ "()V", false);
+ m.visitLabel(l11);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitInsn(Opcodes.ATHROW);
+ }
+ m.visitLabel(l4);
+ { // nextIsEcjClose("r1")
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitJumpInsn(Opcodes.IFNULL, l7);
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun2$Resource", "close",
+ "()V", false);
+ }
+ m.visitJumpInsn(Opcodes.GOTO, l7);
+ { // nextIsEcjSuppress
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ final Label suppressStart = new Label();
+ m.visitJumpInsn(Opcodes.IFNONNULL, suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ final Label suppressEnd = new Label();
+ m.visitJumpInsn(Opcodes.GOTO, suppressEnd);
+ m.visitLabel(suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitJumpInsn(Opcodes.IF_ACMPEQ, suppressEnd);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitLabel(suppressEnd);
+ }
+ { // nextIsEcjCloseAndThrow("r1")
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ final Label l14 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l14);
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun2$Resource", "close",
+ "()V", false);
+ m.visitLabel(l14);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitInsn(Opcodes.ATHROW);
+ }
+ m.visitLabel(l7);
+ { // nextIsEcjClose("r2")
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitJumpInsn(Opcodes.IFNULL, end);
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun2$Resource", "close",
+ "()V", false);
+ m.visitJumpInsn(Opcodes.GOTO, end);
+ }
+ { // nextIsEcjSuppress
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ final Label suppressStart = new Label();
+ m.visitJumpInsn(Opcodes.IFNONNULL, suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ final Label suppressEnd = new Label();
+ m.visitJumpInsn(Opcodes.GOTO, suppressEnd);
+ m.visitLabel(suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitJumpInsn(Opcodes.IF_ACMPEQ, suppressEnd);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitLabel(suppressEnd);
+ }
+ { // nextIsEcjCloseAndThrow("r2")
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ final Label l18 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l18);
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun2$Resource", "close",
+ "()V", false);
+ m.visitLabel(l18);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitInsn(Opcodes.ATHROW);
+ }
+ { // nextIsEcjSuppress
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ final Label suppressStart = new Label();
+ m.visitJumpInsn(Opcodes.IFNONNULL, suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ final Label suppressEnd = new Label();
+ m.visitJumpInsn(Opcodes.GOTO, suppressEnd);
+ m.visitLabel(suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitJumpInsn(Opcodes.IF_ACMPEQ, suppressEnd);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitLabel(suppressEnd);
+ }
+ // throw primaryExc
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitInsn(Opcodes.ATHROW);
+ range1.toInclusive = m.instructions.getLast();
+
+ // additional handlers
+ m.visitInsn(Opcodes.NOP);
+
+ filter.filter("Foo", "java/lang/Object", m, this);
+
+ assertEquals(2, from.size());
+
+ assertEquals(range0.fromInclusive, from.get(0));
+ assertEquals(range0.toInclusive, to.get(0));
+
+ assertEquals(range1.fromInclusive, from.get(1));
+ assertEquals(range1.toInclusive, to.get(1));
+ }
+
+ /**
+ * ECJ for
+ *
+ * <pre>
+ * try (r1 = ...; r2 = ...; r3 = ...) {
+ * return ...
+ * } finally {
+ * ...
+ * }
+ * </pre>
+ *
+ * generates
+ *
+ * <pre>
+ * ACONST_NULL
+ * astore primaryExc
+ * ACONST_NULL
+ * astore suppressedExc
+ *
+ * ...
+ * ASTORE r1
+ * ...
+ * ASTORE r2
+ * ...
+ * ASTORE r3
+ *
+ * ... // body
+ *
+ * ALOAD r3
+ * IFNULL n
+ * ALOAD r3
+ * INVOKEVIRTUAL close:()V
+ * n:
+ * ALOAD r2
+ * IFNULL n
+ * ALOAD r2
+ * INVOKEVIRTUAL close:()V
+ * n:
+ * ALOAD r1
+ * IFNULL n
+ * ALOAD r1
+ * INVOKEVIRTUAL close:()V
+ * n:
+ *
+ * ... // finally on normal path
+ * ARETURN
+ *
+ * ASTORE primaryExc
+ * ALOAD r3
+ * IFNULL n
+ * ALOAD r3
+ * INVOKEVIRTUAL close:()V
+ * n:
+ * ALOAD primaryExc
+ * ATHROW
+ *
+ * ASTORE suppressedExc
+ * ALOAD primaryExc
+ * IFNONNULL s
+ * ALOAD suppressedExc
+ * ASTORE primaryExc
+ * GOTO e
+ * s:
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * IF_ACMPEQ e
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * e:
+ *
+ * ALOAD r2
+ * IFNULL n
+ * ALOAD r2
+ * INVOKEVIRTUAL close:()V
+ * n:
+ * ALOAD primaryExc
+ * ATHROW
+ *
+ * ASTORE suppressedExc
+ * ALOAD primaryExc
+ * IFNONNULL s
+ * ALOAD suppressedExc
+ * ASTORE primaryExc
+ * GOTO e
+ * s:
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * IF_ACMPEQ e
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * e:
+ *
+ * ALOAD r1
+ * IFNULL n
+ * ALOAD r1
+ * INVOKEVIRTUAL close:()V
+ * n:
+ * ALOAD primaryExc
+ * ATHROW
+ *
+ * ASTORE suppressedExc
+ * ALOAD primaryExc
+ * IFNONNULL s
+ * ALOAD suppressedExc
+ * ASTORE primaryExc
+ * GOTO e
+ * s:
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * IF_ACMPEQ e
+ * ALOAD primaryExc
+ * ALOAD suppressedExc
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * e:
+ *
+ * ALOAD primaryExc
+ * ATHROW
+ *
+ * ... // additional handlers for catch blocks and finally on exceptional path
+ * </pre>
+ */
+ @Test
+ public void ecj_noFlowOut() {
+ final Range range0 = new Range();
+ final Range range1 = new Range();
+
+ final Label handler = new Label();
+ m.visitTryCatchBlock(handler, handler, handler, null);
+
+ // primaryExc = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ // suppressedExc = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+
+ // body
+ m.visitInsn(Opcodes.NOP);
+
+ { // nextIsEcjClose("r0")
+ final Label label = new Label();
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ range0.fromInclusive = m.instructions.getLast();
+ m.visitJumpInsn(Opcodes.IFNULL, label);
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource", "close",
+ "()V", false);
+ m.visitLabel(label);
+ }
+ { // nextIsEcjClose("r1")
+ final Label label = new Label();
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitJumpInsn(Opcodes.IFNULL, label);
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource", "close",
+ "()V", false);
+ m.visitLabel(label);
+ }
+ { // nextIsEcjClose("r2")
+ final Label label = new Label();
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitJumpInsn(Opcodes.IFNULL, label);
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource", "close",
+ "()V", false);
+ range0.toInclusive = m.instructions.getLast();
+ m.visitLabel(label);
+ }
+
+ // finally
+ m.visitInsn(Opcodes.NOP);
+ m.visitInsn(Opcodes.ARETURN);
+
+ // catch (any primaryExc)
+ m.visitLabel(handler);
+ range1.fromInclusive = m.instructions.getLast();
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ { // nextIsEcjCloseAndThrow("r0")
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ final Label throwLabel = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, throwLabel);
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource", "close",
+ "()V", false);
+ m.visitLabel(throwLabel);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitInsn(Opcodes.ATHROW);
+ }
+ { // nextIsEcjSuppress
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ final Label suppressStart = new Label();
+ m.visitJumpInsn(Opcodes.IFNONNULL, suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ final Label suppressEnd = new Label();
+ m.visitJumpInsn(Opcodes.GOTO, suppressEnd);
+ m.visitLabel(suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitJumpInsn(Opcodes.IF_ACMPEQ, suppressEnd);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitLabel(suppressEnd);
+ }
+ { // nextIsEcjCloseAndThrow("r1")
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ final Label throwLabel = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, throwLabel);
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource", "close",
+ "()V", false);
+ m.visitLabel(throwLabel);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitInsn(Opcodes.ATHROW);
+ }
+ { // nextIsEcjSuppress
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ final Label suppressStart = new Label();
+ m.visitJumpInsn(Opcodes.IFNONNULL, suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ final Label suppressEnd = new Label();
+ m.visitJumpInsn(Opcodes.GOTO, suppressEnd);
+ m.visitLabel(suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitJumpInsn(Opcodes.IF_ACMPEQ, suppressEnd);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitLabel(suppressEnd);
+ }
+ { // nextIsEcjCloseAndThrow("r2")
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ final Label throwLabel = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, throwLabel);
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource", "close",
+ "()V", false);
+ m.visitLabel(throwLabel);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitInsn(Opcodes.ATHROW);
+ }
+ { // nextIsEcjSuppress
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ final Label suppressStart = new Label();
+ m.visitJumpInsn(Opcodes.IFNONNULL, suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+ final Label suppressEnd = new Label();
+ m.visitJumpInsn(Opcodes.GOTO, suppressEnd);
+ m.visitLabel(suppressStart);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitJumpInsn(Opcodes.IF_ACMPEQ, suppressEnd);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitLabel(suppressEnd);
+ }
+ // throw primaryExc
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitInsn(Opcodes.ATHROW);
+ range1.toInclusive = m.instructions.getLast();
+
+ // additional handlers
+ m.visitInsn(Opcodes.NOP);
+
+ filter.filter("Foo", "java/lang/Object", m, this);
+
+ assertEquals(2, from.size());
+
+ assertEquals(range0.fromInclusive, from.get(0));
+ assertEquals(range0.toInclusive, to.get(0));
+
+ assertEquals(range1.fromInclusive, from.get(1));
+ assertEquals(range1.toInclusive, to.get(1));
+ }
+
+ static class Range {
+ AbstractInsnNode fromInclusive;
+ AbstractInsnNode toInclusive;
+ }
+
+ private final List<AbstractInsnNode> from = new ArrayList<AbstractInsnNode>();
+ private final List<AbstractInsnNode> to = new ArrayList<AbstractInsnNode>();
+
+ public void ignore(AbstractInsnNode from, AbstractInsnNode to) {
+ this.from.add(from);
+ this.to.add(to);
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilterTest.java
new file mode 100644
index 00000000..26b4cfe1
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilterTest.java
@@ -0,0 +1,814 @@
+/*******************************************************************************
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jacoco.core.internal.instr.InstrSupport;
+import org.junit.Test;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+public class TryWithResourcesJavacFilterTest implements IFilterOutput {
+
+ private final TryWithResourcesJavacFilter filter = new TryWithResourcesJavacFilter();
+
+ private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
+ "name", "()V", null, null);
+
+ /**
+ * javac 9 for
+ *
+ * <pre>
+ * try (r0 = open(...); r1 = new ...) {
+ * return ...
+ * } finally {
+ * ...
+ * }
+ * </pre>
+ *
+ * generates
+ *
+ * <pre>
+ * ...
+ * ASTORE r0
+ * ACONST_NULL
+ * ASTORE primaryExc0
+ *
+ * ...
+ * ASTORE r1
+ * ACONST_NULL
+ * ASTORE primaryExc1
+ *
+ * ... // body
+ *
+ * ALOAD primaryExc1
+ * ALOAD r1
+ * INVOKESTATIC $closeResource:(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V
+ *
+ * ALOAD r0
+ * IFNULL n
+ * ALOAD primaryExc0
+ * ALOAD r0
+ * INVOKESTATIC $closeResource:(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V
+ * n:
+ *
+ * ... // finally on normal path
+ * ARETURN
+ *
+ * ASTORE t
+ * ALOAD t
+ * ASTORE primaryExc1
+ * ALOAD t
+ * ATHROW
+ *
+ * ASTORE t
+ * ALOAD primaryExc1
+ * ALOAD r1
+ * INVOKESTATIC $closeResource:(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V
+ * ALOAD t
+ * ATHROW
+ *
+ * ASTORE t
+ * ALOAD t
+ * ASTORE primaryExc0
+ * ALOAD t
+ * ATHROW
+ *
+ * ASTORE t
+ * ALOAD r0
+ * IFNULL n
+ * ALOAD primaryExc0
+ * ALOAD r0
+ * INVOKESTATIC $closeResource:(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V
+ * n:
+ * ALOAD t
+ * ATHROW
+ *
+ * ... // additional handlers for catch blocks and finally on exceptional path
+ * </pre>
+ */
+ @Test
+ public void javac9() {
+ final Range range0 = new Range();
+ final Range range1 = new Range();
+ final Range range2 = new Range();
+ final Range range3 = new Range();
+
+ final Label handler1 = new Label();
+ m.visitTryCatchBlock(handler1, handler1, handler1,
+ "java/lang/Throwable");
+
+ final Label handler2 = new Label();
+ m.visitTryCatchBlock(handler2, handler2, handler2,
+ "java/lang/Throwable");
+
+ // r0 = open(...)
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+
+ // primaryExc0 = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+
+ // r1 = new ..
+ m.visitVarInsn(Opcodes.ASTORE, 3);
+
+ // primaryExc1 = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 4);
+
+ // body
+ m.visitInsn(Opcodes.NOP);
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 5);
+
+ // $closeResource(primaryExc1, r1)
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ range0.fromInclusive = m.instructions.getLast();
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKESTATIC, "Fun", "$closeResource",
+ "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V", false);
+ range0.toInclusive = m.instructions.getLast();
+
+ // if (r0 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ range2.fromInclusive = m.instructions.getLast();
+ final Label l11 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l11);
+ // $closeResource(primaryExc0, r0)
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKESTATIC, "Fun", "$closeResource",
+ "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V", false);
+ range2.toInclusive = m.instructions.getLast();
+ m.visitLabel(l11);
+
+ // finally
+ m.visitInsn(Opcodes.NOP);
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitInsn(Opcodes.ARETURN);
+
+ // catch (Throwable t)
+ m.visitLabel(handler1);
+ range1.fromInclusive = m.instructions.getLast();
+ m.visitVarInsn(Opcodes.ASTORE, 5);
+ // primaryExc1 = t
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitVarInsn(Opcodes.ASTORE, 4);
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitInsn(Opcodes.ATHROW);
+
+ // catch (any t)
+ m.visitVarInsn(Opcodes.ASTORE, 6);
+ // $closeResource(primaryExc1, r1)
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKESTATIC, "Fun", "$closeResource",
+ "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V", false);
+ m.visitVarInsn(Opcodes.ALOAD, 6);
+ m.visitInsn(Opcodes.ATHROW);
+ range1.toInclusive = m.instructions.getLast();
+
+ // catch (Throwable t)
+ m.visitLabel(handler2);
+ range3.fromInclusive = m.instructions.getLast();
+ m.visitVarInsn(Opcodes.ASTORE, 3);
+ // primaryExc0 = t
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitInsn(Opcodes.ATHROW);
+
+ // catch (any t)
+ m.visitVarInsn(Opcodes.ASTORE, 7);
+ // if (r0 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ final Label l14 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l14);
+ // $closeResource(primaryExc0, r0)
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKESTATIC, "Fun", "$closeResource",
+ "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V", false);
+ m.visitLabel(l14);
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 7);
+ m.visitInsn(Opcodes.ATHROW);
+ range3.toInclusive = m.instructions.getLast();
+
+ m.visitVarInsn(Opcodes.ASTORE, 8);
+ // finally
+ m.visitInsn(Opcodes.NOP);
+ m.visitVarInsn(Opcodes.ALOAD, 8);
+ m.visitInsn(Opcodes.ATHROW);
+
+ filter.filter("Foo", "java/lang/Object", m, this);
+
+ assertEquals(4, from.size());
+
+ assertEquals(range0.fromInclusive, from.get(0));
+ assertEquals(range0.toInclusive, to.get(0));
+
+ assertEquals(range1.fromInclusive, from.get(1));
+ assertEquals(range1.toInclusive, to.get(1));
+
+ assertEquals(range2.fromInclusive, from.get(2));
+ assertEquals(range2.toInclusive, to.get(2));
+
+ assertEquals(range3.fromInclusive, from.get(3));
+ assertEquals(range3.toInclusive, to.get(3));
+ }
+
+ /**
+ * javac 7 and 8 for
+ *
+ * <pre>
+ * try (r0 = ...; r1 = ...) {
+ * return ...
+ * } finally {
+ * ...
+ * }
+ * </pre>
+ *
+ * generate
+ *
+ * <pre>
+ * ...
+ * ASTORE r0
+ * ACONST_NULL
+ * ASTORE primaryExc0
+ *
+ * ...
+ * ASTORE r1
+ * ACONST_NULL
+ * ASTORE primaryExc1
+ *
+ * ... // body
+ *
+ * ALOAD r1
+ * IFNULL n
+ * ALOAD primaryExc1
+ * IFNULL c
+ * ALOAD r1
+ * INVOKEINTERFACE close:()V
+ * GOTO n
+ * ASTORE t
+ * ALOAD primaryExc1
+ * ALOAD t
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * GOTO n
+ * c:
+ * ALOAD r1
+ * INVOKEINTERFACE close:()V
+ * n:
+ *
+ * ALOAD r0
+ * IFNULL n
+ * ALOAD primaryExc0
+ * IFNULL c
+ * ALOAD r0
+ * INVOKEVIRTUAL close:()V
+ * GOTO n
+ * ASTORE t
+ * ALOAD primaryExc0
+ * ALOAD t
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * GOTO n
+ * c:
+ * ALOAD r0
+ * INVOKEVIRTUAL close:()V
+ * n:
+ *
+ * ... // finally on normal path
+ * ARETURN
+ *
+ * ASTORE t
+ * ALOAD t
+ * ASTORE primaryExc1
+ * ALOAD t
+ * ATHROW
+ *
+ * ASTORE t1
+ * ALOAD r1
+ * IFNULL e
+ * ALOAD primaryExc1
+ * IFNULL c
+ * ALOAD r1
+ * INVOKEINTERFACE close:()V
+ * GOTO e
+ * ASTORE t2
+ * ALOAD primaryExc1
+ * ALOAD t2
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * GOTO e
+ * c:
+ * ALOAD r1
+ * INVOKEINTERFACE close:()V
+ * e:
+ * ALOAD t1
+ * ATHROW
+ *
+ * ASTORE t
+ * ALOAD t
+ * ASTORE primaryExc0
+ * ALOAD t
+ * ATHROW
+ *
+ * ASTORE t1
+ * ALOAD r0
+ * IFNULL e
+ * ALOAD primaryExc0
+ * IFNULL c
+ * ALOAD r0
+ * INVOKEVIRTUAL close:()V
+ * GOTO e
+ * ASTORE t2
+ * ALOAD primaryExc0
+ * ALOAD t2
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * GOTO e
+ * c:
+ * ALOAD r0
+ * INVOKEVIRTUAL close:()V
+ * e:
+ * ALOAD t1
+ * ATHROW
+ *
+ * ... // additional handlers for catch blocks and finally on exceptional path
+ * </pre>
+ */
+ @Test
+ public void javac_7_8() {
+ final Range range0 = new Range();
+ final Range range1 = new Range();
+ final Range range2 = new Range();
+ final Range range3 = new Range();
+
+ final Label handler1 = new Label();
+ m.visitTryCatchBlock(handler1, handler1, handler1,
+ "java/lang/Throwable");
+ final Label handler2 = new Label();
+ m.visitTryCatchBlock(handler2, handler2, handler2,
+ "java/lang/Throwable");
+
+ // r1 = ...
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+
+ // primaryExc1 = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+
+ // r2 = ...
+ m.visitVarInsn(Opcodes.ASTORE, 3);
+ // primaryExc2 = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 4);
+
+ // body
+ m.visitInsn(Opcodes.NOP);
+
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 5);
+
+ final Label l15 = new Label();
+ // if (r2 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ range0.fromInclusive = m.instructions.getLast();
+ m.visitJumpInsn(Opcodes.IFNULL, l15);
+ // if (primaryExc2 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ final Label l26 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l26);
+ // r2.close
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Fun$Resource2", "close",
+ "()V", false);
+ m.visitJumpInsn(Opcodes.GOTO, l15);
+
+ m.visitVarInsn(Opcodes.ASTORE, 6);
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitVarInsn(Opcodes.ALOAD, 6);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitJumpInsn(Opcodes.GOTO, l15);
+
+ m.visitLabel(l26);
+
+ // r2.close
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Fun$Resource2", "close",
+ "()V", false);
+ range0.toInclusive = m.instructions.getLast();
+ m.visitLabel(l15);
+
+ // if (r1 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ range2.fromInclusive = m.instructions.getLast();
+ final Label l23 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l23);
+ // if (primaryExc1 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ final Label l27 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l27);
+ // r1.close
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource1", "close",
+ "()V", false);
+ m.visitJumpInsn(Opcodes.GOTO, l23);
+
+ m.visitVarInsn(Opcodes.ASTORE, 6);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 6);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitJumpInsn(Opcodes.GOTO, l23);
+
+ m.visitLabel(l27);
+ // r1.close
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource1", "close",
+ "()V", false);
+ range2.toInclusive = m.instructions.getLast();
+ m.visitLabel(l23);
+
+ // finally
+ m.visitInsn(Opcodes.NOP);
+ m.visitInsn(Opcodes.ARETURN);
+
+ // catch (Throwable t)
+ m.visitLabel(handler1);
+ range1.fromInclusive = m.instructions.getLast();
+ m.visitVarInsn(Opcodes.ASTORE, 5);
+ // primaryExc2 = t
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitVarInsn(Opcodes.ASTORE, 4);
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitInsn(Opcodes.ATHROW);
+
+ // catch (any t)
+ m.visitVarInsn(Opcodes.ASTORE, 7);
+ // if (r2 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ final Label l28 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l28);
+ // if (primaryExc2 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ final Label l29 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l29);
+ // r2.close
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Fun$Resource2", "close",
+ "()V", false);
+ m.visitJumpInsn(Opcodes.GOTO, l28);
+
+ m.visitVarInsn(Opcodes.ASTORE, 8);
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitVarInsn(Opcodes.ALOAD, 8);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitJumpInsn(Opcodes.GOTO, l28);
+
+ m.visitLabel(l29);
+ // r2.close
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Fun$Resource2", "close",
+ "()V", false);
+ m.visitLabel(l28);
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 7);
+ m.visitInsn(Opcodes.ATHROW);
+ range1.toInclusive = m.instructions.getLast();
+
+ // catch (Throwable t)
+ m.visitLabel(handler2);
+ range3.fromInclusive = m.instructions.getLast();
+ m.visitVarInsn(Opcodes.ASTORE, 3);
+ // primaryExc2 = t
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitInsn(Opcodes.ATHROW);
+
+ // catch (any t)
+ m.visitVarInsn(Opcodes.ASTORE, 9);
+ // if (r1 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ final Label l30 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l30);
+ // if (primaryExc1 != null)
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ final Label l31 = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, l31);
+ // r1.close
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource1", "close",
+ "()V", false);
+ m.visitJumpInsn(Opcodes.GOTO, l30);
+
+ m.visitVarInsn(Opcodes.ASTORE, 10);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 10);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitJumpInsn(Opcodes.GOTO, l30);
+
+ m.visitLabel(l31);
+ // r1.close
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Fun$Resource1", "close",
+ "()V", false);
+ m.visitLabel(l30);
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 9);
+ m.visitInsn(Opcodes.ATHROW);
+ range3.toInclusive = m.instructions.getLast();
+
+ m.visitVarInsn(Opcodes.ASTORE, 11);
+ // finally
+ m.visitInsn(Opcodes.NOP);
+ m.visitVarInsn(Opcodes.ALOAD, 11);
+ m.visitInsn(Opcodes.ATHROW);
+
+ filter.filter("Foo", "java/lang/Object", m, this);
+
+ assertEquals(4, from.size());
+
+ assertEquals(range0.fromInclusive, from.get(0));
+ assertEquals(range0.toInclusive, to.get(0));
+
+ assertEquals(range1.fromInclusive, from.get(1));
+ assertEquals(range1.toInclusive, to.get(1));
+
+ assertEquals(range2.fromInclusive, from.get(2));
+ assertEquals(range2.toInclusive, to.get(2));
+
+ assertEquals(range3.fromInclusive, from.get(3));
+ assertEquals(range3.toInclusive, to.get(3));
+ }
+
+ /**
+ * javac 9 for
+ *
+ * <pre>
+ * try (r = new ...) {
+ * ...
+ * } finally {
+ * ...
+ * }
+ * </pre>
+ *
+ * generates
+ *
+ * <pre>
+ * ...
+ * ASTORE r
+ * ACONST_NULL
+ * ASTORE primaryExc
+ *
+ * ... // body
+ *
+ * ALOAD primaryExc
+ * IFNULL c
+ * ALOAD r
+ * INVOKEVIRTUAL close:()V
+ * GOTO f
+ * ASTORE t
+ * ALOAD primaryExc
+ * ALOAD t
+ * NVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * GOTO f
+ * c:
+ * ALOAD r
+ * INVOKEVIRTUAL close:()V
+ * GOTO f
+ *
+ * ASTORE t
+ * ALOAD t
+ * ASTORE primaryExc
+ * ALOAD t
+ * ATHROW
+ *
+ * ASTORE t
+ * ALOAD primaryExc
+ * IFNULL c
+ * ALOAD r
+ * INVOKEVIRTUAL close:()V
+ * GOTO L78
+ * ASTORE t2
+ * ALOAD primaryExc
+ * ALOAD t2
+ * INVOKEVIRTUAL java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+ * goto e
+ * c:
+ * ALOAD r
+ * INVOKEVIRTUAL close:()V
+ * e:
+ * ALOAD t
+ * ATHROW
+ *
+ * f:
+ * ... // finally on normal path
+ * ... // additional handlers for catch blocks and finally on exceptional path
+ * ...
+ * </pre>
+ */
+ @Test
+ public void javac9_omitted_null_check() {
+ final Range range0 = new Range();
+ final Range range1 = new Range();
+
+ final Label handler = new Label();
+ m.visitTryCatchBlock(handler, handler, handler, "java/lang/Throwable");
+
+ // primaryExc = null
+ m.visitInsn(Opcodes.ACONST_NULL);
+ m.visitVarInsn(Opcodes.ASTORE, 1);
+
+ // r = new ...
+ m.visitInsn(Opcodes.NOP);
+
+ final Label end = new Label();
+ // "finally" on a normal path
+ {
+ // if (primaryExc != null)
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ range0.fromInclusive = m.instructions.getLast();
+ final Label closeLabel = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, closeLabel);
+ // r.close
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Resource", "close", "()V",
+ false);
+ m.visitJumpInsn(Opcodes.GOTO, end);
+
+ // catch (Throwable t)
+ m.visitVarInsn(Opcodes.ASTORE, 3);
+ // primaryExc.addSuppressed(t)
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitJumpInsn(Opcodes.GOTO, end);
+
+ m.visitLabel(closeLabel);
+ // r.close()
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Resource", "close", "()V",
+ false);
+ }
+ m.visitJumpInsn(Opcodes.GOTO, end);
+ range0.toInclusive = m.instructions.getLast();
+ // catch (Throwable t)
+ m.visitLabel(handler);
+ {
+ range1.fromInclusive = m.instructions.getLast();
+ m.visitVarInsn(Opcodes.ASTORE, 3);
+ // primaryExc = t
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitInsn(Opcodes.ATHROW);
+ }
+ // catch (any t)
+ m.visitVarInsn(Opcodes.ASTORE, 4);
+ // "finally" on exceptional path
+ {
+ // if (primaryExc != null)
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ final Label closeLabel = new Label();
+ m.visitJumpInsn(Opcodes.IFNULL, closeLabel);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Resource", "close", "()V",
+ false);
+ final Label finallyEndLabel = new Label();
+ m.visitJumpInsn(Opcodes.GOTO, finallyEndLabel);
+
+ // catch (Throwable t)
+ m.visitVarInsn(Opcodes.ASTORE, 5);
+ // primaryExc.addSuppressed(t)
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 5);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
+ "addSuppressed", "(Ljava/lang/Throwable;)V", false);
+ m.visitJumpInsn(Opcodes.GOTO, finallyEndLabel);
+
+ m.visitLabel(closeLabel);
+ // r.close()
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Resource", "close", "()V",
+ false);
+ m.visitLabel(finallyEndLabel);
+ }
+ // throw t
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitInsn(Opcodes.ATHROW);
+ range1.toInclusive = m.instructions.getLast();
+
+ m.visitLabel(end);
+
+ filter.filter("Foo", "java/lang/Object", m, this);
+
+ assertEquals(2, from.size());
+
+ assertEquals(range0.fromInclusive, from.get(0));
+ assertEquals(range0.toInclusive, to.get(0));
+
+ assertEquals(range1.fromInclusive, from.get(1));
+ assertEquals(range1.toInclusive, to.get(1));
+ }
+
+ /**
+ * javac 9 for
+ *
+ * <pre>
+ * try (r = new ...) {
+ * throw ...
+ * }
+ * </pre>
+ *
+ * generates
+ *
+ * <pre>
+ * ...
+ * ASTORE r
+ * ACONST_NULL
+ * ASTORE primaryExc
+ *
+ * ...
+ * ATHROW
+ *
+ * ASTORE t
+ * ALOAD t
+ * ASTORE primaryExc
+ * ALOAD t
+ * ATHROW
+ *
+ * ASTORE t
+ * ALOAD primaryExc
+ * ALOAD r
+ * INVOKESTATIC $closeResource:(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V
+ * ALOAD t
+ * ATHROW
+ * </pre>
+ */
+ @Test
+ public void only_exceptional_path() {
+ final Label start = new Label();
+ final Label handler = new Label();
+ m.visitTryCatchBlock(start, handler, handler, "java/lang/Throwable");
+
+ m.visitLabel(start);
+ m.visitInsn(Opcodes.ATHROW);
+ m.visitLabel(handler);
+ m.visitVarInsn(Opcodes.ASTORE, 3);
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitVarInsn(Opcodes.ASTORE, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 3);
+ m.visitInsn(Opcodes.ATHROW);
+
+ m.visitVarInsn(Opcodes.ASTORE, 4);
+ m.visitVarInsn(Opcodes.ALOAD, 2);
+ m.visitVarInsn(Opcodes.ALOAD, 1);
+ m.visitMethodInsn(Opcodes.INVOKESTATIC, "Fun", "$closeResource",
+ "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V", false);
+ m.visitVarInsn(Opcodes.ALOAD, 4);
+ m.visitInsn(Opcodes.ATHROW);
+
+ filter.filter("Foo", "java/lang/Object", m, this);
+
+ assertEquals(0, from.size());
+ }
+
+ static class Range {
+ AbstractInsnNode fromInclusive;
+ AbstractInsnNode toInclusive;
+ }
+
+ private final List<AbstractInsnNode> from = new ArrayList<AbstractInsnNode>();
+ private final List<AbstractInsnNode> to = new ArrayList<AbstractInsnNode>();
+
+ public void ignore(AbstractInsnNode from, AbstractInsnNode to) {
+ this.from.add(from);
+ this.to.add(to);
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java
index 993fe88a..ee051436 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java
@@ -25,6 +25,8 @@ import org.jacoco.core.internal.analysis.filter.IFilterOutput;
import org.jacoco.core.internal.analysis.filter.LombokGeneratedFilter;
import org.jacoco.core.internal.analysis.filter.SynchronizedFilter;
import org.jacoco.core.internal.analysis.filter.SyntheticFilter;
+import org.jacoco.core.internal.analysis.filter.TryWithResourcesEcjFilter;
+import org.jacoco.core.internal.analysis.filter.TryWithResourcesJavacFilter;
import org.jacoco.core.internal.flow.IFrame;
import org.jacoco.core.internal.flow.Instruction;
import org.jacoco.core.internal.flow.LabelInfo;
@@ -45,6 +47,7 @@ public class MethodAnalyzer extends MethodProbesVisitor
private static final IFilter[] FILTERS = new IFilter[] { new EnumFilter(),
new SyntheticFilter(), new SynchronizedFilter(),
+ new TryWithResourcesJavacFilter(), new TryWithResourcesEcjFilter(),
new LombokGeneratedFilter() };
private final String className;
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java
new file mode 100644
index 00000000..e4958bdb
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * 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.MethodInsnNode;
+import org.objectweb.asm.tree.VarInsnNode;
+
+abstract class AbstractMatcher {
+
+ final Map<String, VarInsnNode> vars = new HashMap<String, VarInsnNode>();
+
+ AbstractInsnNode cursor;
+
+ final void nextIsAddSuppressed() {
+ nextIs(Opcodes.INVOKEVIRTUAL);
+ if (cursor == null) {
+ return;
+ }
+ final MethodInsnNode m = (MethodInsnNode) cursor;
+ if ("java/lang/Throwable".equals(m.owner)
+ && "addSuppressed".equals(m.name)) {
+ return;
+ }
+ cursor = null;
+ }
+
+ final void nextIsVar(final int opcode, final String name) {
+ nextIs(opcode);
+ if (cursor == null) {
+ return;
+ }
+ final VarInsnNode actual = (VarInsnNode) cursor;
+ final VarInsnNode expected = vars.get(name);
+ if (expected == null) {
+ vars.put(name, actual);
+ } else if (expected.var != actual.var) {
+ cursor = null;
+ }
+ }
+
+ /**
+ * Moves {@link #cursor} to next instruction if it has given opcode,
+ * otherwise sets it to <code>null</code>.
+ */
+ final void nextIs(final int opcode) {
+ next();
+ if (cursor == null) {
+ return;
+ }
+ if (cursor.getOpcode() != opcode) {
+ cursor = null;
+ }
+ }
+
+ /**
+ * Moves {@link #cursor} to next instruction.
+ */
+ final void next() {
+ if (cursor == null) {
+ return;
+ }
+ do {
+ cursor = cursor.getNext();
+ } while (cursor != null && (cursor.getType() == AbstractInsnNode.FRAME
+ || cursor.getType() == AbstractInsnNode.LABEL
+ || cursor.getType() == AbstractInsnNode.LINE));
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java
index 655226e7..eec00948 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java
@@ -38,9 +38,8 @@ public final class SynchronizedFilter implements IFilter {
}
}
- private static class Matcher {
+ private static class Matcher extends AbstractMatcher {
private final AbstractInsnNode start;
- private AbstractInsnNode cursor;
private Matcher(final AbstractInsnNode start) {
this.start = start;
@@ -55,36 +54,20 @@ public final class SynchronizedFilter implements IFilter {
private boolean nextIsJavac() {
cursor = start;
- return nextIs(Opcodes.ASTORE) && nextIs(Opcodes.ALOAD)
- && nextIs(Opcodes.MONITOREXIT) && nextIs(Opcodes.ALOAD)
- && nextIs(Opcodes.ATHROW);
+ nextIsVar(Opcodes.ASTORE, "t");
+ nextIs(Opcodes.ALOAD);
+ nextIs(Opcodes.MONITOREXIT);
+ nextIsVar(Opcodes.ALOAD, "t");
+ nextIs(Opcodes.ATHROW);
+ return cursor != null;
}
private boolean nextIsEcj() {
cursor = start;
- return nextIs(Opcodes.ALOAD) && nextIs(Opcodes.MONITOREXIT)
- && nextIs(Opcodes.ATHROW);
- }
-
- /**
- * Moves {@link #cursor} to next instruction and returns
- * <code>true</code> if it has given opcode.
- */
- private boolean nextIs(int opcode) {
- next();
- return cursor != null && cursor.getOpcode() == opcode;
- }
-
- /**
- * Moves {@link #cursor} to next instruction.
- */
- private void next() {
- do {
- cursor = cursor.getNext();
- } while (cursor != null
- && (cursor.getType() == AbstractInsnNode.FRAME
- || cursor.getType() == AbstractInsnNode.LABEL
- || cursor.getType() == AbstractInsnNode.LINE));
+ nextIs(Opcodes.ALOAD);
+ nextIs(Opcodes.MONITOREXIT);
+ nextIs(Opcodes.ATHROW);
+ return cursor != null;
}
}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java
new file mode 100644
index 00000000..7d7d396c
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java
@@ -0,0 +1,265 @@
+/*******************************************************************************
+ * 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<String, String> owners = new HashMap<String, String>();
+ private final Map<String, LabelNode> labels = new HashMap<String, LabelNode>();
+
+ 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;
+ }
+ }
+
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java
new file mode 100644
index 00000000..02ed47e5
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java
@@ -0,0 +1,253 @@
+/*******************************************************************************
+ * 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 org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+
+/**
+ * Filters code that javac generates for try-with-resources statement.
+ */
+public final class TryWithResourcesJavacFilter 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 ("java/lang/Throwable".equals(t.type)) {
+ for (Matcher.JavacPattern p : Matcher.JavacPattern.values()) {
+ matcher.start(t.handler);
+ if (matcher.matchJavac(p)) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * javac from JDK 7 and 8 generates bytecode that is equivalent to the
+ * compilation of source code that is described in <a href=
+ * "http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20.3.1">JLS
+ * 14.20.3. try-with-resources</a>:
+ *
+ * <pre>
+ * Resource r = ...;
+ * Throwable primaryExc = null;
+ * try {
+ * ...
+ * } finally {
+ * if (r != null) {
+ * if (primaryExc != null) {
+ * try {
+ * r.close();
+ * } catch (Throwable suppressedExc) {
+ * primaryExc.addSuppressed(suppressedExc);
+ * }
+ * } else {
+ * r.close();
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * Case of multiple resources looks like multiple nested try-with-resources
+ * statements. javac from JDK 9 EA b160 does the same, but with some
+ * optimizations (see <a href=
+ * "https://bugs.openjdk.java.net/browse/JDK-7020499">JDK-7020499</a>):
+ * <ul>
+ * <li><code>null</code> check for resource is omitted when it is
+ * initialized using <code>new</code></li>
+ * <li>synthetic method <code>$closeResource</code> containing
+ * <code>null</code> check of primaryExc and calls to methods
+ * <code>addSuppressed</code> and <code>close</code> is used when number of
+ * copies of closing logic reaches threshold, <code>null</code> check of
+ * resource (if present) is done before call of this method</li>
+ * </ul>
+ * During matching association between resource and slot of variable is done
+ * on exceptional path and is used to find close of resource on normal path.
+ * Order of loading variables primaryExc and r is different in different
+ * cases, which implies that this order should be determined before
+ * association. So {@link JavacPattern} defines all possible variants that
+ * will be tried sequentially.
+ */
+ static class Matcher extends AbstractMatcher {
+ private final IFilterOutput output;
+
+ private String expectedOwner;
+
+ private AbstractInsnNode start;
+
+ Matcher(final IFilterOutput output) {
+ this.output = output;
+ }
+
+ private enum JavacPattern {
+ /**
+ * resource is loaded after primaryExc, <code>null</code> check of
+ * resource is omitted, method <code>$closeResource</code> is used
+ */
+ OPTIMAL,
+ /**
+ * resource is loaded before primaryExc and both are checked on
+ * <code>null</code>
+ */
+ FULL,
+ /**
+ * resource is loaded after primaryExc, <code>null</code> check of
+ * resource is omitted
+ */
+ OMITTED_NULL_CHECK,
+ /**
+ * resource is loaded before primaryExc and checked on
+ * <code>null</code>, method <code>$closeResource</code> is used
+ */
+ METHOD,
+ }
+
+ private void start(final AbstractInsnNode start) {
+ this.start = start;
+ cursor = start.getPrevious();
+ vars.clear();
+ expectedOwner = null;
+ }
+
+ private boolean matchJavac(final JavacPattern p) {
+ // "catch (Throwable t)"
+ nextIsVar(Opcodes.ASTORE, "t1");
+ // "primaryExc = t"
+ nextIsVar(Opcodes.ALOAD, "t1");
+ nextIsVar(Opcodes.ASTORE, "primaryExc");
+ // "throw t"
+ nextIsVar(Opcodes.ALOAD, "t1");
+ nextIs(Opcodes.ATHROW);
+
+ // "catch (any t)"
+ nextIsVar(Opcodes.ASTORE, "t2");
+ nextIsJavacClose(p, "e");
+ // "throw t"
+ nextIsVar(Opcodes.ALOAD, "t2");
+ nextIs(Opcodes.ATHROW);
+ if (cursor == null) {
+ return false;
+ }
+ final AbstractInsnNode end = cursor;
+
+ AbstractInsnNode startOnNonExceptionalPath = start.getPrevious();
+ cursor = startOnNonExceptionalPath;
+ while (!nextIsJavacClose(p, "n")) {
+ startOnNonExceptionalPath = startOnNonExceptionalPath
+ .getPrevious();
+ cursor = startOnNonExceptionalPath;
+ if (cursor == null) {
+ return false;
+ }
+ }
+ startOnNonExceptionalPath = startOnNonExceptionalPath.getNext();
+
+ final AbstractInsnNode m = cursor;
+ next();
+ if (cursor.getOpcode() != Opcodes.GOTO) {
+ cursor = m;
+ }
+
+ output.ignore(startOnNonExceptionalPath, cursor);
+ output.ignore(start, end);
+ return true;
+ }
+
+ /**
+ * On a first invocation will associate variables with names "r" and
+ * "primaryExc", on subsequent invocations will use those associations
+ * for checks.
+ */
+ private boolean nextIsJavacClose(final JavacPattern p,
+ final String ctx) {
+ switch (p) {
+ case METHOD:
+ case FULL:
+ // "if (r != null)"
+ nextIsVar(Opcodes.ALOAD, "r");
+ nextIs(Opcodes.IFNULL);
+ }
+ switch (p) {
+ case METHOD:
+ case OPTIMAL:
+ nextIsVar(Opcodes.ALOAD, "primaryExc");
+ nextIsVar(Opcodes.ALOAD, "r");
+ nextIs(Opcodes.INVOKESTATIC);
+ if (cursor != null) {
+ final MethodInsnNode m = (MethodInsnNode) cursor;
+ if ("$closeResource".equals(m.name)
+ && "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V"
+ .equals(m.desc)) {
+ return true;
+ }
+ cursor = null;
+ }
+ return false;
+ case FULL:
+ case OMITTED_NULL_CHECK:
+ nextIsVar(Opcodes.ALOAD, "primaryExc");
+ // "if (primaryExc != null)"
+ nextIs(Opcodes.IFNULL);
+ // "r.close()"
+ nextIsClose();
+ nextIs(Opcodes.GOTO);
+ // "catch (Throwable t)"
+ nextIsVar(Opcodes.ASTORE, ctx + "t");
+ // "primaryExc.addSuppressed(t)"
+ nextIsVar(Opcodes.ALOAD, "primaryExc");
+ nextIsVar(Opcodes.ALOAD, ctx + "t");
+ nextIsAddSuppressed();
+ nextIs(Opcodes.GOTO);
+ // "r.close()"
+ nextIsClose();
+ return cursor != null;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private void nextIsClose() {
+ nextIsVar(Opcodes.ALOAD, "r");
+ 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;
+ if (expectedOwner == null) {
+ expectedOwner = actual;
+ } else if (!expectedOwner.equals(actual)) {
+ cursor = null;
+ }
+ }
+
+ }
+
+}
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 43e42aa4..604b5379 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -28,6 +28,9 @@
<li>Exclude from a report a part of bytecode that compiler generates for a
synchronized statement
(GitHub <a href="https://github.com/jacoco/jacoco/issues/501">#501</a>).</li>
+ <li>Exclude from a report a part of bytecode that compiler generates for a
+ try-with-resources statement
+ (GitHub <a href="https://github.com/jacoco/jacoco/issues/500">#500</a>).</li>
<li>Exclude from a report methods which are annotated with <code>@lombok.Generated</code>.
Initial analysis and contribution by RĂ¼diger zu Dohna.
(GitHub <a href="https://github.com/jacoco/jacoco/issues/513">#513</a>).</li>