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