DexMaker.java revision 008290ab55ac24ef656d254e41a03ad2b1fba7d2
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 defines 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: <pre> {@code 157 * 158 * code.loadConstant(constant1, 1); 159 * code.loadConstant(constant2, 2); 160 * Label baseCase = new Label(); 161 * code.compare(Comparison.LT, baseCase, i, constant2); 162 * code.op(BinaryOp.SUBTRACT, a, i, constant1); 163 * code.op(BinaryOp.SUBTRACT, b, i, constant2); 164 * code.invokeStatic(fib, c, a); 165 * code.invokeStatic(fib, d, b); 166 * code.op(BinaryOp.ADD, result, c, d); 167 * code.returnValue(result); 168 * code.mark(baseCase); 169 * code.returnValue(i); 170 * }</pre> 171 * 172 * <p>We're done defining the dex file. We just need to write it to the 173 * filesystem or load it into the current process. For this example we'll load 174 * the generated code into the current process. This only works when the current 175 * process is running on Android. We use {@link #generateAndLoad} which takes 176 * the class loader that will be used as our generated code's parent class 177 * loader. It also requires a directory where temporary files can be written. 178 * <pre> {@code 179 * 180 * ClassLoader loader = dexMaker.generateAndLoad( 181 * Fibonacci.class.getClassLoader(), getDataDirectory()); 182 * }</pre> 183 * Finally we'll use reflection to lookup our generated class on its class 184 * loader and invoke its {@code fib()} method: <pre> {@code 185 * 186 * Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci"); 187 * Method fibMethod = fibonacciClass.getMethod("fib", int.class); 188 * System.out.println(fibMethod.invoke(null, 8)); 189 * }</pre> 190 */ 191public final class DexMaker { 192 private final Map<TypeId<?>, TypeDeclaration> types 193 = new LinkedHashMap<TypeId<?>, TypeDeclaration>(); 194 195 private TypeDeclaration getTypeDeclaration(TypeId<?> type) { 196 TypeDeclaration result = types.get(type); 197 if (result == null) { 198 result = new TypeDeclaration(type); 199 types.put(type, result); 200 } 201 return result; 202 } 203 204 /** 205 * Declares {@code type}. 206 * 207 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link 208 * Modifier#FINAL} and {@link Modifier#ABSTRACT}. 209 */ 210 public void declare(TypeId<?> type, String sourceFile, int flags, 211 TypeId<?> supertype, TypeId<?>... interfaces) { 212 TypeDeclaration declaration = getTypeDeclaration(type); 213 if (declaration.declared) { 214 throw new IllegalStateException("already declared: " + type); 215 } 216 declaration.declared = true; 217 declaration.flags = flags; 218 declaration.supertype = supertype; 219 declaration.sourceFile = sourceFile; 220 declaration.interfaces = new TypeList(interfaces); 221 } 222 223 /** 224 * Declares a constructor. The name of {@code method} must be "<init>", 225 * as it is on all instances returned by {@link TypeId#getConstructor}. 226 * 227 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link 228 * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC}, 229 * {@link Modifier#FINAL}, and {@link Modifier#VARARGS}. 230 */ 231 public Code declareConstructor(MethodId<?, ?> method, int flags) { 232 return declare(method, flags | ACC_CONSTRUCTOR); 233 } 234 235 /** 236 * Declares a method. The name of {@code method} must not be "<init>". 237 * 238 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link 239 * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC}, 240 * {@link Modifier#FINAL}, and {@link Modifier#VARARGS}. 241 */ 242 public Code declare(MethodId<?, ?> method, int flags) { 243 TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType); 244 if (typeDeclaration.methods.containsKey(method)) { 245 throw new IllegalStateException("already declared: " + method); 246 } 247 MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags); 248 typeDeclaration.methods.put(method, methodDeclaration); 249 return methodDeclaration.code; 250 } 251 252 /** 253 * Declares a field. 254 * 255 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link 256 * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC}, 257 * {@link Modifier#FINAL}, {@link Modifier#VOLATILE}, and {@link 258 * Modifier#TRANSIENT}. 259 */ 260 public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) { 261 TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType); 262 if (typeDeclaration.fields.containsKey(fieldId)) { 263 throw new IllegalStateException("already declared: " + fieldId); 264 } 265 FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue); 266 typeDeclaration.fields.put(fieldId, fieldDeclaration); 267 } 268 269 /** 270 * Generates a dex file and returns its bytes. 271 */ 272 public byte[] generate() { 273 DexOptions options = new DexOptions(); 274 options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES; 275 DexFile outputDex = new DexFile(options); 276 277 for (TypeDeclaration typeDeclaration : types.values()) { 278 outputDex.add(typeDeclaration.toClassDefItem()); 279 } 280 281 try { 282 return outputDex.toDex(null, false); 283 } catch (IOException e) { 284 throw new RuntimeException(e); 285 } 286 } 287 288 /** 289 * Generates a dex file and loads its types into the current process. 290 * 291 * <p>All parameters are optional; you may pass {@code null} and suitable 292 * defaults will be used. 293 * 294 * <p>If you opt to provide your own {@code dexDir}, take care to ensure 295 * that it is not world-writable, otherwise a malicious app may be able 296 * to inject code into your process. A suitable parameter is: 297 * {@code getApplicationContext().getDir("dx", Context.MODE_PRIVATE); } 298 * 299 * @param parent the parent ClassLoader to be used when loading 300 * our generated types 301 * @param dexDir the destination directory where generated and 302 * optimized dex files will be written. 303 */ 304 public ClassLoader generateAndLoad(ClassLoader parent, File dexDir) throws IOException { 305 byte[] dex = generate(); 306 307 /* 308 * This implementation currently dumps the dex to the filesystem. It 309 * jars the emitted .dex for the benefit of Gingerbread and earlier 310 * devices, which can't load .dex files directly. 311 * 312 * TODO: load the dex from memory where supported. 313 */ 314 File result = File.createTempFile("Generated", ".jar", dexDir); 315 result.deleteOnExit(); 316 JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result)); 317 jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME)); 318 jarOut.write(dex); 319 jarOut.closeEntry(); 320 jarOut.close(); 321 try { 322 return (ClassLoader) Class.forName("dalvik.system.DexClassLoader") 323 .getConstructor(String.class, String.class, String.class, ClassLoader.class) 324 .newInstance(result.getPath(), dexDir.getAbsolutePath(), null, parent); 325 } catch (ClassNotFoundException e) { 326 throw new UnsupportedOperationException("load() requires a Dalvik VM", e); 327 } catch (InvocationTargetException e) { 328 throw new RuntimeException(e.getCause()); 329 } catch (InstantiationException e) { 330 throw new AssertionError(); 331 } catch (NoSuchMethodException e) { 332 throw new AssertionError(); 333 } catch (IllegalAccessException e) { 334 throw new AssertionError(); 335 } 336 } 337 338 private static class TypeDeclaration { 339 private final TypeId<?> type; 340 341 /** declared state */ 342 private boolean declared; 343 private int flags; 344 private TypeId<?> supertype; 345 private String sourceFile; 346 private TypeList interfaces; 347 348 private final Map<FieldId, FieldDeclaration> fields 349 = new LinkedHashMap<FieldId, FieldDeclaration>(); 350 private final Map<MethodId, MethodDeclaration> methods 351 = new LinkedHashMap<MethodId, MethodDeclaration>(); 352 353 TypeDeclaration(TypeId<?> type) { 354 this.type = type; 355 } 356 357 ClassDefItem toClassDefItem() { 358 if (!declared) { 359 throw new IllegalStateException("Undeclared type " + type + " declares members: " 360 + fields.keySet() + " " + methods.keySet()); 361 } 362 363 DexOptions dexOptions = new DexOptions(); 364 dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES; 365 366 CstType thisType = type.constant; 367 368 ClassDefItem out = new ClassDefItem(thisType, flags, supertype.constant, 369 interfaces.ropTypes, new CstString(sourceFile)); 370 371 for (MethodDeclaration method : methods.values()) { 372 EncodedMethod encoded = method.toEncodedMethod(dexOptions); 373 if (method.isDirect()) { 374 out.addDirectMethod(encoded); 375 } else { 376 out.addVirtualMethod(encoded); 377 } 378 } 379 for (FieldDeclaration field : fields.values()) { 380 EncodedField encoded = field.toEncodedField(); 381 if (field.isStatic()) { 382 out.addStaticField(encoded, Constants.getConstant(field.staticValue)); 383 } else { 384 out.addInstanceField(encoded); 385 } 386 } 387 388 return out; 389 } 390 } 391 392 static class FieldDeclaration { 393 final FieldId<?, ?> fieldId; 394 private final int accessFlags; 395 private final Object staticValue; 396 397 FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) { 398 if ((accessFlags & STATIC) == 0 && staticValue != null) { 399 throw new IllegalArgumentException("instance fields may not have a value"); 400 } 401 this.fieldId = fieldId; 402 this.accessFlags = accessFlags; 403 this.staticValue = staticValue; 404 } 405 406 EncodedField toEncodedField() { 407 return new EncodedField(fieldId.constant, accessFlags); 408 } 409 410 public boolean isStatic() { 411 return (accessFlags & STATIC) != 0; 412 } 413 } 414 415 static class MethodDeclaration { 416 final MethodId<?, ?> method; 417 private final int flags; 418 private final Code code; 419 420 public MethodDeclaration(MethodId<?, ?> method, int flags) { 421 this.method = method; 422 this.flags = flags; 423 this.code = new Code(this); 424 } 425 426 boolean isStatic() { 427 return (flags & STATIC) != 0; 428 } 429 430 boolean isDirect() { 431 return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0; 432 } 433 434 EncodedMethod toEncodedMethod(DexOptions dexOptions) { 435 RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0); 436 LocalVariableInfo locals = null; 437 DalvCode dalvCode = RopTranslator.translate( 438 ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions); 439 return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY); 440 } 441 } 442} 443