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