Tool that scans a given Jar for references to select classes and outputs corresponding Proguard-style -keep rules
RELNOTES: None.

PiperOrigin-RevId: 186372769
diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/test_keep_scanner.sh b/src/test/java/com/google/devtools/build/android/desugar/scan/test_keep_scanner.sh
new file mode 100755
index 0000000..d42859f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/scan/test_keep_scanner.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+set -eux
+
+out=$(mktemp)
+"$1" --input "$2" --keep_file "${out}" --prefix java/
+
+if ! diff "$3" "${out}"; then
+  echo "Unexpected output"
+  cat "${out}"
+  rm "${out}"
+  exit 1
+fi
+rm "${out}"
diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java
new file mode 100644
index 0000000..482c32a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java
@@ -0,0 +1,60 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar.scan.testdata;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/** Test data for {@code KeepScanner} with references to java.* */
+public class CollectionReferences {
+
+  private final List<Date> dates;
+
+  public CollectionReferences() {
+    dates = new ArrayList<>(7);
+    assert !(dates instanceof LinkedList);
+  }
+
+  @SuppressWarnings("unchecked")
+  public void add(Date date) {
+    List<Date> l = (AbstractList<Date>) Collection.class.cast(dates);
+    l.add(date);
+  }
+
+  public Date first() {
+    try {
+      return dates.get(0);
+    } catch (IndexOutOfBoundsException e) {
+      return null;
+    }
+  }
+
+  public long min() {
+    long result = Long.MAX_VALUE; // compile-time constant, no ref
+    for (Date d : dates) {
+      if (d.getTime() < result) {
+        result = d.getTime();
+      }
+    }
+    return result;
+  }
+
+  static {
+    System.out.println("Hello!");
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/OverlappingCollectionReferences.java b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/OverlappingCollectionReferences.java
new file mode 100644
index 0000000..e743a0d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/OverlappingCollectionReferences.java
@@ -0,0 +1,49 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar.scan.testdata;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+/** Supplements {@link CollectionReferences} with additional and overlapping references to java.* */
+public class OverlappingCollectionReferences {
+
+  private final ArrayList<Date> dates;
+
+  public OverlappingCollectionReferences() {
+    dates = new ArrayList<>();
+  }
+
+  public void add(Date date) {
+    dates.add(date);
+  }
+
+  public Date first() {
+    try {
+      return dates.get(0);
+    } catch (IndexOutOfBoundsException e) {
+      return null;
+    }
+  }
+
+  public Date max() {
+    long result = Long.MIN_VALUE; // compile-time constant, no ref
+    for (Date d : dates) {
+      if (d.getTime() > result) {
+        result = d.getTime();
+      }
+    }
+    return new Date(result);
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt
new file mode 100644
index 0000000..e4509b4
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt
@@ -0,0 +1,46 @@
+-keep class java.io.PrintStream {
+  *** println(java.lang.String);
+}
+-keep class java.lang.AssertionError {
+  *** <init>();
+}
+-keep class java.lang.Class {
+  *** cast(java.lang.Object);
+  *** desiredAssertionStatus();
+}
+-keep class java.lang.IndexOutOfBoundsException {
+}
+-keep class java.lang.Object {
+  *** <init>();
+}
+-keep class java.lang.String {
+}
+-keep class java.lang.System {
+  *** out;
+}
+-keep class java.util.AbstractList {
+}
+-keep class java.util.ArrayList {
+  *** <init>();
+  *** <init>(int);
+  *** add(java.lang.Object);
+  *** get(int);
+  *** iterator();
+}
+-keep class java.util.Collection {
+}
+-keep class java.util.Date {
+  *** <init>(long);
+  *** getTime();
+}
+-keep class java.util.Iterator {
+  *** hasNext();
+  *** next();
+}
+-keep class java.util.LinkedList {
+}
+-keep class java.util.List {
+  *** add(java.lang.Object);
+  *** get(int);
+  *** iterator();
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/scan/KeepReference.java b/src/tools/android/java/com/google/devtools/build/android/desugar/scan/KeepReference.java
new file mode 100644
index 0000000..bae3f38
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/scan/KeepReference.java
@@ -0,0 +1,51 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar.scan;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.Immutable;
+
+@AutoValue
+@Immutable
+abstract class KeepReference {
+  public static KeepReference classReference(String internalName) {
+    checkArgument(!internalName.isEmpty());
+    return new AutoValue_KeepReference(internalName, "", "");
+  }
+
+  public static KeepReference memberReference(String internalName, String name, String desc) {
+    checkArgument(!internalName.isEmpty());
+    checkArgument(!name.isEmpty());
+    checkArgument(!desc.isEmpty());
+    return new AutoValue_KeepReference(internalName, name, desc);
+  }
+
+  public final boolean isMemberReference() {
+    return !name().isEmpty();
+  }
+
+  public final boolean isMethodReference() {
+    return desc().startsWith("(");
+  }
+
+  public final boolean isFieldReference() {
+    return isMemberReference() && !isMethodReference();
+  }
+
+  public abstract String internalName();
+  public abstract String name();
+  public abstract String desc();
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java b/src/tools/android/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java
new file mode 100644
index 0000000..5892bf5
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java
@@ -0,0 +1,174 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar.scan;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.util.Comparator.comparing;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.android.Converters.ExistingPathConverter;
+import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Type;
+
+class KeepScanner {
+
+  public static class KeepScannerOptions extends OptionsBase {
+    @Option(
+      name = "input",
+      defaultValue = "null",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = OptionEffectTag.UNKNOWN,
+      converter = ExistingPathConverter.class,
+      abbrev = 'i',
+      help = "Input Jar with classes to scan."
+    )
+    public Path inputJars;
+
+    @Option(
+      name = "keep_file",
+      defaultValue = "null",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = OptionEffectTag.UNKNOWN,
+      converter = PathConverter.class,
+      help = "Where to write keep rules to."
+    )
+    public Path keepDest;
+
+    @Option(
+      name = "prefix",
+      defaultValue = "j$/",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = OptionEffectTag.UNKNOWN,
+      help = "type to scan for."
+    )
+    public String prefix;
+  }
+
+  public static void main(String... args) throws Exception {
+    OptionsParser parser = OptionsParser.newOptionsParser(KeepScannerOptions.class);
+    parser.setAllowResidue(false);
+    parser.enableParamsFileSupport(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault()));
+    parser.parseAndExitUponError(args);
+
+    KeepScannerOptions options = parser.getOptions(KeepScannerOptions.class);
+    Map<String, ImmutableSet<KeepReference>> seeds =
+        scan(checkNotNull(options.inputJars), options.prefix);
+
+    try (PrintStream out =
+        new PrintStream(
+            Files.newOutputStream(options.keepDest, CREATE), /*autoFlush=*/ false, "UTF-8")) {
+      writeKeepDirectives(out, seeds);
+    }
+  }
+
+  /**
+   * Writes a -keep rule for each class listing any members to keep.  We sort classes and members
+   * so the output is deterministic.
+   */
+  private static void writeKeepDirectives(
+      PrintStream out, Map<String, ImmutableSet<KeepReference>> seeds) {
+    seeds
+        .entrySet()
+        .stream()
+        .sorted(comparing(Map.Entry::getKey))
+        .forEachOrdered(
+            type -> {
+              out.printf("-keep class %s {%n", type.getKey().replace('/', '.'));
+              type.getValue()
+                  .stream()
+                  .filter(KeepReference::isMemberReference)
+                  .sorted(comparing(KeepReference::name).thenComparing(KeepReference::desc))
+                  .map(ref -> toKeepDescriptor(ref))
+                  .distinct() // drop duplicates due to method descriptors with different returns
+                  .forEachOrdered(line -> out.append("  ").append(line).append(";").println());
+              out.printf("}%n");
+            });
+  }
+
+  /**
+   * Scans for and returns references with owners matching the given prefix grouped by owner.
+   */
+  private static Map<String, ImmutableSet<KeepReference>> scan(Path jarFile, String prefix)
+      throws IOException {
+    // We read the Jar sequentially since ZipFile uses locks anyway but then allow scanning each
+    // class in parallel.
+    try (ZipFile zip = new ZipFile(jarFile.toFile())) {
+      return zip.stream()
+          .filter(entry -> entry.getName().endsWith(".class"))
+          .map(entry -> readFully(zip, entry))
+          .parallel()
+          .flatMap(
+              content -> PrefixReferenceScanner.scan(new ClassReader(content), prefix).stream())
+          .collect(
+              Collectors.groupingByConcurrent(
+                  KeepReference::internalName, ImmutableSet.toImmutableSet()));
+    }
+  }
+
+  private static byte[] readFully(ZipFile zip, ZipEntry entry) {
+    byte[] result = new byte[(int) entry.getSize()];
+    try (InputStream content = zip.getInputStream(entry)) {
+      checkState(content.read(result) == result.length);
+      checkState(content.read() == -1);
+    } catch (IOException e) {
+      throw new IOError(e);
+    }
+    return result;
+  }
+
+  private static CharSequence toKeepDescriptor(KeepReference member) {
+    StringBuilder result = new StringBuilder();
+    if (member.isMethodReference()) {
+      result.append("*** ").append(member.name()).append("(");
+      // Ignore return type as it's unique in the source language
+      boolean first = true;
+      for (Type param : Type.getMethodType(member.desc()).getArgumentTypes()) {
+        if (first) {
+          first = false;
+        } else {
+          result.append(", ");
+        }
+        result.append(param.getClassName());
+      }
+      result.append(")");
+    } else {
+      checkArgument(member.isFieldReference());
+      result.append("*** ").append(member.name()); // field names are unique so ignore descriptor
+    }
+    return result;
+  }
+
+  private KeepScanner() {}
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java b/src/tools/android/java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java
new file mode 100644
index 0000000..b899ccc
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java
@@ -0,0 +1,405 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar.scan;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableSet;
+import javax.annotation.Nullable;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypePath;
+
+/** {@link ClassVisitor} that records references to classes starting with a given prefix. */
+class PrefixReferenceScanner extends ClassVisitor {
+
+  /**
+   * Returns references with the given prefix in the given class.
+   *
+   * @param prefix an internal name prefix, typically a package such as {@code com/google/}
+   */
+  public static ImmutableSet<KeepReference> scan(ClassReader reader, String prefix) {
+    PrefixReferenceScanner scanner = new PrefixReferenceScanner(prefix);
+    // Frames irrelevant for Android so skip them.  Don't skip debug info in case the class we're
+    // visiting has local variable tables (typically it doesn't anyways).
+    reader.accept(scanner, ClassReader.SKIP_FRAMES);
+    return scanner.roots.build();
+  }
+
+  private final ImmutableSet.Builder<KeepReference> roots = ImmutableSet.builder();
+  private final PrefixReferenceMethodVisitor mv = new PrefixReferenceMethodVisitor();
+  private final PrefixReferenceFieldVisitor fv = new PrefixReferenceFieldVisitor();
+  private final PrefixReferenceAnnotationVisitor av = new PrefixReferenceAnnotationVisitor();
+
+  private final String prefix;
+
+  public PrefixReferenceScanner(String prefix) {
+    super(Opcodes.ASM6);
+    this.prefix = prefix;
+  }
+
+  @Override
+  public void visit(
+      int version,
+      int access,
+      String name,
+      String signature,
+      String superName,
+      String[] interfaces) {
+    checkArgument(!name.startsWith(prefix));
+    if (superName != null) {
+      classReference(superName);
+    }
+    classReferences(interfaces);
+    super.visit(version, access, name, signature, superName, interfaces);
+  }
+
+  @Override
+  public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+    typeReference(desc);
+    return av;
+  }
+
+  @Override
+  public void visitOuterClass(String owner, String name, String desc) {
+    classReference(owner);
+    if (desc != null) {
+      typeReference(Type.getMethodType(desc));
+    }
+  }
+
+  @Override
+  public AnnotationVisitor visitTypeAnnotation(
+      int typeRef, TypePath typePath, String desc, boolean visible) {
+    typeReference(desc);
+    return av;
+  }
+
+  @Override
+  public void visitInnerClass(String name, String outerName, String innerName, int access) {
+    classReference(name);
+    if (outerName != null) {
+      classReference(outerName);
+    }
+  }
+
+  @Override
+  public FieldVisitor visitField(
+      int access, String name, String desc, String signature, Object value) {
+    typeReference(desc);
+    return fv;
+  }
+
+  @Override
+  public MethodVisitor visitMethod(
+      int access, String name, String desc, String signature, String[] exceptions) {
+    typeReference(Type.getMethodType(desc));
+    classReferences(exceptions);
+    return mv;
+  }
+
+  private void classReferences(@Nullable String[] internalNames) {
+    if (internalNames != null) {
+      for (String itf : internalNames) {
+        classReference(itf);
+      }
+    }
+  }
+
+  // The following methods are package-private so they don't incur bridge methods when called from
+  // inner classes below.
+
+  void classReference(String internalName) {
+    checkArgument(internalName.charAt(0) != '[' && internalName.charAt(0) != '(', internalName);
+    checkArgument(!internalName.endsWith(";"), internalName);
+    if (internalName.startsWith(prefix)) {
+      roots.add(KeepReference.classReference(internalName));
+    }
+  }
+
+  void objectReference(String internalName) {
+    // don't call this for method types, convert to Type instead
+    checkArgument(internalName.charAt(0) != '(', internalName);
+    if (internalName.charAt(0) == '[') {
+      typeReference(internalName);
+    } else {
+      classReference(internalName);
+    }
+  }
+
+  void typeReference(String typeDesc) {
+    // don't call this for method types, convert to Type instead
+    checkArgument(typeDesc.charAt(0) != '(', typeDesc);
+
+    int lpos = typeDesc.lastIndexOf('[') + 1;
+    if (typeDesc.charAt(lpos) == 'L') {
+      checkArgument(typeDesc.endsWith(";"), typeDesc);
+      classReference(typeDesc.substring(lpos, typeDesc.length() - 1));
+    } else {
+      // else primitive or primitive array
+      checkArgument(typeDesc.length() == lpos + 1, typeDesc);
+      switch (typeDesc.charAt(lpos)) {
+        case 'B':
+        case 'C':
+        case 'S':
+        case 'I':
+        case 'J':
+        case 'D':
+        case 'F':
+        case 'Z':
+          break;
+        default:
+          throw new AssertionError("Unexpected type descriptor: " + typeDesc);
+      }
+    }
+  }
+
+  void typeReference(Type type) {
+    switch (type.getSort()) {
+      case Type.ARRAY:
+        typeReference(type.getElementType());
+        break;
+      case Type.OBJECT:
+        classReference(type.getInternalName());
+        break;
+
+      case Type.METHOD:
+        for (Type param : type.getArgumentTypes()) {
+          typeReference(param);
+        }
+        typeReference(type.getReturnType());
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  void fieldReference(String owner, String name, String desc) {
+    objectReference(owner);
+    typeReference(desc);
+    if (owner.startsWith(prefix)) {
+      roots.add(KeepReference.memberReference(owner, name, desc));
+    }
+  }
+
+  void methodReference(String owner, String name, String desc) {
+    checkArgument(desc.charAt(0) == '(', desc);
+    objectReference(owner);
+    typeReference(Type.getMethodType(desc));
+    if (owner.startsWith(prefix)) {
+      roots.add(KeepReference.memberReference(owner, name, desc));
+    }
+  }
+
+  void handleReference(Handle handle) {
+    switch (handle.getTag()) {
+      case Opcodes.H_GETFIELD:
+      case Opcodes.H_GETSTATIC:
+      case Opcodes.H_PUTFIELD:
+      case Opcodes.H_PUTSTATIC:
+        fieldReference(handle.getOwner(), handle.getName(), handle.getDesc());
+        break;
+
+      default:
+        methodReference(handle.getOwner(), handle.getName(), handle.getDesc());
+        break;
+    }
+  }
+
+  private class PrefixReferenceMethodVisitor extends MethodVisitor {
+
+    public PrefixReferenceMethodVisitor() {
+      super(Opcodes.ASM6);
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotationDefault() {
+      return av;
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      typeReference(desc);
+      return av;
+    }
+
+    @Override
+    public AnnotationVisitor visitTypeAnnotation(
+        int typeRef, TypePath typePath, String desc, boolean visible) {
+      typeReference(desc);
+      return av;
+    }
+
+    @Override
+    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+      typeReference(desc);
+      return av;
+    }
+
+    @Override
+    public void visitTypeInsn(int opcode, String type) {
+      objectReference(type);
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+      fieldReference(owner, name, desc);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation") // Implementing deprecated method to be sure
+    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+      visitMethodInsn(opcode, owner, name, desc, opcode == Opcodes.INVOKEINTERFACE);
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+      methodReference(owner, name, desc);
+    }
+
+    @Override
+    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
+      typeReference(Type.getMethodType(desc));
+      handleReference(bsm);
+      for (Object bsmArg : bsmArgs) {
+        visitConstant(bsmArg);
+      }
+    }
+
+    @Override
+    public void visitLdcInsn(Object cst) {
+      visitConstant(cst);
+    }
+
+    private void visitConstant(Object cst) {
+      if (cst instanceof Type) {
+        typeReference((Type) cst);
+      } else if (cst instanceof Handle) {
+        handleReference((Handle) cst);
+      } else {
+        // Check for other expected types as javadoc recommends
+        checkArgument(
+            cst instanceof String
+                || cst instanceof Integer
+                || cst instanceof Long
+                || cst instanceof Float
+                || cst instanceof Double,
+            "Unexpected constant: ", cst);
+      }
+    }
+
+    @Override
+    public void visitMultiANewArrayInsn(String desc, int dims) {
+      typeReference(desc);
+    }
+
+    @Override
+    public AnnotationVisitor visitInsnAnnotation(
+        int typeRef, TypePath typePath, String desc, boolean visible) {
+      typeReference(desc);
+      return av;
+    }
+
+    @Override
+    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+      if (type != null) {
+        classReference(type);
+      }
+    }
+
+    @Override
+    public AnnotationVisitor visitTryCatchAnnotation(
+        int typeRef, TypePath typePath, String desc, boolean visible) {
+      typeReference(desc);
+      return av;
+    }
+
+    @Override
+    public void visitLocalVariable(
+        String name, String desc, String signature, Label start, Label end, int index) {
+      typeReference(desc);
+    }
+
+    @Override
+    public AnnotationVisitor visitLocalVariableAnnotation(
+        int typeRef,
+        TypePath typePath,
+        Label[] start,
+        Label[] end,
+        int[] index,
+        String desc,
+        boolean visible) {
+      typeReference(desc);
+      return av;
+    }
+  }
+
+  private class PrefixReferenceFieldVisitor extends FieldVisitor {
+
+    public PrefixReferenceFieldVisitor() {
+      super(Opcodes.ASM6);
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      typeReference(desc);
+      return av;
+    }
+
+    @Override
+    public AnnotationVisitor visitTypeAnnotation(
+        int typeRef, TypePath typePath, String desc, boolean visible) {
+      typeReference(desc);
+      return av;
+    }
+  }
+
+  private class PrefixReferenceAnnotationVisitor extends AnnotationVisitor {
+
+    public PrefixReferenceAnnotationVisitor() {
+      super(Opcodes.ASM6);
+    }
+
+    @Override
+    public void visit(String name, Object value) {
+      if (value instanceof Type) {
+        typeReference((Type) value);
+      }
+    }
+
+    @Override
+    public void visitEnum(String name, String desc, String value) {
+      fieldReference(desc.substring(1, desc.length() - 1), value, desc);
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String name, String desc) {
+      typeReference(desc);
+      return av;
+    }
+
+    @Override
+    public AnnotationVisitor visitArray(String name) {
+      return av;
+    }
+  }
+}