• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
CfTranslator()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      */
translate(DirectClassFile cf, byte[] bytes, CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile)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      */
translate0(DirectClassFile cf, byte[] bytes, CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile)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      */
processFields( DirectClassFile cf, ClassDefItem out, FieldIdsSection fieldIdsSection)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      */
coerceConstant(TypedConstant constant, Type type)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      */
processMethods(DirectClassFile cf, CfOptions cfOptions, DexOptions dexOptions, ClassDefItem out, MethodIdsSection methodIds)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      */
updateDexStatistics(CfOptions cfOptions, DexOptions dexOptions, RopMethod optRmeth, RopMethod nonOptRmeth, LocalVariableInfo locals, int paramSize, int originalByteCount)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