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.dx.cf.code.ConcreteMethod;
20import com.android.dx.cf.code.Ropper;
21import com.android.dx.cf.direct.DirectClassFile;
22import com.android.dx.cf.direct.StdAttributeFactory;
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.EncodedField;
33import com.android.dx.dex.file.EncodedMethod;
34import com.android.dx.rop.annotation.Annotations;
35import com.android.dx.rop.annotation.AnnotationsList;
36import com.android.dx.rop.code.AccessFlags;
37import com.android.dx.rop.code.LocalVariableExtractor;
38import com.android.dx.rop.code.LocalVariableInfo;
39import com.android.dx.rop.code.RopMethod;
40import com.android.dx.rop.code.DexTranslationAdvice;
41import com.android.dx.rop.code.TranslationAdvice;
42import com.android.dx.rop.cst.Constant;
43import com.android.dx.rop.cst.CstBoolean;
44import com.android.dx.rop.cst.CstByte;
45import com.android.dx.rop.cst.CstChar;
46import com.android.dx.rop.cst.CstFieldRef;
47import com.android.dx.rop.cst.CstInteger;
48import com.android.dx.rop.cst.CstMethodRef;
49import com.android.dx.rop.cst.CstShort;
50import com.android.dx.rop.cst.CstString;
51import com.android.dx.rop.cst.CstType;
52import com.android.dx.rop.cst.TypedConstant;
53import com.android.dx.rop.type.Type;
54import com.android.dx.rop.type.TypeList;
55import com.android.dx.ssa.Optimizer;
56import com.android.dx.util.ExceptionWithContext;
57
58/**
59 * Static method that turns {@code byte[]}s containing Java
60 * classfiles into {@link ClassDefItem} instances.
61 */
62public 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