summaryrefslogtreecommitdiff
path: root/dx/src/com/android/dx/dex/cf/CfTranslator.java
diff options
context:
space:
mode:
Diffstat (limited to 'dx/src/com/android/dx/dex/cf/CfTranslator.java')
-rw-r--r--dx/src/com/android/dx/dex/cf/CfTranslator.java387
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);
+ }
+}