Home | History | Annotate | Download | only in dexmaker
      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 
     17 package com.google.dexmaker;
     18 
     19 import com.android.dx.dex.DexFormat;
     20 import com.android.dx.dex.DexOptions;
     21 import com.android.dx.dex.code.DalvCode;
     22 import com.android.dx.dex.code.PositionList;
     23 import com.android.dx.dex.code.RopTranslator;
     24 import com.android.dx.dex.file.ClassDefItem;
     25 import com.android.dx.dex.file.DexFile;
     26 import com.android.dx.dex.file.EncodedField;
     27 import com.android.dx.dex.file.EncodedMethod;
     28 import com.android.dx.rop.code.AccessFlags;
     29 import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR;
     30 import com.android.dx.rop.code.LocalVariableInfo;
     31 import com.android.dx.rop.code.RopMethod;
     32 import com.android.dx.rop.cst.CstString;
     33 import com.android.dx.rop.cst.CstType;
     34 import com.android.dx.rop.type.StdTypeList;
     35 import java.io.File;
     36 import java.io.FileOutputStream;
     37 import java.io.IOException;
     38 import java.lang.reflect.InvocationTargetException;
     39 import java.lang.reflect.Modifier;
     40 import static java.lang.reflect.Modifier.PRIVATE;
     41 import static java.lang.reflect.Modifier.STATIC;
     42 import java.util.LinkedHashMap;
     43 import java.util.Map;
     44 import java.util.jar.JarEntry;
     45 import 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  */
    193 public 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