summaryrefslogtreecommitdiff
path: root/java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java')
-rw-r--r--java/com/google/devtools/build/android/desugar/scan/PrefixReferenceScanner.java405
1 files changed, 405 insertions, 0 deletions
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;
+ }
+ }
+}