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