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