/* * 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); } }