Home | History | Annotate | Download | only in cf
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dx.dex.cf;
     18 
     19 import com.android.dex.util.ExceptionWithContext;
     20 import com.android.dx.cf.code.ConcreteMethod;
     21 import com.android.dx.cf.code.Ropper;
     22 import com.android.dx.cf.direct.DirectClassFile;
     23 import com.android.dx.cf.iface.Field;
     24 import com.android.dx.cf.iface.FieldList;
     25 import com.android.dx.cf.iface.Method;
     26 import com.android.dx.cf.iface.MethodList;
     27 import com.android.dx.dex.DexOptions;
     28 import com.android.dx.dex.code.DalvCode;
     29 import com.android.dx.dex.code.PositionList;
     30 import com.android.dx.dex.code.RopTranslator;
     31 import com.android.dx.dex.file.ClassDefItem;
     32 import com.android.dx.dex.file.DexFile;
     33 import com.android.dx.dex.file.EncodedField;
     34 import com.android.dx.dex.file.EncodedMethod;
     35 import com.android.dx.dex.file.FieldIdsSection;
     36 import com.android.dx.dex.file.MethodIdsSection;
     37 import com.android.dx.dex.file.TypeIdsSection;
     38 import com.android.dx.rop.annotation.Annotations;
     39 import com.android.dx.rop.annotation.AnnotationsList;
     40 import com.android.dx.rop.code.AccessFlags;
     41 import com.android.dx.rop.code.DexTranslationAdvice;
     42 import com.android.dx.rop.code.LocalVariableExtractor;
     43 import com.android.dx.rop.code.LocalVariableInfo;
     44 import com.android.dx.rop.code.RopMethod;
     45 import com.android.dx.rop.code.TranslationAdvice;
     46 import com.android.dx.rop.cst.Constant;
     47 import com.android.dx.rop.cst.ConstantPool;
     48 import com.android.dx.rop.cst.CstBaseMethodRef;
     49 import com.android.dx.rop.cst.CstBoolean;
     50 import com.android.dx.rop.cst.CstByte;
     51 import com.android.dx.rop.cst.CstChar;
     52 import com.android.dx.rop.cst.CstEnumRef;
     53 import com.android.dx.rop.cst.CstFieldRef;
     54 import com.android.dx.rop.cst.CstInteger;
     55 import com.android.dx.rop.cst.CstInterfaceMethodRef;
     56 import com.android.dx.rop.cst.CstMethodRef;
     57 import com.android.dx.rop.cst.CstShort;
     58 import com.android.dx.rop.cst.CstString;
     59 import com.android.dx.rop.cst.CstType;
     60 import com.android.dx.rop.cst.TypedConstant;
     61 import com.android.dx.rop.type.Type;
     62 import com.android.dx.rop.type.TypeList;
     63 import com.android.dx.ssa.Optimizer;
     64 
     65 /**
     66  * Static method that turns {@code byte[]}s containing Java
     67  * classfiles into {@link ClassDefItem} instances.
     68  */
     69 public class CfTranslator {
     70     /** set to {@code true} to enable development-time debugging code */
     71     private static final boolean DEBUG = false;
     72 
     73     /**
     74      * This class is uninstantiable.
     75      */
     76     private CfTranslator() {
     77         // This space intentionally left blank.
     78     }
     79 
     80     /**
     81      * Takes a {@code byte[]}, interprets it as a Java classfile, and
     82      * translates it into a {@link ClassDefItem}.
     83      *
     84      * @param filePath {@code non-null;} the file path for the class,
     85      * excluding any base directory specification
     86      * @param bytes {@code non-null;} contents of the file
     87      * @param cfOptions options for class translation
     88      * @param dexOptions options for dex output
     89      * @return {@code non-null;} the translated class
     90      */
     91     public static ClassDefItem translate(DirectClassFile cf, byte[] bytes,
     92             CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) {
     93         try {
     94             return translate0(cf, bytes, cfOptions, dexOptions, dexFile);
     95         } catch (RuntimeException ex) {
     96             String msg = "...while processing " + cf.getFilePath();
     97             throw ExceptionWithContext.withContext(ex, msg);
     98         }
     99     }
    100 
    101     /**
    102      * Performs the main act of translation. This method is separated
    103      * from {@link #translate} just to keep things a bit simpler in
    104      * terms of exception handling.
    105      *
    106      * @param filePath {@code non-null;} the file path for the class,
    107      * excluding any base directory specification
    108      * @param bytes {@code non-null;} contents of the file
    109      * @param cfOptions options for class translation
    110      * @param dexOptions options for dex output
    111      * @return {@code non-null;} the translated class
    112      */
    113     private static ClassDefItem translate0(DirectClassFile cf, byte[] bytes,
    114             CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) {
    115 
    116         OptimizerOptions.loadOptimizeLists(cfOptions.optimizeListFile,
    117                 cfOptions.dontOptimizeListFile);
    118 
    119         // Build up a class to output.
    120 
    121         CstType thisClass = cf.getThisClass();
    122         int classAccessFlags = cf.getAccessFlags() & ~AccessFlags.ACC_SUPER;
    123         CstString sourceFile = (cfOptions.positionInfo == PositionList.NONE) ? null :
    124             cf.getSourceFile();
    125         ClassDefItem out =
    126             new ClassDefItem(thisClass, classAccessFlags,
    127                     cf.getSuperclass(), cf.getInterfaces(), sourceFile);
    128 
    129         Annotations classAnnotations =
    130             AttributeTranslator.getClassAnnotations(cf, cfOptions);
    131         if (classAnnotations.size() != 0) {
    132             out.setClassAnnotations(classAnnotations);
    133         }
    134 
    135         FieldIdsSection fieldIdsSection = dexFile.getFieldIds();
    136         MethodIdsSection methodIdsSection = dexFile.getMethodIds();
    137         TypeIdsSection typeIdsSection = dexFile.getTypeIds();
    138         processFields(cf, out, fieldIdsSection);
    139         processMethods(cf, cfOptions, dexOptions, out, methodIdsSection);
    140 
    141         // intern constant pool method, field and type references
    142         ConstantPool constantPool = cf.getConstantPool();
    143         int constantPoolSize = constantPool.size();
    144 
    145         for (int i = 0; i < constantPoolSize; i++) {
    146             Constant constant = constantPool.getOrNull(i);
    147             if (constant instanceof CstMethodRef) {
    148                 methodIdsSection.intern((CstBaseMethodRef) constant);
    149             } else if (constant instanceof CstInterfaceMethodRef) {
    150                 methodIdsSection.intern(((CstInterfaceMethodRef) constant).toMethodRef());
    151             } else if (constant instanceof CstFieldRef) {
    152                 fieldIdsSection.intern((CstFieldRef) constant);
    153             } else if (constant instanceof CstEnumRef) {
    154                 fieldIdsSection.intern(((CstEnumRef) constant).getFieldRef());
    155             } else if (constant instanceof CstType) {
    156                 typeIdsSection.intern((CstType) constant);
    157             }
    158         }
    159 
    160         return out;
    161     }
    162 
    163     /**
    164      * Processes the fields of the given class.
    165      *
    166      * @param cf {@code non-null;} class being translated
    167      * @param out {@code non-null;} output class
    168      */
    169     private static void processFields(
    170             DirectClassFile cf, ClassDefItem out, FieldIdsSection fieldIdsSection) {
    171         CstType thisClass = cf.getThisClass();
    172         FieldList fields = cf.getFields();
    173         int sz = fields.size();
    174 
    175         for (int i = 0; i < sz; i++) {
    176             Field one = fields.get(i);
    177             try {
    178                 CstFieldRef field = new CstFieldRef(thisClass, one.getNat());
    179                 int accessFlags = one.getAccessFlags();
    180 
    181                 if (AccessFlags.isStatic(accessFlags)) {
    182                     TypedConstant constVal = one.getConstantValue();
    183                     EncodedField fi = new EncodedField(field, accessFlags);
    184                     if (constVal != null) {
    185                         constVal = coerceConstant(constVal, field.getType());
    186                     }
    187                     out.addStaticField(fi, constVal);
    188                 } else {
    189                     EncodedField fi = new EncodedField(field, accessFlags);
    190                     out.addInstanceField(fi);
    191                 }
    192 
    193                 Annotations annotations =
    194                     AttributeTranslator.getAnnotations(one.getAttributes());
    195                 if (annotations.size() != 0) {
    196                     out.addFieldAnnotations(field, annotations);
    197                 }
    198                 fieldIdsSection.intern(field);
    199             } catch (RuntimeException ex) {
    200                 String msg = "...while processing " + one.getName().toHuman() +
    201                     " " + one.getDescriptor().toHuman();
    202                 throw ExceptionWithContext.withContext(ex, msg);
    203             }
    204         }
    205     }
    206 
    207     /**
    208      * Helper for {@link #processFields}, which translates constants into
    209      * more specific types if necessary.
    210      *
    211      * @param constant {@code non-null;} the constant in question
    212      * @param type {@code non-null;} the desired type
    213      */
    214     private static TypedConstant coerceConstant(TypedConstant constant,
    215             Type type) {
    216         Type constantType = constant.getType();
    217 
    218         if (constantType.equals(type)) {
    219             return constant;
    220         }
    221 
    222         switch (type.getBasicType()) {
    223             case Type.BT_BOOLEAN: {
    224                 return CstBoolean.make(((CstInteger) constant).getValue());
    225             }
    226             case Type.BT_BYTE: {
    227                 return CstByte.make(((CstInteger) constant).getValue());
    228             }
    229             case Type.BT_CHAR: {
    230                 return CstChar.make(((CstInteger) constant).getValue());
    231             }
    232             case Type.BT_SHORT: {
    233                 return CstShort.make(((CstInteger) constant).getValue());
    234             }
    235             default: {
    236                 throw new UnsupportedOperationException("can't coerce " +
    237                         constant + " to " + type);
    238             }
    239         }
    240     }
    241 
    242     /**
    243      * Processes the methods of the given class.
    244      *
    245      * @param cf {@code non-null;} class being translated
    246      * @param cfOptions {@code non-null;} options for class translation
    247      * @param dexOptions {@code non-null;} options for dex output
    248      * @param out {@code non-null;} output class
    249      */
    250     private static void processMethods(DirectClassFile cf, CfOptions cfOptions,
    251             DexOptions dexOptions, ClassDefItem out, MethodIdsSection methodIds) {
    252         CstType thisClass = cf.getThisClass();
    253         MethodList methods = cf.getMethods();
    254         int sz = methods.size();
    255 
    256         for (int i = 0; i < sz; i++) {
    257             Method one = methods.get(i);
    258             try {
    259                 CstMethodRef meth = new CstMethodRef(thisClass, one.getNat());
    260                 int accessFlags = one.getAccessFlags();
    261                 boolean isStatic = AccessFlags.isStatic(accessFlags);
    262                 boolean isPrivate = AccessFlags.isPrivate(accessFlags);
    263                 boolean isNative = AccessFlags.isNative(accessFlags);
    264                 boolean isAbstract = AccessFlags.isAbstract(accessFlags);
    265                 boolean isConstructor = meth.isInstanceInit() ||
    266                     meth.isClassInit();
    267                 DalvCode code;
    268 
    269                 if (isNative || isAbstract) {
    270                     // There's no code for native or abstract methods.
    271                     code = null;
    272                 } else {
    273                     ConcreteMethod concrete =
    274                         new ConcreteMethod(one, cf,
    275                                 (cfOptions.positionInfo != PositionList.NONE),
    276                                 cfOptions.localInfo);
    277 
    278                     TranslationAdvice advice;
    279 
    280                     advice = DexTranslationAdvice.THE_ONE;
    281 
    282                     RopMethod rmeth = Ropper.convert(concrete, advice);
    283                     RopMethod nonOptRmeth = null;
    284                     int paramSize;
    285 
    286                     paramSize = meth.getParameterWordCount(isStatic);
    287 
    288                     String canonicalName
    289                             = thisClass.getClassType().getDescriptor()
    290                                 + "." + one.getName().getString();
    291 
    292                     if (cfOptions.optimize &&
    293                             OptimizerOptions.shouldOptimize(canonicalName)) {
    294                         if (DEBUG) {
    295                             System.err.println("Optimizing " + canonicalName);
    296                         }
    297 
    298                         nonOptRmeth = rmeth;
    299                         rmeth = Optimizer.optimize(rmeth,
    300                                 paramSize, isStatic, cfOptions.localInfo, advice);
    301 
    302                         if (DEBUG) {
    303                             OptimizerOptions.compareOptimizerStep(nonOptRmeth,
    304                                     paramSize, isStatic, cfOptions, advice, rmeth);
    305                         }
    306 
    307                         if (cfOptions.statistics) {
    308                             CodeStatistics.updateRopStatistics(
    309                                     nonOptRmeth, rmeth);
    310                         }
    311                     }
    312 
    313                     LocalVariableInfo locals = null;
    314 
    315                     if (cfOptions.localInfo) {
    316                         locals = LocalVariableExtractor.extract(rmeth);
    317                     }
    318 
    319                     code = RopTranslator.translate(rmeth, cfOptions.positionInfo,
    320                             locals, paramSize, dexOptions);
    321 
    322                     if (cfOptions.statistics && nonOptRmeth != null) {
    323                         updateDexStatistics(cfOptions, dexOptions, rmeth, nonOptRmeth, locals,
    324                                 paramSize, concrete.getCode().size());
    325                     }
    326                 }
    327 
    328                 // Preserve the synchronized flag as its "declared" variant...
    329                 if (AccessFlags.isSynchronized(accessFlags)) {
    330                     accessFlags |= AccessFlags.ACC_DECLARED_SYNCHRONIZED;
    331 
    332                     /*
    333                      * ...but only native methods are actually allowed to be
    334                      * synchronized.
    335                      */
    336                     if (!isNative) {
    337                         accessFlags &= ~AccessFlags.ACC_SYNCHRONIZED;
    338                     }
    339                 }
    340 
    341                 if (isConstructor) {
    342                     accessFlags |= AccessFlags.ACC_CONSTRUCTOR;
    343                 }
    344 
    345                 TypeList exceptions = AttributeTranslator.getExceptions(one);
    346                 EncodedMethod mi =
    347                     new EncodedMethod(meth, accessFlags, code, exceptions);
    348 
    349                 if (meth.isInstanceInit() || meth.isClassInit() ||
    350                     isStatic || isPrivate) {
    351                     out.addDirectMethod(mi);
    352                 } else {
    353                     out.addVirtualMethod(mi);
    354                 }
    355 
    356                 Annotations annotations =
    357                     AttributeTranslator.getMethodAnnotations(one);
    358                 if (annotations.size() != 0) {
    359                     out.addMethodAnnotations(meth, annotations);
    360                 }
    361 
    362                 AnnotationsList list =
    363                     AttributeTranslator.getParameterAnnotations(one);
    364                 if (list.size() != 0) {
    365                     out.addParameterAnnotations(meth, list);
    366                 }
    367                 methodIds.intern(meth);
    368             } catch (RuntimeException ex) {
    369                 String msg = "...while processing " + one.getName().toHuman() +
    370                     " " + one.getDescriptor().toHuman();
    371                 throw ExceptionWithContext.withContext(ex, msg);
    372             }
    373         }
    374     }
    375 
    376     /**
    377      * Helper that updates the dex statistics.
    378      */
    379     private static void updateDexStatistics(CfOptions cfOptions, DexOptions dexOptions,
    380             RopMethod optRmeth, RopMethod nonOptRmeth,
    381             LocalVariableInfo locals, int paramSize, int originalByteCount) {
    382         /*
    383          * Run rop->dex again on optimized vs. non-optimized method to
    384          * collect statistics. We have to totally convert both ways,
    385          * since converting the "real" method getting added to the
    386          * file would corrupt it (by messing with its constant pool
    387          * indices).
    388          */
    389 
    390         DalvCode optCode = RopTranslator.translate(optRmeth,
    391                 cfOptions.positionInfo, locals, paramSize, dexOptions);
    392         DalvCode nonOptCode = RopTranslator.translate(nonOptRmeth,
    393                 cfOptions.positionInfo, locals, paramSize, dexOptions);
    394 
    395         /*
    396          * Fake out the indices, so code.getInsns() can work well enough
    397          * for the current purpose.
    398          */
    399 
    400         DalvCode.AssignIndicesCallback callback =
    401             new DalvCode.AssignIndicesCallback() {
    402                 public int getIndex(Constant cst) {
    403                     // Everything is at index 0!
    404                     return 0;
    405                 }
    406             };
    407 
    408         optCode.assignIndices(callback);
    409         nonOptCode.assignIndices(callback);
    410 
    411         CodeStatistics.updateDexStatistics(nonOptCode, optCode);
    412         CodeStatistics.updateOriginalByteCount(originalByteCount);
    413     }
    414 }
    415