diff options
Diffstat (limited to 'dx/src/com/android/dx/dex/cf/CfTranslator.java')
-rw-r--r-- | dx/src/com/android/dx/dex/cf/CfTranslator.java | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/dex/cf/CfTranslator.java b/dx/src/com/android/dx/dex/cf/CfTranslator.java new file mode 100644 index 0000000..8b94f91 --- /dev/null +++ b/dx/src/com/android/dx/dex/cf/CfTranslator.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.cf; + +import com.android.dx.cf.code.ConcreteMethod; +import com.android.dx.cf.code.Ropper; +import com.android.dx.cf.direct.DirectClassFile; +import com.android.dx.cf.direct.StdAttributeFactory; +import com.android.dx.cf.iface.Field; +import com.android.dx.cf.iface.FieldList; +import com.android.dx.cf.iface.Method; +import com.android.dx.cf.iface.MethodList; +import com.android.dx.dex.DexOptions; +import com.android.dx.dex.code.DalvCode; +import com.android.dx.dex.code.PositionList; +import com.android.dx.dex.code.RopTranslator; +import com.android.dx.dex.file.ClassDefItem; +import com.android.dx.dex.file.EncodedField; +import com.android.dx.dex.file.EncodedMethod; +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.code.LocalVariableExtractor; +import com.android.dx.rop.code.LocalVariableInfo; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.DexTranslationAdvice; +import com.android.dx.rop.code.TranslationAdvice; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstBoolean; +import com.android.dx.rop.cst.CstByte; +import com.android.dx.rop.cst.CstChar; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstShort; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.TypedConstant; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.ssa.Optimizer; +import com.android.dx.util.ExceptionWithContext; + +/** + * Static method that turns {@code byte[]}s containing Java + * classfiles into {@link ClassDefItem} instances. + */ +public class CfTranslator { + /** set to {@code true} to enable development-time debugging code */ + private static final boolean DEBUG = false; + + /** + * This class is uninstantiable. + */ + private CfTranslator() { + // This space intentionally left blank. + } + + /** + * Takes a {@code byte[]}, interprets it as a Java classfile, and + * translates it into a {@link ClassDefItem}. + * + * @param filePath {@code non-null;} the file path for the class, + * excluding any base directory specification + * @param bytes {@code non-null;} contents of the file + * @param cfOptions options for class translation + * @param dexOptions options for dex output + * @return {@code non-null;} the translated class + */ + public static ClassDefItem translate(String filePath, byte[] bytes, + CfOptions cfOptions, DexOptions dexOptions) { + try { + return translate0(filePath, bytes, cfOptions, dexOptions); + } catch (RuntimeException ex) { + String msg = "...while processing " + filePath; + throw ExceptionWithContext.withContext(ex, msg); + } + } + + /** + * Performs the main act of translation. This method is separated + * from {@link #translate} just to keep things a bit simpler in + * terms of exception handling. + * + * @param filePath {@code non-null;} the file path for the class, + * excluding any base directory specification + * @param bytes {@code non-null;} contents of the file + * @param cfOptions options for class translation + * @param dexOptions options for dex output + * @return {@code non-null;} the translated class + */ + private static ClassDefItem translate0(String filePath, byte[] bytes, + CfOptions cfOptions, DexOptions dexOptions) { + DirectClassFile cf = + new DirectClassFile(bytes, filePath, cfOptions.strictNameCheck); + + cf.setAttributeFactory(StdAttributeFactory.THE_ONE); + cf.getMagic(); + + OptimizerOptions.loadOptimizeLists(cfOptions.optimizeListFile, + cfOptions.dontOptimizeListFile); + + // Build up a class to output. + + CstType thisClass = cf.getThisClass(); + int classAccessFlags = cf.getAccessFlags() & ~AccessFlags.ACC_SUPER; + CstString sourceFile = (cfOptions.positionInfo == PositionList.NONE) ? null : + cf.getSourceFile(); + ClassDefItem out = + new ClassDefItem(thisClass, classAccessFlags, + cf.getSuperclass(), cf.getInterfaces(), sourceFile); + + Annotations classAnnotations = + AttributeTranslator.getClassAnnotations(cf, cfOptions); + if (classAnnotations.size() != 0) { + out.setClassAnnotations(classAnnotations); + } + + processFields(cf, out); + processMethods(cf, cfOptions, dexOptions, out); + + return out; + } + + /** + * Processes the fields of the given class. + * + * @param cf {@code non-null;} class being translated + * @param out {@code non-null;} output class + */ + private static void processFields(DirectClassFile cf, ClassDefItem out) { + CstType thisClass = cf.getThisClass(); + FieldList fields = cf.getFields(); + int sz = fields.size(); + + for (int i = 0; i < sz; i++) { + Field one = fields.get(i); + try { + CstFieldRef field = new CstFieldRef(thisClass, one.getNat()); + int accessFlags = one.getAccessFlags(); + + if (AccessFlags.isStatic(accessFlags)) { + TypedConstant constVal = one.getConstantValue(); + EncodedField fi = new EncodedField(field, accessFlags); + if (constVal != null) { + constVal = coerceConstant(constVal, field.getType()); + } + out.addStaticField(fi, constVal); + } else { + EncodedField fi = new EncodedField(field, accessFlags); + out.addInstanceField(fi); + } + + Annotations annotations = + AttributeTranslator.getAnnotations(one.getAttributes()); + if (annotations.size() != 0) { + out.addFieldAnnotations(field, annotations); + } + } catch (RuntimeException ex) { + String msg = "...while processing " + one.getName().toHuman() + + " " + one.getDescriptor().toHuman(); + throw ExceptionWithContext.withContext(ex, msg); + } + } + } + + /** + * Helper for {@link #processFields}, which translates constants into + * more specific types if necessary. + * + * @param constant {@code non-null;} the constant in question + * @param type {@code non-null;} the desired type + */ + private static TypedConstant coerceConstant(TypedConstant constant, + Type type) { + Type constantType = constant.getType(); + + if (constantType.equals(type)) { + return constant; + } + + switch (type.getBasicType()) { + case Type.BT_BOOLEAN: { + return CstBoolean.make(((CstInteger) constant).getValue()); + } + case Type.BT_BYTE: { + return CstByte.make(((CstInteger) constant).getValue()); + } + case Type.BT_CHAR: { + return CstChar.make(((CstInteger) constant).getValue()); + } + case Type.BT_SHORT: { + return CstShort.make(((CstInteger) constant).getValue()); + } + default: { + throw new UnsupportedOperationException("can't coerce " + + constant + " to " + type); + } + } + } + + /** + * Processes the methods of the given class. + * + * @param cf {@code non-null;} class being translated + * @param cfOptions {@code non-null;} options for class translation + * @param dexOptions {@code non-null;} options for dex output + * @param out {@code non-null;} output class + */ + private static void processMethods(DirectClassFile cf, CfOptions cfOptions, + DexOptions dexOptions, ClassDefItem out) { + CstType thisClass = cf.getThisClass(); + MethodList methods = cf.getMethods(); + int sz = methods.size(); + + for (int i = 0; i < sz; i++) { + Method one = methods.get(i); + try { + CstMethodRef meth = new CstMethodRef(thisClass, one.getNat()); + int accessFlags = one.getAccessFlags(); + boolean isStatic = AccessFlags.isStatic(accessFlags); + boolean isPrivate = AccessFlags.isPrivate(accessFlags); + boolean isNative = AccessFlags.isNative(accessFlags); + boolean isAbstract = AccessFlags.isAbstract(accessFlags); + boolean isConstructor = meth.isInstanceInit() || + meth.isClassInit(); + DalvCode code; + + if (isNative || isAbstract) { + // There's no code for native or abstract methods. + code = null; + } else { + ConcreteMethod concrete = + new ConcreteMethod(one, cf, + (cfOptions.positionInfo != PositionList.NONE), + cfOptions.localInfo); + + TranslationAdvice advice; + + advice = DexTranslationAdvice.THE_ONE; + + RopMethod rmeth = Ropper.convert(concrete, advice); + RopMethod nonOptRmeth = null; + int paramSize; + + paramSize = meth.getParameterWordCount(isStatic); + + String canonicalName + = thisClass.getClassType().getDescriptor() + + "." + one.getName().getString(); + + if (cfOptions.optimize && + OptimizerOptions.shouldOptimize(canonicalName)) { + if (DEBUG) { + System.err.println("Optimizing " + canonicalName); + } + + nonOptRmeth = rmeth; + rmeth = Optimizer.optimize(rmeth, + paramSize, isStatic, cfOptions.localInfo, advice); + + if (DEBUG) { + OptimizerOptions.compareOptimizerStep(nonOptRmeth, + paramSize, isStatic, cfOptions, advice, rmeth); + } + + if (cfOptions.statistics) { + CodeStatistics.updateRopStatistics( + nonOptRmeth, rmeth); + } + } + + LocalVariableInfo locals = null; + + if (cfOptions.localInfo) { + locals = LocalVariableExtractor.extract(rmeth); + } + + code = RopTranslator.translate(rmeth, cfOptions.positionInfo, + locals, paramSize, dexOptions); + + if (cfOptions.statistics && nonOptRmeth != null) { + updateDexStatistics(cfOptions, dexOptions, rmeth, nonOptRmeth, locals, + paramSize, concrete.getCode().size()); + } + } + + // Preserve the synchronized flag as its "declared" variant... + if (AccessFlags.isSynchronized(accessFlags)) { + accessFlags |= AccessFlags.ACC_DECLARED_SYNCHRONIZED; + + /* + * ...but only native methods are actually allowed to be + * synchronized. + */ + if (!isNative) { + accessFlags &= ~AccessFlags.ACC_SYNCHRONIZED; + } + } + + if (isConstructor) { + accessFlags |= AccessFlags.ACC_CONSTRUCTOR; + } + + TypeList exceptions = AttributeTranslator.getExceptions(one); + EncodedMethod mi = + new EncodedMethod(meth, accessFlags, code, exceptions); + + if (meth.isInstanceInit() || meth.isClassInit() || + isStatic || isPrivate) { + out.addDirectMethod(mi); + } else { + out.addVirtualMethod(mi); + } + + Annotations annotations = + AttributeTranslator.getMethodAnnotations(one); + if (annotations.size() != 0) { + out.addMethodAnnotations(meth, annotations); + } + + AnnotationsList list = + AttributeTranslator.getParameterAnnotations(one); + if (list.size() != 0) { + out.addParameterAnnotations(meth, list); + } + } catch (RuntimeException ex) { + String msg = "...while processing " + one.getName().toHuman() + + " " + one.getDescriptor().toHuman(); + throw ExceptionWithContext.withContext(ex, msg); + } + } + } + + /** + * Helper that updates the dex statistics. + */ + private static void updateDexStatistics(CfOptions cfOptions, DexOptions dexOptions, + RopMethod optRmeth, RopMethod nonOptRmeth, + LocalVariableInfo locals, int paramSize, int originalByteCount) { + /* + * Run rop->dex again on optimized vs. non-optimized method to + * collect statistics. We have to totally convert both ways, + * since converting the "real" method getting added to the + * file would corrupt it (by messing with its constant pool + * indices). + */ + + DalvCode optCode = RopTranslator.translate(optRmeth, + cfOptions.positionInfo, locals, paramSize, dexOptions); + DalvCode nonOptCode = RopTranslator.translate(nonOptRmeth, + cfOptions.positionInfo, locals, paramSize, dexOptions); + + /* + * Fake out the indices, so code.getInsns() can work well enough + * for the current purpose. + */ + + DalvCode.AssignIndicesCallback callback = + new DalvCode.AssignIndicesCallback() { + public int getIndex(Constant cst) { + // Everything is at index 0! + return 0; + } + }; + + optCode.assignIndices(callback); + nonOptCode.assignIndices(callback); + + CodeStatistics.updateDexStatistics(nonOptCode, optCode); + CodeStatistics.updateOriginalByteCount(originalByteCount); + } +} |