Home | History | Annotate | Download | only in scan
      1 // Copyright 2018 The Bazel Authors. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //    http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 package com.google.devtools.build.android.desugar.scan;
     15 
     16 import static com.google.common.base.Preconditions.checkArgument;
     17 
     18 import com.google.common.collect.ImmutableSet;
     19 import javax.annotation.Nullable;
     20 import org.objectweb.asm.AnnotationVisitor;
     21 import org.objectweb.asm.ClassReader;
     22 import org.objectweb.asm.ClassVisitor;
     23 import org.objectweb.asm.FieldVisitor;
     24 import org.objectweb.asm.Handle;
     25 import org.objectweb.asm.Label;
     26 import org.objectweb.asm.MethodVisitor;
     27 import org.objectweb.asm.Opcodes;
     28 import org.objectweb.asm.Type;
     29 import org.objectweb.asm.TypePath;
     30 
     31 /** {@link ClassVisitor} that records references to classes starting with a given prefix. */
     32 class PrefixReferenceScanner extends ClassVisitor {
     33 
     34   /**
     35    * Returns references with the given prefix in the given class.
     36    *
     37    * @param prefix an internal name prefix, typically a package such as {@code com/google/}
     38    */
     39   public static ImmutableSet<KeepReference> scan(ClassReader reader, String prefix) {
     40     PrefixReferenceScanner scanner = new PrefixReferenceScanner(prefix);
     41     // Frames irrelevant for Android so skip them.  Don't skip debug info in case the class we're
     42     // visiting has local variable tables (typically it doesn't anyways).
     43     reader.accept(scanner, ClassReader.SKIP_FRAMES);
     44     return scanner.roots.build();
     45   }
     46 
     47   private final ImmutableSet.Builder<KeepReference> roots = ImmutableSet.builder();
     48   private final PrefixReferenceMethodVisitor mv = new PrefixReferenceMethodVisitor();
     49   private final PrefixReferenceFieldVisitor fv = new PrefixReferenceFieldVisitor();
     50   private final PrefixReferenceAnnotationVisitor av = new PrefixReferenceAnnotationVisitor();
     51 
     52   private final String prefix;
     53 
     54   public PrefixReferenceScanner(String prefix) {
     55     super(Opcodes.ASM6);
     56     this.prefix = prefix;
     57   }
     58 
     59   @Override
     60   public void visit(
     61       int version,
     62       int access,
     63       String name,
     64       String signature,
     65       String superName,
     66       String[] interfaces) {
     67     checkArgument(!name.startsWith(prefix));
     68     if (superName != null) {
     69       classReference(superName);
     70     }
     71     classReferences(interfaces);
     72     super.visit(version, access, name, signature, superName, interfaces);
     73   }
     74 
     75   @Override
     76   public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
     77     typeReference(desc);
     78     return av;
     79   }
     80 
     81   @Override
     82   public void visitOuterClass(String owner, String name, String desc) {
     83     classReference(owner);
     84     if (desc != null) {
     85       typeReference(Type.getMethodType(desc));
     86     }
     87   }
     88 
     89   @Override
     90   public AnnotationVisitor visitTypeAnnotation(
     91       int typeRef, TypePath typePath, String desc, boolean visible) {
     92     typeReference(desc);
     93     return av;
     94   }
     95 
     96   @Override
     97   public void visitInnerClass(String name, String outerName, String innerName, int access) {
     98     classReference(name);
     99     if (outerName != null) {
    100       classReference(outerName);
    101     }
    102   }
    103 
    104   @Override
    105   public FieldVisitor visitField(
    106       int access, String name, String desc, String signature, Object value) {
    107     typeReference(desc);
    108     return fv;
    109   }
    110 
    111   @Override
    112   public MethodVisitor visitMethod(
    113       int access, String name, String desc, String signature, String[] exceptions) {
    114     typeReference(Type.getMethodType(desc));
    115     classReferences(exceptions);
    116     return mv;
    117   }
    118 
    119   private void classReferences(@Nullable String[] internalNames) {
    120     if (internalNames != null) {
    121       for (String itf : internalNames) {
    122         classReference(itf);
    123       }
    124     }
    125   }
    126 
    127   // The following methods are package-private so they don't incur bridge methods when called from
    128   // inner classes below.
    129 
    130   void classReference(String internalName) {
    131     checkArgument(internalName.charAt(0) != '[' && internalName.charAt(0) != '(', internalName);
    132     checkArgument(!internalName.endsWith(";"), internalName);
    133     if (internalName.startsWith(prefix)) {
    134       roots.add(KeepReference.classReference(internalName));
    135     }
    136   }
    137 
    138   void objectReference(String internalName) {
    139     // don't call this for method types, convert to Type instead
    140     checkArgument(internalName.charAt(0) != '(', internalName);
    141     if (internalName.charAt(0) == '[') {
    142       typeReference(internalName);
    143     } else {
    144       classReference(internalName);
    145     }
    146   }
    147 
    148   void typeReference(String typeDesc) {
    149     // don't call this for method types, convert to Type instead
    150     checkArgument(typeDesc.charAt(0) != '(', typeDesc);
    151 
    152     int lpos = typeDesc.lastIndexOf('[') + 1;
    153     if (typeDesc.charAt(lpos) == 'L') {
    154       checkArgument(typeDesc.endsWith(";"), typeDesc);
    155       classReference(typeDesc.substring(lpos, typeDesc.length() - 1));
    156     } else {
    157       // else primitive or primitive array
    158       checkArgument(typeDesc.length() == lpos + 1, typeDesc);
    159       switch (typeDesc.charAt(lpos)) {
    160         case 'B':
    161         case 'C':
    162         case 'S':
    163         case 'I':
    164         case 'J':
    165         case 'D':
    166         case 'F':
    167         case 'Z':
    168           break;
    169         default:
    170           throw new AssertionError("Unexpected type descriptor: " + typeDesc);
    171       }
    172     }
    173   }
    174 
    175   void typeReference(Type type) {
    176     switch (type.getSort()) {
    177       case Type.ARRAY:
    178         typeReference(type.getElementType());
    179         break;
    180       case Type.OBJECT:
    181         classReference(type.getInternalName());
    182         break;
    183 
    184       case Type.METHOD:
    185         for (Type param : type.getArgumentTypes()) {
    186           typeReference(param);
    187         }
    188         typeReference(type.getReturnType());
    189         break;
    190 
    191       default:
    192         break;
    193     }
    194   }
    195 
    196   void fieldReference(String owner, String name, String desc) {
    197     objectReference(owner);
    198     typeReference(desc);
    199     if (owner.startsWith(prefix)) {
    200       roots.add(KeepReference.memberReference(owner, name, desc));
    201     }
    202   }
    203 
    204   void methodReference(String owner, String name, String desc) {
    205     checkArgument(desc.charAt(0) == '(', desc);
    206     objectReference(owner);
    207     typeReference(Type.getMethodType(desc));
    208     if (owner.startsWith(prefix)) {
    209       roots.add(KeepReference.memberReference(owner, name, desc));
    210     }
    211   }
    212 
    213   void handleReference(Handle handle) {
    214     switch (handle.getTag()) {
    215       case Opcodes.H_GETFIELD:
    216       case Opcodes.H_GETSTATIC:
    217       case Opcodes.H_PUTFIELD:
    218       case Opcodes.H_PUTSTATIC:
    219         fieldReference(handle.getOwner(), handle.getName(), handle.getDesc());
    220         break;
    221 
    222       default:
    223         methodReference(handle.getOwner(), handle.getName(), handle.getDesc());
    224         break;
    225     }
    226   }
    227 
    228   private class PrefixReferenceMethodVisitor extends MethodVisitor {
    229 
    230     public PrefixReferenceMethodVisitor() {
    231       super(Opcodes.ASM6);
    232     }
    233 
    234     @Override
    235     public AnnotationVisitor visitAnnotationDefault() {
    236       return av;
    237     }
    238 
    239     @Override
    240     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    241       typeReference(desc);
    242       return av;
    243     }
    244 
    245     @Override
    246     public AnnotationVisitor visitTypeAnnotation(
    247         int typeRef, TypePath typePath, String desc, boolean visible) {
    248       typeReference(desc);
    249       return av;
    250     }
    251 
    252     @Override
    253     public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
    254       typeReference(desc);
    255       return av;
    256     }
    257 
    258     @Override
    259     public void visitTypeInsn(int opcode, String type) {
    260       objectReference(type);
    261     }
    262 
    263     @Override
    264     public void visitFieldInsn(int opcode, String owner, String name, String desc) {
    265       fieldReference(owner, name, desc);
    266     }
    267 
    268     @Override
    269     @SuppressWarnings("deprecation") // Implementing deprecated method to be sure
    270     public void visitMethodInsn(int opcode, String owner, String name, String desc) {
    271       visitMethodInsn(opcode, owner, name, desc, opcode == Opcodes.INVOKEINTERFACE);
    272     }
    273 
    274     @Override
    275     public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
    276       methodReference(owner, name, desc);
    277     }
    278 
    279     @Override
    280     public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
    281       typeReference(Type.getMethodType(desc));
    282       handleReference(bsm);
    283       for (Object bsmArg : bsmArgs) {
    284         visitConstant(bsmArg);
    285       }
    286     }
    287 
    288     @Override
    289     public void visitLdcInsn(Object cst) {
    290       visitConstant(cst);
    291     }
    292 
    293     private void visitConstant(Object cst) {
    294       if (cst instanceof Type) {
    295         typeReference((Type) cst);
    296       } else if (cst instanceof Handle) {
    297         handleReference((Handle) cst);
    298       } else {
    299         // Check for other expected types as javadoc recommends
    300         checkArgument(
    301             cst instanceof String
    302                 || cst instanceof Integer
    303                 || cst instanceof Long
    304                 || cst instanceof Float
    305                 || cst instanceof Double,
    306             "Unexpected constant: ", cst);
    307       }
    308     }
    309 
    310     @Override
    311     public void visitMultiANewArrayInsn(String desc, int dims) {
    312       typeReference(desc);
    313     }
    314 
    315     @Override
    316     public AnnotationVisitor visitInsnAnnotation(
    317         int typeRef, TypePath typePath, String desc, boolean visible) {
    318       typeReference(desc);
    319       return av;
    320     }
    321 
    322     @Override
    323     public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
    324       if (type != null) {
    325         classReference(type);
    326       }
    327     }
    328 
    329     @Override
    330     public AnnotationVisitor visitTryCatchAnnotation(
    331         int typeRef, TypePath typePath, String desc, boolean visible) {
    332       typeReference(desc);
    333       return av;
    334     }
    335 
    336     @Override
    337     public void visitLocalVariable(
    338         String name, String desc, String signature, Label start, Label end, int index) {
    339       typeReference(desc);
    340     }
    341 
    342     @Override
    343     public AnnotationVisitor visitLocalVariableAnnotation(
    344         int typeRef,
    345         TypePath typePath,
    346         Label[] start,
    347         Label[] end,
    348         int[] index,
    349         String desc,
    350         boolean visible) {
    351       typeReference(desc);
    352       return av;
    353     }
    354   }
    355 
    356   private class PrefixReferenceFieldVisitor extends FieldVisitor {
    357 
    358     public PrefixReferenceFieldVisitor() {
    359       super(Opcodes.ASM6);
    360     }
    361 
    362     @Override
    363     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    364       typeReference(desc);
    365       return av;
    366     }
    367 
    368     @Override
    369     public AnnotationVisitor visitTypeAnnotation(
    370         int typeRef, TypePath typePath, String desc, boolean visible) {
    371       typeReference(desc);
    372       return av;
    373     }
    374   }
    375 
    376   private class PrefixReferenceAnnotationVisitor extends AnnotationVisitor {
    377 
    378     public PrefixReferenceAnnotationVisitor() {
    379       super(Opcodes.ASM6);
    380     }
    381 
    382     @Override
    383     public void visit(String name, Object value) {
    384       if (value instanceof Type) {
    385         typeReference((Type) value);
    386       }
    387     }
    388 
    389     @Override
    390     public void visitEnum(String name, String desc, String value) {
    391       fieldReference(desc.substring(1, desc.length() - 1), value, desc);
    392     }
    393 
    394     @Override
    395     public AnnotationVisitor visitAnnotation(String name, String desc) {
    396       typeReference(desc);
    397       return av;
    398     }
    399 
    400     @Override
    401     public AnnotationVisitor visitArray(String name) {
    402       return av;
    403     }
    404   }
    405 }
    406