1 /* 2 * Copyright (C) 2011 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; 18 19 import com.android.dex.DexFormat; 20 import com.android.dx.dex.DexOptions; 21 import com.android.dx.dex.code.DalvCode; 22 import com.android.dx.dex.code.PositionList; 23 import com.android.dx.dex.code.RopTranslator; 24 import com.android.dx.dex.file.ClassDefItem; 25 import com.android.dx.dex.file.DexFile; 26 import com.android.dx.dex.file.EncodedField; 27 import com.android.dx.dex.file.EncodedMethod; 28 import com.android.dx.rop.code.AccessFlags; 29 import com.android.dx.rop.code.LocalVariableInfo; 30 import com.android.dx.rop.code.RopMethod; 31 import com.android.dx.rop.cst.CstString; 32 import com.android.dx.rop.cst.CstType; 33 import com.android.dx.rop.type.StdTypeList; 34 35 import java.io.BufferedOutputStream; 36 import java.io.File; 37 import java.io.FileOutputStream; 38 import java.io.IOException; 39 import java.lang.reflect.InvocationTargetException; 40 import java.lang.reflect.Modifier; 41 import java.util.Arrays; 42 import java.util.Iterator; 43 import java.util.LinkedHashMap; 44 import java.util.Map; 45 import java.util.Set; 46 import java.util.jar.JarEntry; 47 import java.util.jar.JarOutputStream; 48 49 import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR; 50 import static java.lang.reflect.Modifier.*; 51 ; 52 53 /** 54 * Generates a <strong>D</strong>alvik <strong>EX</strong>ecutable (dex) 55 * file for execution on Android. Dex files define classes and interfaces, 56 * including their member methods and fields, executable code, and debugging 57 * information. They also define annotations, though this API currently has no 58 * facility to create a dex file that contains annotations. 59 * 60 * <p>This library is intended to satisfy two use cases: 61 * <ul> 62 * <li><strong>For runtime code generation.</strong> By embedding this library 63 * in your Android application, you can dynamically generate and load 64 * executable code. This approach takes advantage of the fact that the 65 * host environment and target environment are both Android. 66 * <li><strong>For compile time code generation.</strong> You may use this 67 * library as a part of a compiler that targets Android. In this scenario 68 * the generated dex file must be installed on an Android device before it 69 * can be executed. 70 * </ul> 71 * 72 * <h3>Example: Fibonacci</h3> 73 * To illustrate how this API is used, we'll use DexMaker to generate a class 74 * equivalent to the following Java source: <pre> {@code 75 * 76 * package com.publicobject.fib; 77 * 78 * public class Fibonacci { 79 * public static int fib(int i) { 80 * if (i < 2) { 81 * return i; 82 * } 83 * return fib(i - 1) + fib(i - 2); 84 * } 85 * }}</pre> 86 * 87 * <p>We start by creating a {@link TypeId} to identify the generated {@code 88 * Fibonacci} class. DexMaker identifies types by their internal names like 89 * {@code Ljava/lang/Object;} rather than their Java identifiers like {@code 90 * java.lang.Object}. <pre> {@code 91 * 92 * TypeId<?> fibonacci = TypeId.get("Lcom/google/dexmaker/examples/Fibonacci;"); 93 * }</pre> 94 * 95 * <p>Next we declare the class. It allows us to specify the type's source file 96 * for stack traces, its modifiers, its superclass, and the interfaces it 97 * implements. In this case, {@code Fibonacci} is a public class that extends 98 * from {@code Object}: <pre> {@code 99 * 100 * String fileName = "Fibonacci.generated"; 101 * DexMaker dexMaker = new DexMaker(); 102 * dexMaker.declare(fibonacci, fileName, Modifier.PUBLIC, TypeId.OBJECT); 103 * }</pre> 104 * It is illegal to declare members of a class without also declaring the class 105 * itself. 106 * 107 * <p>To make it easier to go from our Java method to dex instructions, we'll 108 * manually translate it to pseudocode fit for an assembler. We need to replace 109 * control flow like {@code if()} blocks and {@code for()} loops with labels and 110 * branches. We'll also avoid performing multiple operations in one statement, 111 * using local variables to hold intermediate values as necessary: 112 * <pre> {@code 113 * 114 * int constant1 = 1; 115 * int constant2 = 2; 116 * if (i < constant2) goto baseCase; 117 * int a = i - constant1; 118 * int b = i - constant2; 119 * int c = fib(a); 120 * int d = fib(b); 121 * int result = c + d; 122 * return result; 123 * baseCase: 124 * return i; 125 * }</pre> 126 * 127 * <p>We look up the {@code MethodId} for the method on the declaring type. This 128 * takes the method's return type (possibly {@link TypeId#VOID}), its name and 129 * its parameters types. Next we declare the method, specifying its modifiers by 130 * bitwise ORing constants from {@link java.lang.reflect.Modifier}. The declare 131 * call returns a {@link Code} object, which we'll use to define the method's 132 * instructions. <pre> {@code 133 * 134 * MethodId<?, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT); 135 * Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC); 136 * }</pre> 137 * 138 * <p>One limitation of {@code DexMaker}'s API is that it requires all local 139 * variables to be created before any instructions are emitted. Use {@link 140 * Code#newLocal newLocal()} to create a new local variable. The method's 141 * parameters are exposed as locals using {@link Code#getParameter 142 * getParameter()}. For non-static methods the {@code this} pointer is exposed 143 * using {@link Code#getThis getThis()}. Here we declare all of the local 144 * variables that we'll need for our {@code fib()} method: <pre> {@code 145 * 146 * Local<Integer> i = code.getParameter(0, TypeId.INT); 147 * Local<Integer> constant1 = code.newLocal(TypeId.INT); 148 * Local<Integer> constant2 = code.newLocal(TypeId.INT); 149 * Local<Integer> a = code.newLocal(TypeId.INT); 150 * Local<Integer> b = code.newLocal(TypeId.INT); 151 * Local<Integer> c = code.newLocal(TypeId.INT); 152 * Local<Integer> d = code.newLocal(TypeId.INT); 153 * Local<Integer> result = code.newLocal(TypeId.INT); 154 * }</pre> 155 * 156 * <p>Notice that {@link Local} has a type parameter of {@code Integer}. This is 157 * useful for generating code that works with existing types like {@code String} 158 * and {@code Integer}, but it can be a hindrance when generating code that 159 * involves new types. For this reason you may prefer to use raw types only and 160 * add {@code @SuppressWarnings("unsafe")} on your calling code. This will yield 161 * the same result but you won't get IDE support if you make a type error. 162 * 163 * <p>We're ready to start defining our method's instructions. The {@link Code} 164 * class catalogs the available instructions and their use. <pre> {@code 165 * 166 * code.loadConstant(constant1, 1); 167 * code.loadConstant(constant2, 2); 168 * Label baseCase = new Label(); 169 * code.compare(Comparison.LT, baseCase, i, constant2); 170 * code.op(BinaryOp.SUBTRACT, a, i, constant1); 171 * code.op(BinaryOp.SUBTRACT, b, i, constant2); 172 * code.invokeStatic(fib, c, a); 173 * code.invokeStatic(fib, d, b); 174 * code.op(BinaryOp.ADD, result, c, d); 175 * code.returnValue(result); 176 * code.mark(baseCase); 177 * code.returnValue(i); 178 * }</pre> 179 * 180 * <p>We're done defining the dex file. We just need to write it to the 181 * filesystem or load it into the current process. For this example we'll load 182 * the generated code into the current process. This only works when the current 183 * process is running on Android. We use {@link #generateAndLoad 184 * generateAndLoad()} which takes the class loader that will be used as our 185 * generated code's parent class loader. It also requires a directory where 186 * temporary files can be written. <pre> {@code 187 * 188 * ClassLoader loader = dexMaker.generateAndLoad( 189 * FibonacciMaker.class.getClassLoader(), getDataDirectory()); 190 * }</pre> 191 * Finally we'll use reflection to lookup our generated class on its class 192 * loader and invoke its {@code fib()} method: <pre> {@code 193 * 194 * Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci"); 195 * Method fibMethod = fibonacciClass.getMethod("fib", int.class); 196 * System.out.println(fibMethod.invoke(null, 8)); 197 * }</pre> 198 */ 199 public final class DexMaker { 200 private final Map<TypeId<?>, TypeDeclaration> types = new LinkedHashMap<>(); 201 202 // Only warn about not being able to deal with blacklisted methods once. Often this is no 203 // problem and warning on every class load is too spammy. 204 private static boolean didWarnBlacklistedMethods; 205 private static boolean didWarnNonBaseDexClassLoader; 206 207 private ClassLoader sharedClassLoader; 208 private DexFile outputDex; 209 private boolean markAsTrusted; 210 211 /** 212 * Creates a new {@code DexMaker} instance, which can be used to create a 213 * single dex file. 214 */ DexMaker()215 public DexMaker() { 216 } 217 getTypeDeclaration(TypeId<?> type)218 TypeDeclaration getTypeDeclaration(TypeId<?> type) { 219 TypeDeclaration result = types.get(type); 220 if (result == null) { 221 result = new TypeDeclaration(type); 222 types.put(type, result); 223 } 224 return result; 225 } 226 227 /** 228 * Declares {@code type}. 229 * 230 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link 231 * Modifier#FINAL} and {@link Modifier#ABSTRACT}. 232 */ declare(TypeId<?> type, String sourceFile, int flags, TypeId<?> supertype, TypeId<?>... interfaces)233 public void declare(TypeId<?> type, String sourceFile, int flags, 234 TypeId<?> supertype, TypeId<?>... interfaces) { 235 TypeDeclaration declaration = getTypeDeclaration(type); 236 int supportedFlags = Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT 237 | AccessFlags.ACC_SYNTHETIC; 238 if ((flags & ~supportedFlags) != 0) { 239 throw new IllegalArgumentException("Unexpected flag: " 240 + Integer.toHexString(flags)); 241 } 242 if (declaration.declared) { 243 throw new IllegalStateException("already declared: " + type); 244 } 245 declaration.declared = true; 246 declaration.flags = flags; 247 declaration.supertype = supertype; 248 declaration.sourceFile = sourceFile; 249 declaration.interfaces = new TypeList(interfaces); 250 } 251 252 /** 253 * Declares a method or constructor. 254 * 255 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link 256 * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC}, 257 * {@link Modifier#FINAL} and {@link Modifier#SYNCHRONIZED}. 258 * <p><strong>Warning:</strong> the {@link Modifier#SYNCHRONIZED} flag 259 * is insufficient to generate a synchronized method. You must also use 260 * {@link Code#monitorEnter} and {@link Code#monitorExit} to acquire 261 * a monitor. 262 */ declare(MethodId<?, ?> method, int flags)263 public Code declare(MethodId<?, ?> method, int flags) { 264 TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType); 265 if (typeDeclaration.methods.containsKey(method)) { 266 throw new IllegalStateException("already declared: " + method); 267 } 268 269 int supportedFlags = Modifier.ABSTRACT | Modifier.NATIVE | Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED 270 | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED 271 | AccessFlags.ACC_SYNTHETIC | AccessFlags.ACC_BRIDGE; 272 if ((flags & ~supportedFlags) != 0) { 273 throw new IllegalArgumentException("Unexpected flag: " 274 + Integer.toHexString(flags)); 275 } 276 277 // replace the SYNCHRONIZED flag with the DECLARED_SYNCHRONIZED flag 278 if ((flags & Modifier.SYNCHRONIZED) != 0) { 279 flags = (flags & ~Modifier.SYNCHRONIZED) | AccessFlags.ACC_DECLARED_SYNCHRONIZED; 280 } 281 282 if (method.isConstructor() || method.isStaticInitializer()) { 283 flags |= ACC_CONSTRUCTOR; 284 } 285 286 MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags); 287 typeDeclaration.methods.put(method, methodDeclaration); 288 return methodDeclaration.code; 289 } 290 291 /** 292 * Declares a field. 293 * 294 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link 295 * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC}, 296 * {@link Modifier#FINAL}, {@link Modifier#VOLATILE}, and {@link 297 * Modifier#TRANSIENT}. 298 * @param staticValue a constant representing the initial value for the 299 * static field, possibly null. This must be null if this field is 300 * non-static. 301 */ declare(FieldId<?, ?> fieldId, int flags, Object staticValue)302 public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) { 303 TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType); 304 if (typeDeclaration.fields.containsKey(fieldId)) { 305 throw new IllegalStateException("already declared: " + fieldId); 306 } 307 308 int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED 309 | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT 310 | AccessFlags.ACC_SYNTHETIC; 311 if ((flags & ~supportedFlags) != 0) { 312 throw new IllegalArgumentException("Unexpected flag: " 313 + Integer.toHexString(flags)); 314 } 315 316 if ((flags & Modifier.STATIC) == 0 && staticValue != null) { 317 throw new IllegalArgumentException("staticValue is non-null, but field is not static"); 318 } 319 320 FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue); 321 typeDeclaration.fields.put(fieldId, fieldDeclaration); 322 } 323 324 /** 325 * Generates a dex file and returns its bytes. 326 */ generate()327 public byte[] generate() { 328 if (outputDex == null) { 329 DexOptions options = new DexOptions(); 330 options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; 331 outputDex = new DexFile(options); 332 } 333 334 for (TypeDeclaration typeDeclaration : types.values()) { 335 outputDex.add(typeDeclaration.toClassDefItem()); 336 } 337 338 try { 339 return outputDex.toDex(null, false); 340 } catch (IOException e) { 341 throw new RuntimeException(e); 342 } 343 } 344 345 // Generate a file name for the jar by taking a checksum of MethodIds and 346 // parent class types. generateFileName()347 private String generateFileName() { 348 int checksum = 1; 349 350 Set<TypeId<?>> typesKeySet = types.keySet(); 351 Iterator<TypeId<?>> it = typesKeySet.iterator(); 352 int[] checksums = new int[typesKeySet.size()]; 353 int i = 0; 354 355 while (it.hasNext()) { 356 TypeId<?> typeId = it.next(); 357 TypeDeclaration decl = getTypeDeclaration(typeId); 358 Set<MethodId> methodSet = decl.methods.keySet(); 359 if (decl.supertype != null) { 360 int sum = 31 * decl.supertype.hashCode() + decl.interfaces.hashCode(); 361 checksums[i++] = 31 * sum + methodSet.hashCode(); 362 } 363 } 364 Arrays.sort(checksums); 365 366 for (int sum : checksums) { 367 checksum *= 31; 368 checksum += sum; 369 } 370 371 return "Generated_" + checksum +".jar"; 372 } 373 374 /** 375 * Set shared class loader to use. 376 * 377 * <p>If a class wants to call package private methods of another class they need to share a 378 * class loader. One common case for this requirement is a mock class wanting to mock package 379 * private methods of the original class. 380 * 381 * <p>If the classLoader is not a subclass of {@code dalvik.system.BaseDexClassLoader} this 382 * option is ignored. 383 * 384 * @param classLoader the class loader the new class should be loaded by 385 */ setSharedClassLoader(ClassLoader classLoader)386 public void setSharedClassLoader(ClassLoader classLoader) { 387 this.sharedClassLoader = classLoader; 388 } 389 markAsTrusted()390 public void markAsTrusted() { 391 this.markAsTrusted = true; 392 } 393 generateClassLoader(File result, File dexCache, ClassLoader parent)394 private ClassLoader generateClassLoader(File result, File dexCache, ClassLoader parent) { 395 try { 396 boolean shareClassLoader = sharedClassLoader != null; 397 398 ClassLoader preferredClassLoader = null; 399 if (parent != null) { 400 preferredClassLoader = parent; 401 } else if (sharedClassLoader != null) { 402 preferredClassLoader = sharedClassLoader; 403 } 404 405 Class baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); 406 407 if (shareClassLoader) { 408 if (!baseDexClassLoaderClass.isAssignableFrom(preferredClassLoader.getClass())) { 409 if (!preferredClassLoader.getClass().getName().equals( 410 "java.lang.BootClassLoader")) { 411 if (!didWarnNonBaseDexClassLoader) { 412 System.err.println("Cannot share classloader as shared classloader '" 413 + preferredClassLoader + "' is not a subclass of '" 414 + baseDexClassLoaderClass 415 + "'"); 416 didWarnNonBaseDexClassLoader = true; 417 } 418 } 419 420 shareClassLoader = false; 421 } 422 } 423 424 // Try to load the class so that it can call hidden APIs. This is required for spying 425 // on system classes as real-methods of these classes might call blacklisted APIs 426 if (markAsTrusted) { 427 try { 428 if (shareClassLoader) { 429 preferredClassLoader.getClass().getMethod("addDexPath", String.class, 430 Boolean.TYPE).invoke(preferredClassLoader, result.getPath(), true); 431 return preferredClassLoader; 432 } else { 433 return (ClassLoader) baseDexClassLoaderClass 434 .getConstructor(String.class, File.class, String.class, 435 ClassLoader.class, Boolean.TYPE) 436 .newInstance(result.getPath(), dexCache.getAbsoluteFile(), null, 437 preferredClassLoader, true); 438 } 439 } catch (InvocationTargetException e) { 440 if (e.getCause() instanceof SecurityException) { 441 if (!didWarnBlacklistedMethods) { 442 System.err.println("Cannot allow to call blacklisted super methods. " 443 + "This might break spying on system classes." + e.getCause()); 444 didWarnBlacklistedMethods = true; 445 } 446 } else { 447 throw e; 448 } 449 } 450 } 451 452 if (shareClassLoader) { 453 preferredClassLoader.getClass().getMethod("addDexPath", String.class).invoke( 454 preferredClassLoader, result.getPath()); 455 return preferredClassLoader; 456 } else { 457 return (ClassLoader) Class.forName("dalvik.system.DexClassLoader") 458 .getConstructor(String.class, String.class, String.class, ClassLoader.class) 459 .newInstance(result.getPath(), dexCache.getAbsolutePath(), null, 460 preferredClassLoader); 461 } 462 } catch (ClassNotFoundException e) { 463 throw new UnsupportedOperationException("load() requires a Dalvik VM", e); 464 } catch (InvocationTargetException e) { 465 throw new RuntimeException(e.getCause()); 466 } catch (InstantiationException e) { 467 throw new AssertionError(); 468 } catch (NoSuchMethodException e) { 469 throw new AssertionError(); 470 } catch (IllegalAccessException e) { 471 throw new AssertionError(); 472 } 473 } 474 475 /** 476 * Generates a dex file and loads its types into the current process. 477 * 478 * <h3>Picking a dex cache directory</h3> 479 * The {@code dexCache} should be an application-private directory. If 480 * you pass a world-writable directory like {@code /sdcard} a malicious app 481 * could inject code into your process. Most applications should use this: 482 * <pre> {@code 483 * 484 * File dexCache = getApplicationContext().getDir("dx", Context.MODE_PRIVATE); 485 * }</pre> 486 * If the {@code dexCache} is null, this method will consult the {@code 487 * dexmaker.dexcache} system property. If that exists, it will be used for 488 * the dex cache. If it doesn't exist, this method will attempt to guess 489 * the application's private data directory as a last resort. If that fails, 490 * this method will fail with an unchecked exception. You can avoid the 491 * exception by either providing a non-null value or setting the system 492 * property. 493 * 494 * @param parent the parent ClassLoader to be used when loading our 495 * generated types (if set, overrides 496 * {@link #setSharedClassLoader(ClassLoader) shared class loader}. 497 * @param dexCache the destination directory where generated and optimized 498 * dex files will be written. If null, this class will try to guess the 499 * application's private data dir. 500 */ generateAndLoad(ClassLoader parent, File dexCache)501 public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException { 502 if (dexCache == null) { 503 String property = System.getProperty("dexmaker.dexcache"); 504 if (property != null) { 505 dexCache = new File(property); 506 } else { 507 dexCache = new AppDataDirGuesser().guess(); 508 if (dexCache == null) { 509 throw new IllegalArgumentException("dexcache == null (and no default could be" 510 + " found; consider setting the 'dexmaker.dexcache' system property)"); 511 } 512 } 513 } 514 515 File result = new File(dexCache, generateFileName()); 516 // Check that the file exists. If it does, return a DexClassLoader and skip all 517 // the dex bytecode generation. 518 if (result.exists()) { 519 if (!result.canWrite()) { 520 return generateClassLoader(result, dexCache, parent); 521 } else { 522 // Old writable files should be ignored and re-generated 523 result.delete(); 524 } 525 } 526 527 byte[] dex = generate(); 528 529 /* 530 * This implementation currently dumps the dex to the filesystem. It 531 * jars the emitted .dex for the benefit of Gingerbread and earlier 532 * devices, which can't load .dex files directly. 533 * 534 * TODO: load the dex from memory where supported. 535 */ 536 537 JarOutputStream jarOut = 538 new JarOutputStream(new BufferedOutputStream(new FileOutputStream(result))); 539 result.setReadOnly(); 540 try { 541 JarEntry entry = new JarEntry(DexFormat.DEX_IN_JAR_NAME); 542 entry.setSize(dex.length); 543 jarOut.putNextEntry(entry); 544 try { 545 jarOut.write(dex); 546 } finally { 547 jarOut.closeEntry(); 548 } 549 } finally { 550 jarOut.close(); 551 } 552 553 return generateClassLoader(result, dexCache, parent); 554 } 555 getDexFile()556 DexFile getDexFile() { 557 if (outputDex == null) { 558 DexOptions options = new DexOptions(); 559 options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; 560 outputDex = new DexFile(options); 561 } 562 return outputDex; 563 } 564 565 static class TypeDeclaration { 566 private final TypeId<?> type; 567 568 /** declared state */ 569 private boolean declared; 570 private int flags; 571 private TypeId<?> supertype; 572 private String sourceFile; 573 private TypeList interfaces; 574 private ClassDefItem classDefItem; 575 576 private final Map<FieldId, FieldDeclaration> fields = new LinkedHashMap<>(); 577 private final Map<MethodId, MethodDeclaration> methods = new LinkedHashMap<>(); 578 TypeDeclaration(TypeId<?> type)579 TypeDeclaration(TypeId<?> type) { 580 this.type = type; 581 } 582 toClassDefItem()583 ClassDefItem toClassDefItem() { 584 if (!declared) { 585 throw new IllegalStateException("Undeclared type " + type + " declares members: " 586 + fields.keySet() + " " + methods.keySet()); 587 } 588 589 DexOptions dexOptions = new DexOptions(); 590 dexOptions.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; 591 592 CstType thisType = type.constant; 593 594 if (classDefItem == null) { 595 classDefItem = new ClassDefItem(thisType, flags, supertype.constant, 596 interfaces.ropTypes, new CstString(sourceFile)); 597 598 for (MethodDeclaration method : methods.values()) { 599 EncodedMethod encoded = method.toEncodedMethod(dexOptions); 600 if (method.isDirect()) { 601 classDefItem.addDirectMethod(encoded); 602 } else { 603 classDefItem.addVirtualMethod(encoded); 604 } 605 } 606 for (FieldDeclaration field : fields.values()) { 607 EncodedField encoded = field.toEncodedField(); 608 if (field.isStatic()) { 609 classDefItem.addStaticField(encoded, Constants.getConstant(field.staticValue)); 610 } else { 611 classDefItem.addInstanceField(encoded); 612 } 613 } 614 } 615 616 return classDefItem; 617 } 618 } 619 620 static class FieldDeclaration { 621 final FieldId<?, ?> fieldId; 622 private final int accessFlags; 623 private final Object staticValue; 624 FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue)625 FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) { 626 if ((accessFlags & STATIC) == 0 && staticValue != null) { 627 throw new IllegalArgumentException("instance fields may not have a value"); 628 } 629 this.fieldId = fieldId; 630 this.accessFlags = accessFlags; 631 this.staticValue = staticValue; 632 } 633 toEncodedField()634 EncodedField toEncodedField() { 635 return new EncodedField(fieldId.constant, accessFlags); 636 } 637 isStatic()638 public boolean isStatic() { 639 return (accessFlags & STATIC) != 0; 640 } 641 } 642 643 static class MethodDeclaration { 644 final MethodId<?, ?> method; 645 private final int flags; 646 private final Code code; 647 MethodDeclaration(MethodId<?, ?> method, int flags)648 public MethodDeclaration(MethodId<?, ?> method, int flags) { 649 this.method = method; 650 this.flags = flags; 651 this.code = new Code(this); 652 } 653 isStatic()654 boolean isStatic() { 655 return (flags & STATIC) != 0; 656 } 657 isDirect()658 boolean isDirect() { 659 return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0; 660 } 661 toEncodedMethod(DexOptions dexOptions)662 EncodedMethod toEncodedMethod(DexOptions dexOptions) { 663 if((flags & ABSTRACT) != 0 || (flags & NATIVE) != 0){ 664 return new EncodedMethod(method.constant, flags, null, StdTypeList.EMPTY); 665 } 666 667 RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0); 668 LocalVariableInfo locals = null; 669 DalvCode dalvCode = RopTranslator.translate( 670 ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions); 671 return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY); 672 } 673 } 674 } 675