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