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
17package com.android.dx.dex.cf;
18
19import com.android.dex.util.ExceptionWithContext;
20import com.android.dx.cf.code.ConcreteMethod;
21import com.android.dx.cf.code.Ropper;
22import com.android.dx.cf.direct.DirectClassFile;
23import com.android.dx.cf.iface.Field;
24import com.android.dx.cf.iface.FieldList;
25import com.android.dx.cf.iface.Method;
26import com.android.dx.cf.iface.MethodList;
27import com.android.dx.dex.DexOptions;
28import com.android.dx.dex.code.DalvCode;
29import com.android.dx.dex.code.PositionList;
30import com.android.dx.dex.code.RopTranslator;
31import com.android.dx.dex.file.ClassDefItem;
32import com.android.dx.dex.file.DexFile;
33import com.android.dx.dex.file.EncodedField;
34import com.android.dx.dex.file.EncodedMethod;
35import com.android.dx.dex.file.FieldIdsSection;
36import com.android.dx.dex.file.MethodIdsSection;
37import com.android.dx.dex.file.TypeIdsSection;
38import com.android.dx.rop.annotation.Annotations;
39import com.android.dx.rop.annotation.AnnotationsList;
40import com.android.dx.rop.code.AccessFlags;
41import com.android.dx.rop.code.DexTranslationAdvice;
42import com.android.dx.rop.code.LocalVariableExtractor;
43import com.android.dx.rop.code.LocalVariableInfo;
44import com.android.dx.rop.code.RopMethod;
45import com.android.dx.rop.code.TranslationAdvice;
46import com.android.dx.rop.cst.Constant;
47import com.android.dx.rop.cst.ConstantPool;
48import com.android.dx.rop.cst.CstBaseMethodRef;
49import com.android.dx.rop.cst.CstBoolean;
50import com.android.dx.rop.cst.CstByte;
51import com.android.dx.rop.cst.CstChar;
52import com.android.dx.rop.cst.CstEnumRef;
53import com.android.dx.rop.cst.CstFieldRef;
54import com.android.dx.rop.cst.CstInteger;
55import com.android.dx.rop.cst.CstInterfaceMethodRef;
56import com.android.dx.rop.cst.CstMethodRef;
57import com.android.dx.rop.cst.CstShort;
58import com.android.dx.rop.cst.CstString;
59import com.android.dx.rop.cst.CstType;
60import com.android.dx.rop.cst.TypedConstant;
61import com.android.dx.rop.type.Type;
62import com.android.dx.rop.type.TypeList;
63import com.android.dx.ssa.Optimizer;
64
65/**
66 * Static method that turns {@code byte[]}s containing Java
67 * classfiles into {@link ClassDefItem} instances.
68 */
69public 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        synchronized (dexFile) {
146            for (int i = 0; i < constantPoolSize; i++) {
147                Constant constant = constantPool.getOrNull(i);
148                if (constant instanceof CstMethodRef) {
149                    methodIdsSection.intern((CstBaseMethodRef) constant);
150                } else if (constant instanceof CstInterfaceMethodRef) {
151                    methodIdsSection.intern(((CstInterfaceMethodRef) constant).toMethodRef());
152                } else if (constant instanceof CstFieldRef) {
153                    fieldIdsSection.intern((CstFieldRef) constant);
154                } else if (constant instanceof CstEnumRef) {
155                    fieldIdsSection.intern(((CstEnumRef) constant).getFieldRef());
156                } else if (constant instanceof CstType) {
157                    typeIdsSection.intern((CstType) constant);
158                }
159            }
160        }
161
162        return out;
163    }
164
165    /**
166     * Processes the fields of the given class.
167     *
168     * @param cf {@code non-null;} class being translated
169     * @param out {@code non-null;} output class
170     */
171    private static void processFields(
172            DirectClassFile cf, ClassDefItem out, FieldIdsSection fieldIdsSection) {
173        CstType thisClass = cf.getThisClass();
174        FieldList fields = cf.getFields();
175        int sz = fields.size();
176
177        for (int i = 0; i < sz; i++) {
178            Field one = fields.get(i);
179            try {
180                CstFieldRef field = new CstFieldRef(thisClass, one.getNat());
181                int accessFlags = one.getAccessFlags();
182
183                if (AccessFlags.isStatic(accessFlags)) {
184                    TypedConstant constVal = one.getConstantValue();
185                    EncodedField fi = new EncodedField(field, accessFlags);
186                    if (constVal != null) {
187                        constVal = coerceConstant(constVal, field.getType());
188                    }
189                    out.addStaticField(fi, constVal);
190                } else {
191                    EncodedField fi = new EncodedField(field, accessFlags);
192                    out.addInstanceField(fi);
193                }
194
195                Annotations annotations =
196                    AttributeTranslator.getAnnotations(one.getAttributes());
197                if (annotations.size() != 0) {
198                    out.addFieldAnnotations(field, annotations);
199                }
200                synchronized (fieldIdsSection) {
201                    fieldIdsSection.intern(field);
202                }
203            } catch (RuntimeException ex) {
204                String msg = "...while processing " + one.getName().toHuman() +
205                    " " + one.getDescriptor().toHuman();
206                throw ExceptionWithContext.withContext(ex, msg);
207            }
208        }
209    }
210
211    /**
212     * Helper for {@link #processFields}, which translates constants into
213     * more specific types if necessary.
214     *
215     * @param constant {@code non-null;} the constant in question
216     * @param type {@code non-null;} the desired type
217     */
218    private static TypedConstant coerceConstant(TypedConstant constant,
219            Type type) {
220        Type constantType = constant.getType();
221
222        if (constantType.equals(type)) {
223            return constant;
224        }
225
226        switch (type.getBasicType()) {
227            case Type.BT_BOOLEAN: {
228                return CstBoolean.make(((CstInteger) constant).getValue());
229            }
230            case Type.BT_BYTE: {
231                return CstByte.make(((CstInteger) constant).getValue());
232            }
233            case Type.BT_CHAR: {
234                return CstChar.make(((CstInteger) constant).getValue());
235            }
236            case Type.BT_SHORT: {
237                return CstShort.make(((CstInteger) constant).getValue());
238            }
239            default: {
240                throw new UnsupportedOperationException("can't coerce " +
241                        constant + " to " + type);
242            }
243        }
244    }
245
246    /**
247     * Processes the methods of the given class.
248     *
249     * @param cf {@code non-null;} class being translated
250     * @param cfOptions {@code non-null;} options for class translation
251     * @param dexOptions {@code non-null;} options for dex output
252     * @param out {@code non-null;} output class
253     */
254    private static void processMethods(DirectClassFile cf, CfOptions cfOptions,
255            DexOptions dexOptions, ClassDefItem out, MethodIdsSection methodIds) {
256        CstType thisClass = cf.getThisClass();
257        MethodList methods = cf.getMethods();
258        int sz = methods.size();
259
260        for (int i = 0; i < sz; i++) {
261            Method one = methods.get(i);
262            try {
263                CstMethodRef meth = new CstMethodRef(thisClass, one.getNat());
264                int accessFlags = one.getAccessFlags();
265                boolean isStatic = AccessFlags.isStatic(accessFlags);
266                boolean isPrivate = AccessFlags.isPrivate(accessFlags);
267                boolean isNative = AccessFlags.isNative(accessFlags);
268                boolean isAbstract = AccessFlags.isAbstract(accessFlags);
269                boolean isConstructor = meth.isInstanceInit() ||
270                    meth.isClassInit();
271                DalvCode code;
272
273                if (isNative || isAbstract) {
274                    // There's no code for native or abstract methods.
275                    code = null;
276                } else {
277                    ConcreteMethod concrete =
278                        new ConcreteMethod(one, cf,
279                                (cfOptions.positionInfo != PositionList.NONE),
280                                cfOptions.localInfo);
281
282                    TranslationAdvice advice;
283
284                    advice = DexTranslationAdvice.THE_ONE;
285
286                    RopMethod rmeth = Ropper.convert(concrete, advice);
287                    RopMethod nonOptRmeth = null;
288                    int paramSize;
289
290                    paramSize = meth.getParameterWordCount(isStatic);
291
292                    String canonicalName
293                            = thisClass.getClassType().getDescriptor()
294                                + "." + one.getName().getString();
295
296                    if (cfOptions.optimize &&
297                            OptimizerOptions.shouldOptimize(canonicalName)) {
298                        if (DEBUG) {
299                            System.err.println("Optimizing " + canonicalName);
300                        }
301
302                        nonOptRmeth = rmeth;
303                        rmeth = Optimizer.optimize(rmeth,
304                                paramSize, isStatic, cfOptions.localInfo, advice);
305
306                        if (DEBUG) {
307                            OptimizerOptions.compareOptimizerStep(nonOptRmeth,
308                                    paramSize, isStatic, cfOptions, advice, rmeth);
309                        }
310
311                        if (cfOptions.statistics) {
312                            CodeStatistics.updateRopStatistics(
313                                    nonOptRmeth, rmeth);
314                        }
315                    }
316
317                    LocalVariableInfo locals = null;
318
319                    if (cfOptions.localInfo) {
320                        locals = LocalVariableExtractor.extract(rmeth);
321                    }
322
323                    code = RopTranslator.translate(rmeth, cfOptions.positionInfo,
324                            locals, paramSize, dexOptions);
325
326                    if (cfOptions.statistics && nonOptRmeth != null) {
327                        updateDexStatistics(cfOptions, dexOptions, rmeth, nonOptRmeth, locals,
328                                paramSize, concrete.getCode().size());
329                    }
330                }
331
332                // Preserve the synchronized flag as its "declared" variant...
333                if (AccessFlags.isSynchronized(accessFlags)) {
334                    accessFlags |= AccessFlags.ACC_DECLARED_SYNCHRONIZED;
335
336                    /*
337                     * ...but only native methods are actually allowed to be
338                     * synchronized.
339                     */
340                    if (!isNative) {
341                        accessFlags &= ~AccessFlags.ACC_SYNCHRONIZED;
342                    }
343                }
344
345                if (isConstructor) {
346                    accessFlags |= AccessFlags.ACC_CONSTRUCTOR;
347                }
348
349                TypeList exceptions = AttributeTranslator.getExceptions(one);
350                EncodedMethod mi =
351                    new EncodedMethod(meth, accessFlags, code, exceptions);
352
353                if (meth.isInstanceInit() || meth.isClassInit() ||
354                    isStatic || isPrivate) {
355                    out.addDirectMethod(mi);
356                } else {
357                    out.addVirtualMethod(mi);
358                }
359
360                Annotations annotations =
361                    AttributeTranslator.getMethodAnnotations(one);
362                if (annotations.size() != 0) {
363                    out.addMethodAnnotations(meth, annotations);
364                }
365
366                AnnotationsList list =
367                    AttributeTranslator.getParameterAnnotations(one);
368                if (list.size() != 0) {
369                    out.addParameterAnnotations(meth, list);
370                }
371                synchronized (methodIds) {
372                  methodIds.intern(meth);
373                }
374            } catch (RuntimeException ex) {
375                String msg = "...while processing " + one.getName().toHuman() +
376                    " " + one.getDescriptor().toHuman();
377                throw ExceptionWithContext.withContext(ex, msg);
378            }
379        }
380    }
381
382    /**
383     * Helper that updates the dex statistics.
384     */
385    private static void updateDexStatistics(CfOptions cfOptions, DexOptions dexOptions,
386            RopMethod optRmeth, RopMethod nonOptRmeth,
387            LocalVariableInfo locals, int paramSize, int originalByteCount) {
388        /*
389         * Run rop->dex again on optimized vs. non-optimized method to
390         * collect statistics. We have to totally convert both ways,
391         * since converting the "real" method getting added to the
392         * file would corrupt it (by messing with its constant pool
393         * indices).
394         */
395
396        DalvCode optCode = RopTranslator.translate(optRmeth,
397                cfOptions.positionInfo, locals, paramSize, dexOptions);
398        DalvCode nonOptCode = RopTranslator.translate(nonOptRmeth,
399                cfOptions.positionInfo, locals, paramSize, dexOptions);
400
401        /*
402         * Fake out the indices, so code.getInsns() can work well enough
403         * for the current purpose.
404         */
405
406        DalvCode.AssignIndicesCallback callback =
407            new DalvCode.AssignIndicesCallback() {
408                public int getIndex(Constant cst) {
409                    // Everything is at index 0!
410                    return 0;
411                }
412            };
413
414        optCode.assignIndices(callback);
415        nonOptCode.assignIndices(callback);
416
417        CodeStatistics.updateDexStatistics(nonOptCode, optCode);
418        CodeStatistics.updateOriginalByteCount(originalByteCount);
419    }
420}
421