// 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 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 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; } } }