| /* |
| * 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.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.CstType; |
| import com.android.dx.rop.cst.CstUtf8; |
| 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 args command-line arguments |
| * @return {@code non-null;} the translated class |
| */ |
| public static ClassDefItem translate(String filePath, byte[] bytes, |
| CfOptions args) { |
| try { |
| return translate0(filePath, bytes, args); |
| } 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 args command-line arguments |
| * @return {@code non-null;} the translated class |
| */ |
| private static ClassDefItem translate0(String filePath, byte[] bytes, |
| CfOptions args) { |
| DirectClassFile cf = |
| new DirectClassFile(bytes, filePath, args.strictNameCheck); |
| |
| cf.setAttributeFactory(StdAttributeFactory.THE_ONE); |
| cf.getMagic(); |
| |
| OptimizerOptions.loadOptimizeLists(args.optimizeListFile, |
| args.dontOptimizeListFile); |
| |
| // Build up a class to output. |
| |
| CstType thisClass = cf.getThisClass(); |
| int classAccessFlags = cf.getAccessFlags() & ~AccessFlags.ACC_SUPER; |
| CstUtf8 sourceFile = (args.positionInfo == PositionList.NONE) ? null : |
| cf.getSourceFile(); |
| ClassDefItem out = |
| new ClassDefItem(thisClass, classAccessFlags, |
| cf.getSuperclass(), cf.getInterfaces(), sourceFile); |
| |
| Annotations classAnnotations = |
| AttributeTranslator.getClassAnnotations(cf, args); |
| if (classAnnotations.size() != 0) { |
| out.setClassAnnotations(classAnnotations); |
| } |
| |
| processFields(cf, out); |
| processMethods(cf, args, 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 args {@code non-null;} command-line args |
| * @param out {@code non-null;} output class |
| */ |
| private static void processMethods(DirectClassFile cf, |
| CfOptions args, 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, |
| (args.positionInfo != PositionList.NONE), |
| args.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 (args.optimize && |
| OptimizerOptions.shouldOptimize(canonicalName)) { |
| if (DEBUG) { |
| System.err.println("Optimizing " + canonicalName); |
| } |
| |
| nonOptRmeth = rmeth; |
| rmeth = Optimizer.optimize(rmeth, |
| paramSize, isStatic, args.localInfo, advice); |
| |
| if (DEBUG) { |
| OptimizerOptions.compareOptimizerStep(nonOptRmeth, |
| paramSize, isStatic, args, advice, rmeth); |
| } |
| |
| if (args.statistics) { |
| CodeStatistics.updateRopStatistics( |
| nonOptRmeth, rmeth); |
| } |
| } |
| |
| LocalVariableInfo locals = null; |
| |
| if (args.localInfo) { |
| locals = LocalVariableExtractor.extract(rmeth); |
| } |
| |
| code = RopTranslator.translate(rmeth, args.positionInfo, |
| locals, paramSize); |
| |
| if (args.statistics && nonOptRmeth != null) { |
| updateDexStatistics(args, 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 args, |
| 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, |
| args.positionInfo, locals, paramSize); |
| DalvCode nonOptCode = RopTranslator.translate(nonOptRmeth, |
| args.positionInfo, locals, paramSize); |
| |
| /* |
| * 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); |
| } |
| } |