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