summaryrefslogtreecommitdiff
path: root/java/com/google/devtools/build/android/desugar/scan
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/devtools/build/android/desugar/scan')
-rw-r--r--java/com/google/devtools/build/android/desugar/scan/KeepReference.java51
-rw-r--r--java/com/google/devtools/build/android/desugar/scan/KeepScanner.java303
-rw-r--r--java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java405
3 files changed, 759 insertions, 0 deletions
diff --git a/java/com/google/devtools/build/android/desugar/scan/KeepReference.java b/java/com/google/devtools/build/android/desugar/scan/KeepReference.java
new file mode 100644
index 0000000..bae3f38
--- /dev/null
+++ b/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/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java b/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java
new file mode 100644
index 0000000..4924f7c
--- /dev/null
+++ b/java/com/google/devtools/build/android/desugar/scan/KeepScanner.java
@@ -0,0 +1,303 @@
+// 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.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closer;
+import com.google.devtools.build.android.Converters.ExistingPathConverter;
+import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
+import com.google.devtools.build.android.desugar.io.HeaderClassLoader;
+import com.google.devtools.build.android.desugar.io.IndexedInputs;
+import com.google.devtools.build.android.desugar.io.InputFileProvider;
+import com.google.devtools.build.android.desugar.io.ThrowingClassLoader;
+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.lang.reflect.Method;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+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 = "classpath_entry",
+ allowMultiple = true,
+ defaultValue = "",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = ExistingPathConverter.class,
+ help =
+ "Ordered classpath (Jar or directory) to resolve symbols in the --input Jar, like "
+ + "javac's -cp flag."
+ )
+ public List<Path> classpath;
+
+ @Option(
+ name = "bootclasspath_entry",
+ allowMultiple = true,
+ defaultValue = "",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = ExistingPathConverter.class,
+ help =
+ "Bootclasspath that was used to compile the --input Jar with, like javac's "
+ + "-bootclasspath flag (required)."
+ )
+ public List<Path> bootclasspath;
+
+ @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;
+ try (Closer closer = Closer.create()) {
+ // TODO(kmb): Try to share more of this code with Desugar binary
+ IndexedInputs classpath =
+ new IndexedInputs(toRegisteredInputFileProvider(closer, options.classpath));
+ IndexedInputs bootclasspath =
+ new IndexedInputs(toRegisteredInputFileProvider(closer, options.bootclasspath));
+
+ // Construct classloader from classpath. Since we're assuming the prefix we're looking for
+ // isn't part of the input itself we shouldn't need to include the input in the classloader.
+ CoreLibraryRewriter noopRewriter = new CoreLibraryRewriter("");
+ ClassLoader classloader =
+ new HeaderClassLoader(classpath, noopRewriter,
+ new HeaderClassLoader(bootclasspath, noopRewriter,
+ new ThrowingClassLoader()));
+ seeds = scan(checkNotNull(options.inputJars), options.prefix, classloader);
+ }
+
+ 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, ClassLoader classpath) 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())
+ .distinct() // so we don't process the same reference multiple times next
+ .map(ref -> nearestDeclaration(ref, classpath))
+ .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)) {
+ ByteStreams.readFully(content, result);
+ return result;
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ }
+
+ /**
+ * Find the nearest definition of the given reference in the class hierarchy and return the
+ * modified reference. This is needed b/c bytecode sometimes refers to a method or field using
+ * an owner type that inherits the method or field instead of defining the member itself.
+ * In that case we need to find and keep the inherited definition.
+ */
+ private static KeepReference nearestDeclaration(KeepReference ref, ClassLoader classpath) {
+ if (!ref.isMemberReference() || "<init>".equals(ref.name())) {
+ return ref; // class and constructor references don't need any further work
+ }
+
+ Class<?> clazz;
+ try {
+ clazz = classpath.loadClass(ref.internalName().replace('/', '.'));
+ } catch (ClassNotFoundException e) {
+ throw (NoClassDefFoundError) new NoClassDefFoundError("Couldn't load " + ref).initCause(e);
+ }
+
+ Class<?> owner = findDeclaringClass(clazz, ref);
+ if (owner == clazz) {
+ return ref;
+ }
+ String parent = checkNotNull(owner, "Can't resolve: %s", ref).getName().replace('.', '/');
+ return KeepReference.memberReference(parent, ref.name(), ref.desc());
+ }
+
+ private static Class<?> findDeclaringClass(Class<?> clazz, KeepReference ref) {
+ if (ref.isFieldReference()) {
+ try {
+ return clazz.getField(ref.name()).getDeclaringClass();
+ } catch (NoSuchFieldException e) {
+ // field must be non-public, so search class hierarchy
+ do {
+ try {
+ return clazz.getDeclaredField(ref.name()).getDeclaringClass();
+ } catch (NoSuchFieldException ignored) {
+ // fall through for clarity
+ }
+ clazz = clazz.getSuperclass();
+ } while (clazz != null);
+ }
+ } else {
+ checkState(ref.isMethodReference());
+ Type descriptor = Type.getMethodType(ref.desc());
+ for (Method m : clazz.getMethods()) {
+ if (m.getName().equals(ref.name()) && Type.getType(m).equals(descriptor)) {
+ return m.getDeclaringClass();
+ }
+ }
+ do {
+ // Method must be non-public, so search class hierarchy
+ for (Method m : clazz.getDeclaredMethods()) {
+ if (m.getName().equals(ref.name()) && Type.getType(m).equals(descriptor)) {
+ return m.getDeclaringClass();
+ }
+ }
+ clazz = clazz.getSuperclass();
+ } while (clazz != null);
+ }
+ return null;
+ }
+
+ private static CharSequence toKeepDescriptor(KeepReference member) {
+ StringBuilder result = new StringBuilder();
+ if (member.isMethodReference()) {
+ if (!"<init>".equals(member.name())) {
+ result.append("*** ");
+ }
+ result.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;
+ }
+
+ /**
+ * Transform a list of Path to a list of InputFileProvider and register them with the given
+ * closer.
+ */
+ @SuppressWarnings("MustBeClosedChecker")
+ private static ImmutableList<InputFileProvider> toRegisteredInputFileProvider(
+ Closer closer, List<Path> paths) throws IOException {
+ ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>();
+ for (Path path : paths) {
+ builder.add(closer.register(InputFileProvider.open(path)));
+ }
+ return builder.build();
+ }
+
+ private KeepScanner() {}
+}
diff --git a/java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java b/java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java
new file mode 100644
index 0000000..b899ccc
--- /dev/null
+++ b/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;
+ }
+ }
+}