aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.core.test
diff options
context:
space:
mode:
Diffstat (limited to 'org.jacoco.core.test')
-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
5 files changed, 1861 insertions, 0 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);
+ }
+
+}