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