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