1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dx.dex.cf; 18 19 import com.android.dex.util.ExceptionWithContext; 20 import com.android.dx.cf.code.ConcreteMethod; 21 import com.android.dx.cf.code.Ropper; 22 import com.android.dx.cf.direct.DirectClassFile; 23 import com.android.dx.cf.iface.Field; 24 import com.android.dx.cf.iface.FieldList; 25 import com.android.dx.cf.iface.Method; 26 import com.android.dx.cf.iface.MethodList; 27 import com.android.dx.dex.DexOptions; 28 import com.android.dx.dex.code.DalvCode; 29 import com.android.dx.dex.code.PositionList; 30 import com.android.dx.dex.code.RopTranslator; 31 import com.android.dx.dex.file.ClassDefItem; 32 import com.android.dx.dex.file.DexFile; 33 import com.android.dx.dex.file.EncodedField; 34 import com.android.dx.dex.file.EncodedMethod; 35 import com.android.dx.dex.file.FieldIdsSection; 36 import com.android.dx.dex.file.MethodIdsSection; 37 import com.android.dx.dex.file.TypeIdsSection; 38 import com.android.dx.rop.annotation.Annotations; 39 import com.android.dx.rop.annotation.AnnotationsList; 40 import com.android.dx.rop.code.AccessFlags; 41 import com.android.dx.rop.code.DexTranslationAdvice; 42 import com.android.dx.rop.code.LocalVariableExtractor; 43 import com.android.dx.rop.code.LocalVariableInfo; 44 import com.android.dx.rop.code.RopMethod; 45 import com.android.dx.rop.code.TranslationAdvice; 46 import com.android.dx.rop.cst.Constant; 47 import com.android.dx.rop.cst.ConstantPool; 48 import com.android.dx.rop.cst.CstBaseMethodRef; 49 import com.android.dx.rop.cst.CstBoolean; 50 import com.android.dx.rop.cst.CstByte; 51 import com.android.dx.rop.cst.CstChar; 52 import com.android.dx.rop.cst.CstEnumRef; 53 import com.android.dx.rop.cst.CstFieldRef; 54 import com.android.dx.rop.cst.CstInteger; 55 import com.android.dx.rop.cst.CstInterfaceMethodRef; 56 import com.android.dx.rop.cst.CstMethodRef; 57 import com.android.dx.rop.cst.CstShort; 58 import com.android.dx.rop.cst.CstString; 59 import com.android.dx.rop.cst.CstType; 60 import com.android.dx.rop.cst.TypedConstant; 61 import com.android.dx.rop.type.Type; 62 import com.android.dx.rop.type.TypeList; 63 import com.android.dx.ssa.Optimizer; 64 65 /** 66 * Static method that turns {@code byte[]}s containing Java 67 * classfiles into {@link ClassDefItem} instances. 68 */ 69 public class CfTranslator { 70 /** set to {@code true} to enable development-time debugging code */ 71 private static final boolean DEBUG = false; 72 73 /** 74 * This class is uninstantiable. 75 */ CfTranslator()76 private CfTranslator() { 77 // This space intentionally left blank. 78 } 79 80 /** 81 * Takes a {@code byte[]}, interprets it as a Java classfile, and 82 * translates it into a {@link ClassDefItem}. 83 * 84 * @param filePath {@code non-null;} the file path for the class, 85 * excluding any base directory specification 86 * @param bytes {@code non-null;} contents of the file 87 * @param cfOptions options for class translation 88 * @param dexOptions options for dex output 89 * @return {@code non-null;} the translated class 90 */ translate(DirectClassFile cf, byte[] bytes, CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile)91 public static ClassDefItem translate(DirectClassFile cf, byte[] bytes, 92 CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) { 93 try { 94 return translate0(cf, bytes, cfOptions, dexOptions, dexFile); 95 } catch (RuntimeException ex) { 96 String msg = "...while processing " + cf.getFilePath(); 97 throw ExceptionWithContext.withContext(ex, msg); 98 } 99 } 100 101 /** 102 * Performs the main act of translation. This method is separated 103 * from {@link #translate} just to keep things a bit simpler in 104 * terms of exception handling. 105 * 106 * @param filePath {@code non-null;} the file path for the class, 107 * excluding any base directory specification 108 * @param bytes {@code non-null;} contents of the file 109 * @param cfOptions options for class translation 110 * @param dexOptions options for dex output 111 * @return {@code non-null;} the translated class 112 */ translate0(DirectClassFile cf, byte[] bytes, CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile)113 private static ClassDefItem translate0(DirectClassFile cf, byte[] bytes, 114 CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) { 115 116 OptimizerOptions.loadOptimizeLists(cfOptions.optimizeListFile, 117 cfOptions.dontOptimizeListFile); 118 119 // Build up a class to output. 120 121 CstType thisClass = cf.getThisClass(); 122 int classAccessFlags = cf.getAccessFlags() & ~AccessFlags.ACC_SUPER; 123 CstString sourceFile = (cfOptions.positionInfo == PositionList.NONE) ? null : 124 cf.getSourceFile(); 125 ClassDefItem out = 126 new ClassDefItem(thisClass, classAccessFlags, 127 cf.getSuperclass(), cf.getInterfaces(), sourceFile); 128 129 Annotations classAnnotations = 130 AttributeTranslator.getClassAnnotations(cf, cfOptions); 131 if (classAnnotations.size() != 0) { 132 out.setClassAnnotations(classAnnotations); 133 } 134 135 FieldIdsSection fieldIdsSection = dexFile.getFieldIds(); 136 MethodIdsSection methodIdsSection = dexFile.getMethodIds(); 137 TypeIdsSection typeIdsSection = dexFile.getTypeIds(); 138 processFields(cf, out, fieldIdsSection); 139 processMethods(cf, cfOptions, dexOptions, out, methodIdsSection); 140 141 // intern constant pool method, field and type references 142 ConstantPool constantPool = cf.getConstantPool(); 143 int constantPoolSize = constantPool.size(); 144 145 for (int i = 0; i < constantPoolSize; i++) { 146 Constant constant = constantPool.getOrNull(i); 147 if (constant instanceof CstMethodRef) { 148 methodIdsSection.intern((CstBaseMethodRef) constant); 149 } else if (constant instanceof CstInterfaceMethodRef) { 150 methodIdsSection.intern(((CstInterfaceMethodRef) constant).toMethodRef()); 151 } else if (constant instanceof CstFieldRef) { 152 fieldIdsSection.intern((CstFieldRef) constant); 153 } else if (constant instanceof CstEnumRef) { 154 fieldIdsSection.intern(((CstEnumRef) constant).getFieldRef()); 155 } else if (constant instanceof CstType) { 156 typeIdsSection.intern((CstType) constant); 157 } 158 } 159 160 return out; 161 } 162 163 /** 164 * Processes the fields of the given class. 165 * 166 * @param cf {@code non-null;} class being translated 167 * @param out {@code non-null;} output class 168 */ processFields( DirectClassFile cf, ClassDefItem out, FieldIdsSection fieldIdsSection)169 private static void processFields( 170 DirectClassFile cf, ClassDefItem out, FieldIdsSection fieldIdsSection) { 171 CstType thisClass = cf.getThisClass(); 172 FieldList fields = cf.getFields(); 173 int sz = fields.size(); 174 175 for (int i = 0; i < sz; i++) { 176 Field one = fields.get(i); 177 try { 178 CstFieldRef field = new CstFieldRef(thisClass, one.getNat()); 179 int accessFlags = one.getAccessFlags(); 180 181 if (AccessFlags.isStatic(accessFlags)) { 182 TypedConstant constVal = one.getConstantValue(); 183 EncodedField fi = new EncodedField(field, accessFlags); 184 if (constVal != null) { 185 constVal = coerceConstant(constVal, field.getType()); 186 } 187 out.addStaticField(fi, constVal); 188 } else { 189 EncodedField fi = new EncodedField(field, accessFlags); 190 out.addInstanceField(fi); 191 } 192 193 Annotations annotations = 194 AttributeTranslator.getAnnotations(one.getAttributes()); 195 if (annotations.size() != 0) { 196 out.addFieldAnnotations(field, annotations); 197 } 198 fieldIdsSection.intern(field); 199 } catch (RuntimeException ex) { 200 String msg = "...while processing " + one.getName().toHuman() + 201 " " + one.getDescriptor().toHuman(); 202 throw ExceptionWithContext.withContext(ex, msg); 203 } 204 } 205 } 206 207 /** 208 * Helper for {@link #processFields}, which translates constants into 209 * more specific types if necessary. 210 * 211 * @param constant {@code non-null;} the constant in question 212 * @param type {@code non-null;} the desired type 213 */ coerceConstant(TypedConstant constant, Type type)214 private static TypedConstant coerceConstant(TypedConstant constant, 215 Type type) { 216 Type constantType = constant.getType(); 217 218 if (constantType.equals(type)) { 219 return constant; 220 } 221 222 switch (type.getBasicType()) { 223 case Type.BT_BOOLEAN: { 224 return CstBoolean.make(((CstInteger) constant).getValue()); 225 } 226 case Type.BT_BYTE: { 227 return CstByte.make(((CstInteger) constant).getValue()); 228 } 229 case Type.BT_CHAR: { 230 return CstChar.make(((CstInteger) constant).getValue()); 231 } 232 case Type.BT_SHORT: { 233 return CstShort.make(((CstInteger) constant).getValue()); 234 } 235 default: { 236 throw new UnsupportedOperationException("can't coerce " + 237 constant + " to " + type); 238 } 239 } 240 } 241 242 /** 243 * Processes the methods of the given class. 244 * 245 * @param cf {@code non-null;} class being translated 246 * @param cfOptions {@code non-null;} options for class translation 247 * @param dexOptions {@code non-null;} options for dex output 248 * @param out {@code non-null;} output class 249 */ processMethods(DirectClassFile cf, CfOptions cfOptions, DexOptions dexOptions, ClassDefItem out, MethodIdsSection methodIds)250 private static void processMethods(DirectClassFile cf, CfOptions cfOptions, 251 DexOptions dexOptions, ClassDefItem out, MethodIdsSection methodIds) { 252 CstType thisClass = cf.getThisClass(); 253 MethodList methods = cf.getMethods(); 254 int sz = methods.size(); 255 256 for (int i = 0; i < sz; i++) { 257 Method one = methods.get(i); 258 try { 259 CstMethodRef meth = new CstMethodRef(thisClass, one.getNat()); 260 int accessFlags = one.getAccessFlags(); 261 boolean isStatic = AccessFlags.isStatic(accessFlags); 262 boolean isPrivate = AccessFlags.isPrivate(accessFlags); 263 boolean isNative = AccessFlags.isNative(accessFlags); 264 boolean isAbstract = AccessFlags.isAbstract(accessFlags); 265 boolean isConstructor = meth.isInstanceInit() || 266 meth.isClassInit(); 267 DalvCode code; 268 269 if (isNative || isAbstract) { 270 // There's no code for native or abstract methods. 271 code = null; 272 } else { 273 ConcreteMethod concrete = 274 new ConcreteMethod(one, cf, 275 (cfOptions.positionInfo != PositionList.NONE), 276 cfOptions.localInfo); 277 278 TranslationAdvice advice; 279 280 advice = DexTranslationAdvice.THE_ONE; 281 282 RopMethod rmeth = Ropper.convert(concrete, advice); 283 RopMethod nonOptRmeth = null; 284 int paramSize; 285 286 paramSize = meth.getParameterWordCount(isStatic); 287 288 String canonicalName 289 = thisClass.getClassType().getDescriptor() 290 + "." + one.getName().getString(); 291 292 if (cfOptions.optimize && 293 OptimizerOptions.shouldOptimize(canonicalName)) { 294 if (DEBUG) { 295 System.err.println("Optimizing " + canonicalName); 296 } 297 298 nonOptRmeth = rmeth; 299 rmeth = Optimizer.optimize(rmeth, 300 paramSize, isStatic, cfOptions.localInfo, advice); 301 302 if (DEBUG) { 303 OptimizerOptions.compareOptimizerStep(nonOptRmeth, 304 paramSize, isStatic, cfOptions, advice, rmeth); 305 } 306 307 if (cfOptions.statistics) { 308 CodeStatistics.updateRopStatistics( 309 nonOptRmeth, rmeth); 310 } 311 } 312 313 LocalVariableInfo locals = null; 314 315 if (cfOptions.localInfo) { 316 locals = LocalVariableExtractor.extract(rmeth); 317 } 318 319 code = RopTranslator.translate(rmeth, cfOptions.positionInfo, 320 locals, paramSize, dexOptions); 321 322 if (cfOptions.statistics && nonOptRmeth != null) { 323 updateDexStatistics(cfOptions, dexOptions, rmeth, nonOptRmeth, locals, 324 paramSize, concrete.getCode().size()); 325 } 326 } 327 328 // Preserve the synchronized flag as its "declared" variant... 329 if (AccessFlags.isSynchronized(accessFlags)) { 330 accessFlags |= AccessFlags.ACC_DECLARED_SYNCHRONIZED; 331 332 /* 333 * ...but only native methods are actually allowed to be 334 * synchronized. 335 */ 336 if (!isNative) { 337 accessFlags &= ~AccessFlags.ACC_SYNCHRONIZED; 338 } 339 } 340 341 if (isConstructor) { 342 accessFlags |= AccessFlags.ACC_CONSTRUCTOR; 343 } 344 345 TypeList exceptions = AttributeTranslator.getExceptions(one); 346 EncodedMethod mi = 347 new EncodedMethod(meth, accessFlags, code, exceptions); 348 349 if (meth.isInstanceInit() || meth.isClassInit() || 350 isStatic || isPrivate) { 351 out.addDirectMethod(mi); 352 } else { 353 out.addVirtualMethod(mi); 354 } 355 356 Annotations annotations = 357 AttributeTranslator.getMethodAnnotations(one); 358 if (annotations.size() != 0) { 359 out.addMethodAnnotations(meth, annotations); 360 } 361 362 AnnotationsList list = 363 AttributeTranslator.getParameterAnnotations(one); 364 if (list.size() != 0) { 365 out.addParameterAnnotations(meth, list); 366 } 367 methodIds.intern(meth); 368 } catch (RuntimeException ex) { 369 String msg = "...while processing " + one.getName().toHuman() + 370 " " + one.getDescriptor().toHuman(); 371 throw ExceptionWithContext.withContext(ex, msg); 372 } 373 } 374 } 375 376 /** 377 * Helper that updates the dex statistics. 378 */ updateDexStatistics(CfOptions cfOptions, DexOptions dexOptions, RopMethod optRmeth, RopMethod nonOptRmeth, LocalVariableInfo locals, int paramSize, int originalByteCount)379 private static void updateDexStatistics(CfOptions cfOptions, DexOptions dexOptions, 380 RopMethod optRmeth, RopMethod nonOptRmeth, 381 LocalVariableInfo locals, int paramSize, int originalByteCount) { 382 /* 383 * Run rop->dex again on optimized vs. non-optimized method to 384 * collect statistics. We have to totally convert both ways, 385 * since converting the "real" method getting added to the 386 * file would corrupt it (by messing with its constant pool 387 * indices). 388 */ 389 390 DalvCode optCode = RopTranslator.translate(optRmeth, 391 cfOptions.positionInfo, locals, paramSize, dexOptions); 392 DalvCode nonOptCode = RopTranslator.translate(nonOptRmeth, 393 cfOptions.positionInfo, locals, paramSize, dexOptions); 394 395 /* 396 * Fake out the indices, so code.getInsns() can work well enough 397 * for the current purpose. 398 */ 399 400 DalvCode.AssignIndicesCallback callback = 401 new DalvCode.AssignIndicesCallback() { 402 public int getIndex(Constant cst) { 403 // Everything is at index 0! 404 return 0; 405 } 406 }; 407 408 optCode.assignIndices(callback); 409 nonOptCode.assignIndices(callback); 410 411 CodeStatistics.updateDexStatistics(nonOptCode, optCode); 412 CodeStatistics.updateOriginalByteCount(originalByteCount); 413 } 414 } 415