blob: 92ea0f7a0ae4f33ffd8a2b41e0f8fb2d4627834d [file] [log] [blame]
/*
* 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.dex.util.ExceptionWithContext;
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.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.DexFile;
import com.android.dx.dex.file.EncodedField;
import com.android.dx.dex.file.EncodedMethod;
import com.android.dx.dex.file.FieldIdsSection;
import com.android.dx.dex.file.MethodIdsSection;
import com.android.dx.dex.file.TypeIdsSection;
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.DexTranslationAdvice;
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.TranslationAdvice;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.ConstantPool;
import com.android.dx.rop.cst.CstBaseMethodRef;
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.CstEnumRef;
import com.android.dx.rop.cst.CstFieldRef;
import com.android.dx.rop.cst.CstInteger;
import com.android.dx.rop.cst.CstInterfaceMethodRef;
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;
/**
* 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(DirectClassFile cf, byte[] bytes,
CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) {
try {
return translate0(cf, bytes, cfOptions, dexOptions, dexFile);
} catch (RuntimeException ex) {
String msg = "...while processing " + cf.getFilePath();
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(DirectClassFile cf, byte[] bytes,
CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) {
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);
}
FieldIdsSection fieldIdsSection = dexFile.getFieldIds();
MethodIdsSection methodIdsSection = dexFile.getMethodIds();
TypeIdsSection typeIdsSection = dexFile.getTypeIds();
processFields(cf, out, fieldIdsSection);
processMethods(cf, cfOptions, dexOptions, out, methodIdsSection);
// intern constant pool method, field and type references
ConstantPool constantPool = cf.getConstantPool();
int constantPoolSize = constantPool.size();
for (int i = 0; i < constantPoolSize; i++) {
Constant constant = constantPool.getOrNull(i);
if (constant instanceof CstMethodRef) {
methodIdsSection.intern((CstBaseMethodRef) constant);
} else if (constant instanceof CstInterfaceMethodRef) {
methodIdsSection.intern(((CstInterfaceMethodRef) constant).toMethodRef());
} else if (constant instanceof CstFieldRef) {
fieldIdsSection.intern((CstFieldRef) constant);
} else if (constant instanceof CstEnumRef) {
fieldIdsSection.intern(((CstEnumRef) constant).getFieldRef());
} else if (constant instanceof CstType) {
typeIdsSection.intern((CstType) constant);
}
}
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, FieldIdsSection fieldIdsSection) {
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);
}
fieldIdsSection.intern(field);
} 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, MethodIdsSection methodIds) {
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);
}
methodIds.intern(meth);
} 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);
}
}