1c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// Copyright 2017 The Bazel Authors. All rights reserved.
2c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun//
3c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// Licensed under the Apache License, Version 2.0 (the "License");
4c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// you may not use this file except in compliance with the License.
5c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// You may obtain a copy of the License at
6c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun//
7c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun//    http://www.apache.org/licenses/LICENSE-2.0
8c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun//
9c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// Unless required by applicable law or agreed to in writing, software
10c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// distributed under the License is distributed on an "AS IS" BASIS,
11c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// See the License for the specific language governing permissions and
13c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun// limitations under the License.
14c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunpackage com.google.devtools.build.android.desugar;
15c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
166dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsunimport static com.google.common.base.Preconditions.checkNotNull;
17c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport static org.objectweb.asm.Opcodes.ASM5;
18c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport static org.objectweb.asm.Opcodes.INVOKESTATIC;
19c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
20c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
21c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport com.google.common.base.Function;
22c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport com.google.common.collect.FluentIterable;
23c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport com.google.common.collect.ImmutableMap;
24c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport com.google.common.collect.ImmutableMultimap;
25c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport com.google.common.collect.ImmutableSet;
266dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsunimport java.util.Collections;
276dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsunimport java.util.Set;
28c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport java.util.concurrent.atomic.AtomicInteger;
29c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport org.objectweb.asm.ClassVisitor;
306dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsunimport org.objectweb.asm.Label;
31c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunimport org.objectweb.asm.MethodVisitor;
32c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
33c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun/**
34c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun * Desugar try-with-resources. This class visitor intercepts calls to the following methods, and
35c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun * redirect them to ThrowableExtension.
36c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun * <li>{@code Throwable.addSuppressed(Throwable)}
37c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun * <li>{@code Throwable.getSuppressed()}
38c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun * <li>{@code Throwable.printStackTrace()}
39c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun * <li>{@code Throwable.printStackTrace(PrintStream)}
40c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun * <li>{@code Throwable.printStackTrace(PringWriter)}
41c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun */
42c5e345ca89228abdcff50024d8728a4e5bf87d44cnsunpublic class TryWithResourcesRewriter extends ClassVisitor {
43c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
44c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  private static final String RUNTIME_PACKAGE_INTERNAL_NAME =
45c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      "com/google/devtools/build/android/desugar/runtime";
46c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
47c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  static final String THROWABLE_EXTENSION_INTERNAL_NAME =
48c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      RUNTIME_PACKAGE_INTERNAL_NAME + '/' + "ThrowableExtension";
49c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
50c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  /** The extension classes for java.lang.Throwable. */
51c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  static final ImmutableSet<String> THROWABLE_EXT_CLASS_INTERNAL_NAMES =
52c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      ImmutableSet.of(
53c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          THROWABLE_EXTENSION_INTERNAL_NAME,
54c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          THROWABLE_EXTENSION_INTERNAL_NAME + "$AbstractDesugaringStrategy",
55950d20dcbc7760aa9c107c9e0c3e3e79ddc0d9adcnsun          THROWABLE_EXTENSION_INTERNAL_NAME + "$ConcurrentWeakIdentityHashMap",
56950d20dcbc7760aa9c107c9e0c3e3e79ddc0d9adcnsun          THROWABLE_EXTENSION_INTERNAL_NAME + "$ConcurrentWeakIdentityHashMap$WeakKey",
57c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          THROWABLE_EXTENSION_INTERNAL_NAME + "$MimicDesugaringStrategy",
58c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          THROWABLE_EXTENSION_INTERNAL_NAME + "$NullDesugaringStrategy",
59c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          THROWABLE_EXTENSION_INTERNAL_NAME + "$ReuseDesugaringStrategy");
60c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
61c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  /** The extension classes for java.lang.Throwable. All the names end with ".class" */
62c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  static final ImmutableSet<String> THROWABLE_EXT_CLASS_INTERNAL_NAMES_WITH_CLASS_EXT =
63c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      FluentIterable.from(THROWABLE_EXT_CLASS_INTERNAL_NAMES)
64c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .transform(
65c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun              new Function<String, String>() {
66c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun                @Override
67c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun                public String apply(String s) {
68c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun                  return s + ".class";
69c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun                }
70c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun              })
71c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .toSet();
72c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
73c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  static final ImmutableMultimap<String, String> TARGET_METHODS =
74c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      ImmutableMultimap.<String, String>builder()
75c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("addSuppressed", "(Ljava/lang/Throwable;)V")
76c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("getSuppressed", "()[Ljava/lang/Throwable;")
77c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("printStackTrace", "()V")
78c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("printStackTrace", "(Ljava/io/PrintStream;)V")
79c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("printStackTrace", "(Ljava/io/PrintWriter;)V")
80c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .build();
81c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
82c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  static final ImmutableMap<String, String> METHOD_DESC_MAP =
83c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      ImmutableMap.<String, String>builder()
84c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("(Ljava/lang/Throwable;)V", "(Ljava/lang/Throwable;Ljava/lang/Throwable;)V")
85c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("()[Ljava/lang/Throwable;", "(Ljava/lang/Throwable;)[Ljava/lang/Throwable;")
86c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("()V", "(Ljava/lang/Throwable;)V")
87c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("(Ljava/io/PrintStream;)V", "(Ljava/lang/Throwable;Ljava/io/PrintStream;)V")
88c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .put("(Ljava/io/PrintWriter;)V", "(Ljava/lang/Throwable;Ljava/io/PrintWriter;)V")
89c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          .build();
90c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
91c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  private final ClassLoader classLoader;
926dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun  private final Set<String> visitedExceptionTypes;
93c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  private final AtomicInteger numOfTryWithResourcesInvoked;
94885bce7f8d296b091d15c0007a7716f01c3fee33cnsun  /**
95885bce7f8d296b091d15c0007a7716f01c3fee33cnsun   * Indicate whether the current class being desugared should be ignored. If the current class is
96885bce7f8d296b091d15c0007a7716f01c3fee33cnsun   * one of the runtime extension classes, then it should be ignored.
97885bce7f8d296b091d15c0007a7716f01c3fee33cnsun   */
98885bce7f8d296b091d15c0007a7716f01c3fee33cnsun  private boolean shouldCurrentClassBeIgnored;
99c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
100c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  public TryWithResourcesRewriter(
101c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      ClassVisitor classVisitor,
102c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      ClassLoader classLoader,
1036dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      Set<String> visitedExceptionTypes,
104c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      AtomicInteger numOfTryWithResourcesInvoked) {
105c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    super(ASM5, classVisitor);
106c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    this.classLoader = classLoader;
1076dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    this.visitedExceptionTypes = visitedExceptionTypes;
108c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    this.numOfTryWithResourcesInvoked = numOfTryWithResourcesInvoked;
109c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  }
110c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
111c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  @Override
112885bce7f8d296b091d15c0007a7716f01c3fee33cnsun  public void visit(
113885bce7f8d296b091d15c0007a7716f01c3fee33cnsun      int version,
114885bce7f8d296b091d15c0007a7716f01c3fee33cnsun      int access,
115885bce7f8d296b091d15c0007a7716f01c3fee33cnsun      String name,
116885bce7f8d296b091d15c0007a7716f01c3fee33cnsun      String signature,
117885bce7f8d296b091d15c0007a7716f01c3fee33cnsun      String superName,
118885bce7f8d296b091d15c0007a7716f01c3fee33cnsun      String[] interfaces) {
119885bce7f8d296b091d15c0007a7716f01c3fee33cnsun    super.visit(version, access, name, signature, superName, interfaces);
120885bce7f8d296b091d15c0007a7716f01c3fee33cnsun    shouldCurrentClassBeIgnored = THROWABLE_EXT_CLASS_INTERNAL_NAMES.contains(name);
121885bce7f8d296b091d15c0007a7716f01c3fee33cnsun  }
122885bce7f8d296b091d15c0007a7716f01c3fee33cnsun
123885bce7f8d296b091d15c0007a7716f01c3fee33cnsun  @Override
124c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  public MethodVisitor visitMethod(
125c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      int access, String name, String desc, String signature, String[] exceptions) {
1266dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    if (exceptions != null && exceptions.length > 0) {
1276dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      // collect exception types.
1286dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      Collections.addAll(visitedExceptionTypes, exceptions);
1296dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    }
130c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    MethodVisitor visitor = super.cv.visitMethod(access, name, desc, signature, exceptions);
131885bce7f8d296b091d15c0007a7716f01c3fee33cnsun    return visitor == null || shouldCurrentClassBeIgnored
132c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun        ? visitor
1336dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun        : new TryWithResourceVisitor(name + desc, visitor, classLoader);
134c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  }
135c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
136c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  private class TryWithResourceVisitor extends MethodVisitor {
137c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
138c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    private final ClassLoader classLoader;
1396dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    /** For debugging purpose. Enrich exception information. */
1406dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    private final String methodSignature;
141c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
1426dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    public TryWithResourceVisitor(
1436dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun        String methodSignature, MethodVisitor methodVisitor, ClassLoader classLoader) {
144c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      super(ASM5, methodVisitor);
145c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      this.classLoader = classLoader;
1466dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      this.methodSignature = methodSignature;
1476dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    }
1486dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun
1496dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    @Override
1506dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
1516dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      if (type != null) {
1526dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun        visitedExceptionTypes.add(type); // type in a try-catch block must extend Throwable.
1536dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      }
1546dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      super.visitTryCatchBlock(start, end, handler, type);
155c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    }
156c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
157c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    @Override
158c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
159c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      if (!isMethodCallTargeted(opcode, owner, name, desc)) {
160c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun        super.visitMethodInsn(opcode, owner, name, desc, itf);
161c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun        return;
162c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      }
163c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      numOfTryWithResourcesInvoked.incrementAndGet();
1646dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      visitedExceptionTypes.add(checkNotNull(owner)); // owner extends Throwable.
165c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      super.visitMethodInsn(
166c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun          INVOKESTATIC, THROWABLE_EXTENSION_INTERNAL_NAME, name, METHOD_DESC_MAP.get(desc), false);
167c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    }
168c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun
169c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    private boolean isMethodCallTargeted(int opcode, String owner, String name, String desc) {
170c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      if (opcode != INVOKEVIRTUAL) {
171c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun        return false;
172c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      }
173c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      if (!TARGET_METHODS.containsEntry(name, desc)) {
174c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun        return false;
175c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      }
1766dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun      if (visitedExceptionTypes.contains(owner)) {
1776dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun        return true; // The owner is an exception that has been visited before.
178c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      }
179c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      try {
180c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun        Class<?> throwableClass = classLoader.loadClass("java.lang.Throwable");
181c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun        Class<?> klass = classLoader.loadClass(owner.replace('/', '.'));
182c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun        return throwableClass.isAssignableFrom(klass);
183c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      } catch (ClassNotFoundException e) {
1846dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun        throw new AssertionError(
1856dea9f5fe0a15fd4faf6fe16108b97f6dd299b2dcnsun            "Failed to load class when desugaring method " + methodSignature, e);
186c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun      }
187c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun    }
188c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun  }
189c5e345ca89228abdcff50024d8728a4e5bf87d44cnsun}
190