Desugaring of try-with-resources.

Adds desugaring of try-with-resources for pre-19 min-sdk
version, it essentially boils down to removing all calls to
Throwable::addSuppressed(...) and replacing all calls to
Throwable::getSuppressed() with empty array of Throwable.

The desugaring requires library and class path since it needs
to inspect/check exception class hierarchy. Disabling it by
default until we measure performance impact.

Bug:37744723

BUG=

Change-Id: If2781ec68cab3a2b38127ee20fffb94ceac3157a
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResources.java b/src/test/examplesAndroidO/trywithresources/TryWithResources.java
new file mode 100644
index 0000000..ac779d6
--- /dev/null
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResources.java
@@ -0,0 +1,237 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package trywithresources;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+public abstract class TryWithResources {
+  // --- TEST SUPPORT ---
+
+  interface Test {
+    void test() throws Throwable;
+  }
+
+  private void test(Test test) {
+    try {
+      test.test();
+    } catch (Throwable e) {
+      dumpException(e);
+    }
+  }
+
+  private void dumpException(Throwable e) {
+    dumpException(e, "Exception: ");
+  }
+
+  private void dumpException(Throwable e, String indent) {
+    assert e != null;
+    System.out.println(indent + e.getMessage());
+
+    indent = indent.replaceAll("[^:]", " ");
+
+    Throwable cause = e.getCause();
+    if (cause != null) {
+      dumpException(cause, indent + "  cause: ");
+    }
+
+    // Dump suppressed UNLESS it is a desugared code running
+    // on JVM, in which case we avoid dumping suppressed, since
+    // the output will be used for comparison with desugared code
+    // running on device.
+    if (!desugaredCodeRunningOnJvm()) {
+      Throwable[] suppressed = e.getSuppressed();
+      for (int i = 0; i < suppressed.length; i++) {
+        dumpException(suppressed[i], indent + "supp[" + i + "]: ");
+      }
+    }
+  }
+
+  abstract boolean desugaredCodeRunningOnJvm();
+
+  // --- TEST SYMBOLS ---
+
+  static class Resource implements Closeable {
+    final String tag;
+
+    Resource(String tag) {
+      this.tag = tag;
+    }
+
+    @Override
+    public void close() throws IOException {
+      Class<? extends Resource> cls = this.getClass();
+      System.out.println("Closing " + tag + " (" +
+          cls.getName().substring(TryWithResources.class.getName().length() + 1) + ")");
+    }
+  }
+
+  // --- TEST ---
+
+  class RegularTryWithResources {
+    class RegularResource extends Resource {
+      RegularResource(String tag) {
+        super(tag);
+      }
+    }
+
+    private void test() throws Throwable {
+      test(2);
+    }
+
+    private void test(int level) throws Throwable {
+      try (RegularResource a = new RegularResource("a" + level);
+           RegularResource b = new RegularResource("b" + level)) {
+        if (level > 0) {
+          try {
+            test(level - 1);
+          } catch (Throwable e) {
+            throw new RuntimeException("e" + level, e);
+          }
+        }
+        throw new RuntimeException("primary cause");
+      }
+    }
+  }
+
+  // --- TEST ---
+
+  class FailingTryWithResources {
+    class FailingResource extends Resource {
+      FailingResource(String tag) {
+        super(tag);
+      }
+
+      @Override
+      public void close() throws IOException {
+        super.close();
+        throw new RuntimeException("failed to close '" + tag + "'");
+      }
+    }
+
+    private void test() throws Throwable {
+      test(2);
+    }
+
+    private void test(int level) throws Throwable {
+      try (FailingResource a = new FailingResource("a" + level);
+           FailingResource b = new FailingResource("b" + level)) {
+        if (level > 0) {
+          try {
+            test(level - 1);
+          } catch (Throwable e) {
+            throw new RuntimeException("e" + level, e);
+          }
+        }
+        throw new RuntimeException("primary cause");
+      }
+    }
+  }
+
+  // --- TEST ---
+
+  class ExplicitAddGetSuppressed {
+    class RegularResource extends Resource {
+      RegularResource(String tag) {
+        super(tag);
+      }
+
+      @Override
+      public void close() throws IOException {
+        super.close();
+        throw new RuntimeException("failed to close '" + tag + "'");
+      }
+    }
+
+    private void test() throws Throwable {
+      test(2);
+    }
+
+    private void test(int level) throws RuntimeException {
+      try (RegularResource a = new RegularResource("a" + level);
+           RegularResource b = new RegularResource("b" + level)) {
+        if (level > 0) {
+          try {
+            test(level - 1);
+          } catch (RuntimeException e) {
+            // Just collect suppressed, but throw away the exception.
+            RuntimeException re = new RuntimeException("e" + level);
+            for (Throwable suppressed : e.getSuppressed()) {
+              re.addSuppressed(suppressed);
+            }
+            throw re;
+          }
+        }
+        throw new RuntimeException("primary cause");
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+  // --- TEST ---
+
+  interface Consumer {
+    void act(RuntimeException re);
+  }
+
+  interface Supplier {
+    Throwable[] get();
+  }
+
+  class AddGetSuppressedRoundTrip {
+    private void test() throws Throwable {
+      RuntimeException carrier = new RuntimeException("carrier");
+      Consumer packer = carrier::addSuppressed;
+      Supplier unpacker = carrier::getSuppressed;
+
+      packer.act(new RuntimeException("original exception A"));
+      packer.act(new RuntimeException("original exception Z"));
+
+      for (Throwable unpacked : unpacker.get()) {
+        if (!desugaredCodeRunningOnJvm()) {
+          dumpException(unpacked);
+        }
+      }
+    }
+  }
+
+  // --- TEST ---
+
+  class UnreachableCatchAfterCallsRemoved {
+    private void test() throws Throwable {
+      RuntimeException main = new RuntimeException("main");
+      RuntimeException origA = new RuntimeException("original exception A");
+      RuntimeException origB = new RuntimeException("original exception Z");
+
+      try {
+        // After both calls below are removed, the whole catch
+        // handler should be removed.
+        main.addSuppressed(origA);
+        main.addSuppressed(origB);
+      } catch (Throwable t) {
+        throw new RuntimeException("UNREACHABLE");
+      }
+
+      // Return value not used.
+      main.getSuppressed();
+    }
+  }
+
+  // --- MAIN TEST ---
+
+  void test() throws Exception {
+    System.out.println("----- TEST 1 -----");
+    test(new RegularTryWithResources()::test);
+    System.out.println("----- TEST 2 -----");
+    test(new FailingTryWithResources()::test);
+    System.out.println("----- TEST 3 -----");
+    test(new ExplicitAddGetSuppressed()::test);
+    System.out.println("----- TEST 4 -----");
+    test(new AddGetSuppressedRoundTrip()::test);
+    System.out.println("----- TEST 5 -----");
+    test(new UnreachableCatchAfterCallsRemoved()::test);
+    System.out.println("------------------");
+  }
+}
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java b/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java
new file mode 100644
index 0000000..10a96f9
--- /dev/null
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package trywithresources;
+
+public class TryWithResourcesDesugaredTests extends TryWithResources {
+  private boolean isAndroid() {
+    try {
+      Class.forName("dalvik.system.VMRuntime");
+      return true;
+    } catch (Exception ignored) {
+    }
+    return false;
+  }
+
+  @Override
+  boolean desugaredCodeRunningOnJvm() {
+    return !isAndroid();
+  }
+
+  public static void main(String[] args) throws Exception {
+    new TryWithResourcesDesugaredTests().test();
+  }
+}
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java b/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java
new file mode 100644
index 0000000..d2a05c3
--- /dev/null
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package trywithresources;
+
+public class TryWithResourcesNotDesugaredTests extends TryWithResources {
+  @Override
+  boolean desugaredCodeRunningOnJvm() {
+    return false;
+  }
+
+  public static void main(String[] args) throws Exception {
+    new TryWithResourcesNotDesugaredTests().test();
+  }
+}