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