DexMaker.java revision ff314e1f06b974be78de3356a71072dbf0a450cd
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 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 lookup 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. Next we declare the method, specifying its modifiers by ORing 123 * constants from {@link java.lang.reflect.Modifier}. The declare call returns a 124 * {@link Code} object, which we'll use to define the method's instructions. 125 * <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} to create a new local variable. The method's parameters are 134 * exposed as locals using {@link Code#getParameter}. For non-static methods the 135 * 'this' pointer is exposed using {@link Code#getThis}. Here we declare all of 136 * the local variables that we'll need for our {@code fib()} method: 137 * <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. This is useful for 150 * generating code that works with existing types like {@code String} and {@code 151 * Integer}, but it can be a hindrance when generating code that involves new 152 * types. For this reason you may prefer to use raw types only and add 153 * {@code @SuppressWarnings("unsafe")} on your calling code. This will yield the 154 * same result but you won't get a compiler warning 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 = code.newLabel(); 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 instructions! We just need to write the dex to the 173 * file system 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 // TODO: describe the legal flags without referring to a non-public API AccessFlags 205 206 /** 207 * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#CLASS_FLAGS}. 208 */ 209 public void declare(TypeId<?> type, String sourceFile, int flags, 210 TypeId<?> supertype, TypeId<?>... interfaces) { 211 TypeDeclaration declaration = getTypeDeclaration(type); 212 if (declaration.declared) { 213 throw new IllegalStateException("already declared: " + type); 214 } 215 declaration.declared = true; 216 declaration.flags = flags; 217 declaration.supertype = supertype; 218 declaration.sourceFile = sourceFile; 219 declaration.interfaces = new TypeList(interfaces); 220 } 221 222 /** 223 * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#METHOD_FLAGS}. 224 */ 225 public Code declareConstructor(MethodId<?, ?> method, int flags) { 226 return declare(method, flags | ACC_CONSTRUCTOR); 227 } 228 229 /** 230 * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#METHOD_FLAGS}. 231 */ 232 public Code declare(MethodId<?, ?> method, int flags) { 233 TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType); 234 if (typeDeclaration.methods.containsKey(method)) { 235 throw new IllegalStateException("already declared: " + method); 236 } 237 MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags); 238 typeDeclaration.methods.put(method, methodDeclaration); 239 return methodDeclaration.code; 240 } 241 242 /** 243 * @param flags any flags masked by {@link AccessFlags#FIELD_FLAGS}. 244 */ 245 public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) { 246 TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType); 247 if (typeDeclaration.fields.containsKey(fieldId)) { 248 throw new IllegalStateException("already declared: " + fieldId); 249 } 250 FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue); 251 typeDeclaration.fields.put(fieldId, fieldDeclaration); 252 } 253 254 /** 255 * Returns a .dex formatted file. 256 */ 257 public byte[] generate() { 258 DexOptions options = new DexOptions(); 259 options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES; 260 DexFile outputDex = new DexFile(options); 261 262 for (TypeDeclaration typeDeclaration : types.values()) { 263 outputDex.add(typeDeclaration.toClassDefItem()); 264 } 265 266 try { 267 return outputDex.toDex(null, false); 268 } catch (IOException e) { 269 throw new RuntimeException(e); 270 } 271 } 272 273 /** 274 * Loads the generated types into the current process. 275 * 276 * <p>All parameters are optional, you may pass {@code null} and suitable 277 * defaults will be used. 278 * 279 * <p>If you opt to provide your own output directories, take care to 280 * ensure that they are not world-readable, otherwise a malicious app will 281 * be able to inject code to run. A suitable parameter for these output 282 * directories would be something like this: 283 * {@code getApplicationContext().getDir("dx", Context.MODE_PRIVATE); } 284 * 285 * @param parent the parent ClassLoader to be used when loading 286 * our generated types 287 * @param dexDir the destination directory where generated and 288 * optimized dex files will be written. 289 */ 290 public ClassLoader generateAndLoad(ClassLoader parent, File dexDir) throws IOException { 291 byte[] dex = generate(); 292 293 /* 294 * This implementation currently dumps the dex to the filesystem. It 295 * jars the emitted .dex for the benefit of Gingerbread and earlier 296 * devices, which can't load .dex files directly. 297 * 298 * TODO: load the dex from memory where supported. 299 */ 300 File result = File.createTempFile("Generated", ".jar", dexDir); 301 result.deleteOnExit(); 302 JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result)); 303 jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME)); 304 jarOut.write(dex); 305 jarOut.closeEntry(); 306 jarOut.close(); 307 try { 308 return (ClassLoader) Class.forName("dalvik.system.DexClassLoader") 309 .getConstructor(String.class, String.class, String.class, ClassLoader.class) 310 .newInstance(result.getPath(), dexDir.getAbsolutePath(), null, parent); 311 } catch (ClassNotFoundException e) { 312 throw new UnsupportedOperationException("load() requires a Dalvik VM", e); 313 } catch (InvocationTargetException e) { 314 throw new RuntimeException(e.getCause()); 315 } catch (InstantiationException e) { 316 throw new AssertionError(); 317 } catch (NoSuchMethodException e) { 318 throw new AssertionError(); 319 } catch (IllegalAccessException e) { 320 throw new AssertionError(); 321 } 322 } 323 324 private static class TypeDeclaration { 325 private final TypeId<?> type; 326 327 /** declared state */ 328 private boolean declared; 329 private int flags; 330 private TypeId<?> supertype; 331 private String sourceFile; 332 private TypeList interfaces; 333 334 private final Map<FieldId, FieldDeclaration> fields 335 = new LinkedHashMap<FieldId, FieldDeclaration>(); 336 private final Map<MethodId, MethodDeclaration> methods 337 = new LinkedHashMap<MethodId, MethodDeclaration>(); 338 339 TypeDeclaration(TypeId<?> type) { 340 this.type = type; 341 } 342 343 ClassDefItem toClassDefItem() { 344 if (!declared) { 345 throw new IllegalStateException("Undeclared type " + type + " declares members: " 346 + fields.keySet() + " " + methods.keySet()); 347 } 348 349 DexOptions dexOptions = new DexOptions(); 350 dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES; 351 352 CstType thisType = type.constant; 353 354 ClassDefItem out = new ClassDefItem(thisType, flags, supertype.constant, 355 interfaces.ropTypes, new CstString(sourceFile)); 356 357 for (MethodDeclaration method : methods.values()) { 358 EncodedMethod encoded = method.toEncodedMethod(dexOptions); 359 if (method.isDirect()) { 360 out.addDirectMethod(encoded); 361 } else { 362 out.addVirtualMethod(encoded); 363 } 364 } 365 for (FieldDeclaration field : fields.values()) { 366 EncodedField encoded = field.toEncodedField(); 367 if (field.isStatic()) { 368 out.addStaticField(encoded, Constants.getConstant(field.staticValue)); 369 } else { 370 out.addInstanceField(encoded); 371 } 372 } 373 374 return out; 375 } 376 } 377 378 static class FieldDeclaration { 379 final FieldId<?, ?> fieldId; 380 private final int accessFlags; 381 private final Object staticValue; 382 383 FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) { 384 if ((accessFlags & STATIC) == 0 && staticValue != null) { 385 throw new IllegalArgumentException("instance fields may not have a value"); 386 } 387 this.fieldId = fieldId; 388 this.accessFlags = accessFlags; 389 this.staticValue = staticValue; 390 } 391 392 EncodedField toEncodedField() { 393 return new EncodedField(fieldId.constant, accessFlags); 394 } 395 396 public boolean isStatic() { 397 return (accessFlags & STATIC) != 0; 398 } 399 } 400 401 static class MethodDeclaration { 402 final MethodId<?, ?> method; 403 private final int flags; 404 private final Code code; 405 406 public MethodDeclaration(MethodId<?, ?> method, int flags) { 407 this.method = method; 408 this.flags = flags; 409 this.code = new Code(this); 410 } 411 412 boolean isStatic() { 413 return (flags & STATIC) != 0; 414 } 415 416 boolean isDirect() { 417 return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0; 418 } 419 420 EncodedMethod toEncodedMethod(DexOptions dexOptions) { 421 RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0); 422 LocalVariableInfo locals = null; 423 DalvCode dalvCode = RopTranslator.translate( 424 ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions); 425 return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY); 426 } 427 } 428} 429