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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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