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