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 17package com.android.dx.dex.cf; 18 19import com.android.dex.util.ExceptionWithContext; 20import com.android.dx.cf.code.ConcreteMethod; 21import com.android.dx.cf.code.Ropper; 22import com.android.dx.cf.direct.DirectClassFile; 23import com.android.dx.cf.iface.Field; 24import com.android.dx.cf.iface.FieldList; 25import com.android.dx.cf.iface.Method; 26import com.android.dx.cf.iface.MethodList; 27import com.android.dx.dex.DexOptions; 28import com.android.dx.dex.code.DalvCode; 29import com.android.dx.dex.code.PositionList; 30import com.android.dx.dex.code.RopTranslator; 31import com.android.dx.dex.file.ClassDefItem; 32import com.android.dx.dex.file.DexFile; 33import com.android.dx.dex.file.EncodedField; 34import com.android.dx.dex.file.EncodedMethod; 35import com.android.dx.dex.file.FieldIdsSection; 36import com.android.dx.dex.file.MethodIdsSection; 37import com.android.dx.dex.file.TypeIdsSection; 38import com.android.dx.rop.annotation.Annotations; 39import com.android.dx.rop.annotation.AnnotationsList; 40import com.android.dx.rop.code.AccessFlags; 41import com.android.dx.rop.code.DexTranslationAdvice; 42import com.android.dx.rop.code.LocalVariableExtractor; 43import com.android.dx.rop.code.LocalVariableInfo; 44import com.android.dx.rop.code.RopMethod; 45import com.android.dx.rop.code.TranslationAdvice; 46import com.android.dx.rop.cst.Constant; 47import com.android.dx.rop.cst.ConstantPool; 48import com.android.dx.rop.cst.CstBaseMethodRef; 49import com.android.dx.rop.cst.CstBoolean; 50import com.android.dx.rop.cst.CstByte; 51import com.android.dx.rop.cst.CstChar; 52import com.android.dx.rop.cst.CstEnumRef; 53import com.android.dx.rop.cst.CstFieldRef; 54import com.android.dx.rop.cst.CstInteger; 55import com.android.dx.rop.cst.CstInterfaceMethodRef; 56import com.android.dx.rop.cst.CstMethodRef; 57import com.android.dx.rop.cst.CstShort; 58import com.android.dx.rop.cst.CstString; 59import com.android.dx.rop.cst.CstType; 60import com.android.dx.rop.cst.TypedConstant; 61import com.android.dx.rop.type.Type; 62import com.android.dx.rop.type.TypeList; 63import com.android.dx.ssa.Optimizer; 64 65/** 66 * Static method that turns {@code byte[]}s containing Java 67 * classfiles into {@link ClassDefItem} instances. 68 */ 69public 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 synchronized (dexFile) { 146 for (int i = 0; i < constantPoolSize; i++) { 147 Constant constant = constantPool.getOrNull(i); 148 if (constant instanceof CstMethodRef) { 149 methodIdsSection.intern((CstBaseMethodRef) constant); 150 } else if (constant instanceof CstInterfaceMethodRef) { 151 methodIdsSection.intern(((CstInterfaceMethodRef) constant).toMethodRef()); 152 } else if (constant instanceof CstFieldRef) { 153 fieldIdsSection.intern((CstFieldRef) constant); 154 } else if (constant instanceof CstEnumRef) { 155 fieldIdsSection.intern(((CstEnumRef) constant).getFieldRef()); 156 } else if (constant instanceof CstType) { 157 typeIdsSection.intern((CstType) constant); 158 } 159 } 160 } 161 162 return out; 163 } 164 165 /** 166 * Processes the fields of the given class. 167 * 168 * @param cf {@code non-null;} class being translated 169 * @param out {@code non-null;} output class 170 */ 171 private static void processFields( 172 DirectClassFile cf, ClassDefItem out, FieldIdsSection fieldIdsSection) { 173 CstType thisClass = cf.getThisClass(); 174 FieldList fields = cf.getFields(); 175 int sz = fields.size(); 176 177 for (int i = 0; i < sz; i++) { 178 Field one = fields.get(i); 179 try { 180 CstFieldRef field = new CstFieldRef(thisClass, one.getNat()); 181 int accessFlags = one.getAccessFlags(); 182 183 if (AccessFlags.isStatic(accessFlags)) { 184 TypedConstant constVal = one.getConstantValue(); 185 EncodedField fi = new EncodedField(field, accessFlags); 186 if (constVal != null) { 187 constVal = coerceConstant(constVal, field.getType()); 188 } 189 out.addStaticField(fi, constVal); 190 } else { 191 EncodedField fi = new EncodedField(field, accessFlags); 192 out.addInstanceField(fi); 193 } 194 195 Annotations annotations = 196 AttributeTranslator.getAnnotations(one.getAttributes()); 197 if (annotations.size() != 0) { 198 out.addFieldAnnotations(field, annotations); 199 } 200 synchronized (fieldIdsSection) { 201 fieldIdsSection.intern(field); 202 } 203 } catch (RuntimeException ex) { 204 String msg = "...while processing " + one.getName().toHuman() + 205 " " + one.getDescriptor().toHuman(); 206 throw ExceptionWithContext.withContext(ex, msg); 207 } 208 } 209 } 210 211 /** 212 * Helper for {@link #processFields}, which translates constants into 213 * more specific types if necessary. 214 * 215 * @param constant {@code non-null;} the constant in question 216 * @param type {@code non-null;} the desired type 217 */ 218 private static TypedConstant coerceConstant(TypedConstant constant, 219 Type type) { 220 Type constantType = constant.getType(); 221 222 if (constantType.equals(type)) { 223 return constant; 224 } 225 226 switch (type.getBasicType()) { 227 case Type.BT_BOOLEAN: { 228 return CstBoolean.make(((CstInteger) constant).getValue()); 229 } 230 case Type.BT_BYTE: { 231 return CstByte.make(((CstInteger) constant).getValue()); 232 } 233 case Type.BT_CHAR: { 234 return CstChar.make(((CstInteger) constant).getValue()); 235 } 236 case Type.BT_SHORT: { 237 return CstShort.make(((CstInteger) constant).getValue()); 238 } 239 default: { 240 throw new UnsupportedOperationException("can't coerce " + 241 constant + " to " + type); 242 } 243 } 244 } 245 246 /** 247 * Processes the methods of the given class. 248 * 249 * @param cf {@code non-null;} class being translated 250 * @param cfOptions {@code non-null;} options for class translation 251 * @param dexOptions {@code non-null;} options for dex output 252 * @param out {@code non-null;} output class 253 */ 254 private static void processMethods(DirectClassFile cf, CfOptions cfOptions, 255 DexOptions dexOptions, ClassDefItem out, MethodIdsSection methodIds) { 256 CstType thisClass = cf.getThisClass(); 257 MethodList methods = cf.getMethods(); 258 int sz = methods.size(); 259 260 for (int i = 0; i < sz; i++) { 261 Method one = methods.get(i); 262 try { 263 CstMethodRef meth = new CstMethodRef(thisClass, one.getNat()); 264 int accessFlags = one.getAccessFlags(); 265 boolean isStatic = AccessFlags.isStatic(accessFlags); 266 boolean isPrivate = AccessFlags.isPrivate(accessFlags); 267 boolean isNative = AccessFlags.isNative(accessFlags); 268 boolean isAbstract = AccessFlags.isAbstract(accessFlags); 269 boolean isConstructor = meth.isInstanceInit() || 270 meth.isClassInit(); 271 DalvCode code; 272 273 if (isNative || isAbstract) { 274 // There's no code for native or abstract methods. 275 code = null; 276 } else { 277 ConcreteMethod concrete = 278 new ConcreteMethod(one, cf, 279 (cfOptions.positionInfo != PositionList.NONE), 280 cfOptions.localInfo); 281 282 TranslationAdvice advice; 283 284 advice = DexTranslationAdvice.THE_ONE; 285 286 RopMethod rmeth = Ropper.convert(concrete, advice); 287 RopMethod nonOptRmeth = null; 288 int paramSize; 289 290 paramSize = meth.getParameterWordCount(isStatic); 291 292 String canonicalName 293 = thisClass.getClassType().getDescriptor() 294 + "." + one.getName().getString(); 295 296 if (cfOptions.optimize && 297 OptimizerOptions.shouldOptimize(canonicalName)) { 298 if (DEBUG) { 299 System.err.println("Optimizing " + canonicalName); 300 } 301 302 nonOptRmeth = rmeth; 303 rmeth = Optimizer.optimize(rmeth, 304 paramSize, isStatic, cfOptions.localInfo, advice); 305 306 if (DEBUG) { 307 OptimizerOptions.compareOptimizerStep(nonOptRmeth, 308 paramSize, isStatic, cfOptions, advice, rmeth); 309 } 310 311 if (cfOptions.statistics) { 312 CodeStatistics.updateRopStatistics( 313 nonOptRmeth, rmeth); 314 } 315 } 316 317 LocalVariableInfo locals = null; 318 319 if (cfOptions.localInfo) { 320 locals = LocalVariableExtractor.extract(rmeth); 321 } 322 323 code = RopTranslator.translate(rmeth, cfOptions.positionInfo, 324 locals, paramSize, dexOptions); 325 326 if (cfOptions.statistics && nonOptRmeth != null) { 327 updateDexStatistics(cfOptions, dexOptions, rmeth, nonOptRmeth, locals, 328 paramSize, concrete.getCode().size()); 329 } 330 } 331 332 // Preserve the synchronized flag as its "declared" variant... 333 if (AccessFlags.isSynchronized(accessFlags)) { 334 accessFlags |= AccessFlags.ACC_DECLARED_SYNCHRONIZED; 335 336 /* 337 * ...but only native methods are actually allowed to be 338 * synchronized. 339 */ 340 if (!isNative) { 341 accessFlags &= ~AccessFlags.ACC_SYNCHRONIZED; 342 } 343 } 344 345 if (isConstructor) { 346 accessFlags |= AccessFlags.ACC_CONSTRUCTOR; 347 } 348 349 TypeList exceptions = AttributeTranslator.getExceptions(one); 350 EncodedMethod mi = 351 new EncodedMethod(meth, accessFlags, code, exceptions); 352 353 if (meth.isInstanceInit() || meth.isClassInit() || 354 isStatic || isPrivate) { 355 out.addDirectMethod(mi); 356 } else { 357 out.addVirtualMethod(mi); 358 } 359 360 Annotations annotations = 361 AttributeTranslator.getMethodAnnotations(one); 362 if (annotations.size() != 0) { 363 out.addMethodAnnotations(meth, annotations); 364 } 365 366 AnnotationsList list = 367 AttributeTranslator.getParameterAnnotations(one); 368 if (list.size() != 0) { 369 out.addParameterAnnotations(meth, list); 370 } 371 synchronized (methodIds) { 372 methodIds.intern(meth); 373 } 374 } catch (RuntimeException ex) { 375 String msg = "...while processing " + one.getName().toHuman() + 376 " " + one.getDescriptor().toHuman(); 377 throw ExceptionWithContext.withContext(ex, msg); 378 } 379 } 380 } 381 382 /** 383 * Helper that updates the dex statistics. 384 */ 385 private static void updateDexStatistics(CfOptions cfOptions, DexOptions dexOptions, 386 RopMethod optRmeth, RopMethod nonOptRmeth, 387 LocalVariableInfo locals, int paramSize, int originalByteCount) { 388 /* 389 * Run rop->dex again on optimized vs. non-optimized method to 390 * collect statistics. We have to totally convert both ways, 391 * since converting the "real" method getting added to the 392 * file would corrupt it (by messing with its constant pool 393 * indices). 394 */ 395 396 DalvCode optCode = RopTranslator.translate(optRmeth, 397 cfOptions.positionInfo, locals, paramSize, dexOptions); 398 DalvCode nonOptCode = RopTranslator.translate(nonOptRmeth, 399 cfOptions.positionInfo, locals, paramSize, dexOptions); 400 401 /* 402 * Fake out the indices, so code.getInsns() can work well enough 403 * for the current purpose. 404 */ 405 406 DalvCode.AssignIndicesCallback callback = 407 new DalvCode.AssignIndicesCallback() { 408 public int getIndex(Constant cst) { 409 // Everything is at index 0! 410 return 0; 411 } 412 }; 413 414 optCode.assignIndices(callback); 415 nonOptCode.assignIndices(callback); 416 417 CodeStatistics.updateDexStatistics(nonOptCode, optCode); 418 CodeStatistics.updateOriginalByteCount(originalByteCount); 419 } 420} 421