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.android.dx;
18
19import com.android.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 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 com.android.dx.stock.ProxyBuilder;
35import java.io.File;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.lang.reflect.InvocationTargetException;
39import java.lang.reflect.Modifier;
40import dalvik.system.DexClassLoader;
41
42import java.util.Arrays;
43import java.util.Iterator;
44import java.util.LinkedHashMap;
45import java.util.Map;
46import java.util.Set;
47import java.util.jar.JarEntry;
48import java.util.jar.JarOutputStream;
49
50import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR;
51import static java.lang.reflect.Modifier.PRIVATE;
52import static java.lang.reflect.Modifier.STATIC;
53
54/**
55 * Generates a <strong>D</strong>alvik <strong>EX</strong>ecutable (dex)
56 * file for execution on Android. Dex files define classes and interfaces,
57 * including their member methods and fields, executable code, and debugging
58 * information. They also define annotations, though this API currently has no
59 * facility to create a dex file that contains annotations.
60 *
61 * <p>This library is intended to satisfy two use cases:
62 * <ul>
63 *   <li><strong>For runtime code generation.</strong> By embedding this library
64 *       in your Android application, you can dynamically generate and load
65 *       executable code. This approach takes advantage of the fact that the
66 *       host environment and target environment are both Android.
67 *   <li><strong>For compile time code generation.</strong> You may use this
68 *       library as a part of a compiler that targets Android. In this scenario
69 *       the generated dex file must be installed on an Android device before it
70 *       can be executed.
71 * </ul>
72 *
73 * <h3>Example: Fibonacci</h3>
74 * To illustrate how this API is used, we'll use DexMaker to generate a class
75 * equivalent to the following Java source: <pre> {@code
76 *
77 * package com.publicobject.fib;
78 *
79 * public class Fibonacci {
80 *   public static int fib(int i) {
81 *     if (i < 2) {
82 *       return i;
83 *     }
84 *     return fib(i - 1) + fib(i - 2);
85 *   }
86 * }}</pre>
87 *
88 * <p>We start by creating a {@link TypeId} to identify the generated {@code
89 * Fibonacci} class. DexMaker identifies types by their internal names like
90 * {@code Ljava/lang/Object;} rather than their Java identifiers like {@code
91 * java.lang.Object}. <pre>   {@code
92 *
93 *   TypeId<?> fibonacci = TypeId.get("Lcom/google/dexmaker/examples/Fibonacci;");
94 * }</pre>
95 *
96 * <p>Next we declare the class. It allows us to specify the type's source file
97 * for stack traces, its modifiers, its superclass, and the interfaces it
98 * implements. In this case, {@code Fibonacci} is a public class that extends
99 * from {@code Object}: <pre>   {@code
100 *
101 *   String fileName = "Fibonacci.generated";
102 *   DexMaker dexMaker = new DexMaker();
103 *   dexMaker.declare(fibonacci, fileName, Modifier.PUBLIC, TypeId.OBJECT);
104 * }</pre>
105 * It is illegal to declare members of a class without also declaring the class
106 * itself.
107 *
108 * <p>To make it easier to go from our Java method to dex instructions, we'll
109 * manually translate it to pseudocode fit for an assembler. We need to replace
110 * control flow like {@code if()} blocks and {@code for()} loops with labels and
111 * branches. We'll also avoid performing multiple operations in one statement,
112 * using local variables to hold intermediate values as necessary:
113 * <pre>   {@code
114 *
115 *   int constant1 = 1;
116 *   int constant2 = 2;
117 *   if (i < constant2) goto baseCase;
118 *   int a = i - constant1;
119 *   int b = i - constant2;
120 *   int c = fib(a);
121 *   int d = fib(b);
122 *   int result = c + d;
123 *   return result;
124 * baseCase:
125 *   return i;
126 * }</pre>
127 *
128 * <p>We look up the {@code MethodId} for the method on the declaring type. This
129 * takes the method's return type (possibly {@link TypeId#VOID}), its name and
130 * its parameters types. Next we declare the method, specifying its modifiers by
131 * bitwise ORing constants from {@link java.lang.reflect.Modifier}. The declare
132 * call returns a {@link Code} object, which we'll use to define the method's
133 * instructions. <pre>   {@code
134 *
135 *   MethodId<?, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT);
136 *   Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC);
137 * }</pre>
138 *
139 * <p>One limitation of {@code DexMaker}'s API is that it requires all local
140 * variables to be created before any instructions are emitted. Use {@link
141 * Code#newLocal newLocal()} to create a new local variable. The method's
142 * parameters are exposed as locals using {@link Code#getParameter
143 * getParameter()}. For non-static methods the {@code this} pointer is exposed
144 * using {@link Code#getThis getThis()}. Here we declare all of the local
145 * variables that we'll need for our {@code fib()} method: <pre>   {@code
146 *
147 *   Local<Integer> i = code.getParameter(0, TypeId.INT);
148 *   Local<Integer> constant1 = code.newLocal(TypeId.INT);
149 *   Local<Integer> constant2 = code.newLocal(TypeId.INT);
150 *   Local<Integer> a = code.newLocal(TypeId.INT);
151 *   Local<Integer> b = code.newLocal(TypeId.INT);
152 *   Local<Integer> c = code.newLocal(TypeId.INT);
153 *   Local<Integer> d = code.newLocal(TypeId.INT);
154 *   Local<Integer> result = code.newLocal(TypeId.INT);
155 * }</pre>
156 *
157 * <p>Notice that {@link Local} has a type parameter of {@code Integer}. This is
158 * useful for generating code that works with existing types like {@code String}
159 * and {@code Integer}, but it can be a hindrance when generating code that
160 * involves new types. For this reason you may prefer to use raw types only and
161 * add {@code @SuppressWarnings("unsafe")} on your calling code. This will yield
162 * the same result but you won't get IDE support if you make a type error.
163 *
164 * <p>We're ready to start defining our method's instructions. The {@link Code}
165 * class catalogs the available instructions and their use. <pre>   {@code
166 *
167 *   code.loadConstant(constant1, 1);
168 *   code.loadConstant(constant2, 2);
169 *   Label baseCase = new Label();
170 *   code.compare(Comparison.LT, baseCase, i, constant2);
171 *   code.op(BinaryOp.SUBTRACT, a, i, constant1);
172 *   code.op(BinaryOp.SUBTRACT, b, i, constant2);
173 *   code.invokeStatic(fib, c, a);
174 *   code.invokeStatic(fib, d, b);
175 *   code.op(BinaryOp.ADD, result, c, d);
176 *   code.returnValue(result);
177 *   code.mark(baseCase);
178 *   code.returnValue(i);
179 * }</pre>
180 *
181 * <p>We're done defining the dex file. We just need to write it to the
182 * filesystem or load it into the current process. For this example we'll load
183 * the generated code into the current process. This only works when the current
184 * process is running on Android. We use {@link #generateAndLoad
185 * generateAndLoad()} which takes the class loader that will be used as our
186 * generated code's parent class loader. It also requires a directory where
187 * temporary files can be written. <pre>   {@code
188 *
189 *   ClassLoader loader = dexMaker.generateAndLoad(
190 *       FibonacciMaker.class.getClassLoader(), getDataDirectory());
191 * }</pre>
192 * Finally we'll use reflection to lookup our generated class on its class
193 * loader and invoke its {@code fib()} method: <pre>   {@code
194 *
195 *   Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci");
196 *   Method fibMethod = fibonacciClass.getMethod("fib", int.class);
197 *   System.out.println(fibMethod.invoke(null, 8));
198 * }</pre>
199 */
200public final class DexMaker {
201    private final Map<TypeId<?>, TypeDeclaration> types = new LinkedHashMap<>();
202
203    /**
204     * Creates a new {@code DexMaker} instance, which can be used to create a
205     * single dex file.
206     */
207    public DexMaker() {
208    }
209
210    private TypeDeclaration getTypeDeclaration(TypeId<?> type) {
211        TypeDeclaration result = types.get(type);
212        if (result == null) {
213            result = new TypeDeclaration(type);
214            types.put(type, result);
215        }
216        return result;
217    }
218
219    /**
220     * Declares {@code type}.
221     *
222     * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
223     *     Modifier#FINAL} and {@link Modifier#ABSTRACT}.
224     */
225    public void declare(TypeId<?> type, String sourceFile, int flags,
226            TypeId<?> supertype, TypeId<?>... interfaces) {
227        TypeDeclaration declaration = getTypeDeclaration(type);
228        int supportedFlags = Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT;
229        if ((flags & ~supportedFlags) != 0) {
230            throw new IllegalArgumentException("Unexpected flag: "
231                    + Integer.toHexString(flags));
232        }
233        if (declaration.declared) {
234            throw new IllegalStateException("already declared: " + type);
235        }
236        declaration.declared = true;
237        declaration.flags = flags;
238        declaration.supertype = supertype;
239        declaration.sourceFile = sourceFile;
240        declaration.interfaces = new TypeList(interfaces);
241    }
242
243    /**
244     * Declares a method or constructor.
245     *
246     * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
247     *     Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
248     *     {@link Modifier#FINAL} and {@link Modifier#SYNCHRONIZED}.
249     *     <p><strong>Warning:</strong> the {@link Modifier#SYNCHRONIZED} flag
250     *     is insufficient to generate a synchronized method. You must also use
251     *     {@link Code#monitorEnter} and {@link Code#monitorExit} to acquire
252     *     a monitor.
253     */
254    public Code declare(MethodId<?, ?> method, int flags) {
255        TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType);
256        if (typeDeclaration.methods.containsKey(method)) {
257            throw new IllegalStateException("already declared: " + method);
258        }
259
260        int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
261                | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED;
262        if ((flags & ~supportedFlags) != 0) {
263            throw new IllegalArgumentException("Unexpected flag: "
264                    + Integer.toHexString(flags));
265        }
266
267        // replace the SYNCHRONIZED flag with the DECLARED_SYNCHRONIZED flag
268        if ((flags & Modifier.SYNCHRONIZED) != 0) {
269            flags = (flags & ~Modifier.SYNCHRONIZED) | AccessFlags.ACC_DECLARED_SYNCHRONIZED;
270        }
271
272        if (method.isConstructor()) {
273            flags |= ACC_CONSTRUCTOR;
274        }
275
276        MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags);
277        typeDeclaration.methods.put(method, methodDeclaration);
278        return methodDeclaration.code;
279    }
280
281    /**
282     * Declares a field.
283     *
284     * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
285     *     Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
286     *     {@link Modifier#FINAL}, {@link Modifier#VOLATILE}, and {@link
287     *     Modifier#TRANSIENT}.
288     * @param staticValue a constant representing the initial value for the
289     *     static field, possibly null. This must be null if this field is
290     *     non-static.
291     */
292    public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) {
293        TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType);
294        if (typeDeclaration.fields.containsKey(fieldId)) {
295            throw new IllegalStateException("already declared: " + fieldId);
296        }
297
298        int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
299                | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT;
300        if ((flags & ~supportedFlags) != 0) {
301            throw new IllegalArgumentException("Unexpected flag: "
302                    + Integer.toHexString(flags));
303        }
304
305        if ((flags & Modifier.STATIC) == 0 && staticValue != null) {
306            throw new IllegalArgumentException("staticValue is non-null, but field is not static");
307        }
308
309        FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue);
310        typeDeclaration.fields.put(fieldId, fieldDeclaration);
311    }
312
313    /**
314     * Generates a dex file and returns its bytes.
315     */
316    public byte[] generate() {
317        DexOptions options = new DexOptions();
318        options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
319        DexFile outputDex = new DexFile(options);
320
321        for (TypeDeclaration typeDeclaration : types.values()) {
322            outputDex.add(typeDeclaration.toClassDefItem());
323        }
324
325        try {
326            return outputDex.toDex(null, false);
327        } catch (IOException e) {
328            throw new RuntimeException(e);
329        }
330    }
331
332    // Generate a file name for the jar by taking a checksum of MethodIds and
333    // parent class types.
334    private String generateFileName() {
335        int checksum = 1;
336
337        Set<TypeId<?>> typesKeySet = types.keySet();
338        Iterator<TypeId<?>> it = typesKeySet.iterator();
339        int[] checksums = new int[typesKeySet.size()];
340        int i = 0;
341
342        while (it.hasNext()) {
343            TypeId<?> typeId = it.next();
344            TypeDeclaration decl = getTypeDeclaration(typeId);
345            Set<MethodId> methodSet = decl.methods.keySet();
346            if (decl.supertype != null) {
347                checksums[i++] = 31 * decl.supertype.hashCode() + methodSet.hashCode();
348            }
349        }
350        Arrays.sort(checksums);
351
352        for (int sum : checksums) {
353            checksum *= 31;
354            checksum += sum;
355        }
356
357        return "Generated_" + checksum +".jar";
358    }
359
360    private ClassLoader generateClassLoader(ClassLoader classLoader, File result, File dexCache,
361            ClassLoader parent) {
362        try {
363            boolean shareClassLoader = Boolean.parseBoolean(System.getProperty(
364                        "dexmaker.share_classloader", "false"));
365            if (shareClassLoader) {
366                ClassLoader loader = parent != null ? parent : classLoader;
367                loader.getClass().getMethod("addDexPath", String.class).invoke(loader,
368                         result.getPath());
369                return loader;
370            } else {
371                return (ClassLoader) Class.forName("dalvik.system.DexClassLoader")
372                        .getConstructor(String.class, String.class, String.class, ClassLoader.class)
373                        .newInstance(result.getPath(), dexCache.getAbsolutePath(), null, parent);
374            }
375        } catch (ClassNotFoundException e) {
376            throw new UnsupportedOperationException("load() requires a Dalvik VM", e);
377        } catch (InvocationTargetException e) {
378            throw new RuntimeException(e.getCause());
379        } catch (InstantiationException e) {
380            throw new AssertionError();
381        } catch (NoSuchMethodException e) {
382            throw new AssertionError();
383        } catch (IllegalAccessException e) {
384            throw new AssertionError();
385        }
386    }
387
388    /**
389     * Generates a dex file and loads its types into the current process.
390     *
391     * <h3>Picking a dex cache directory</h3>
392     * The {@code dexCache} should be an application-private directory. If
393     * you pass a world-writable directory like {@code /sdcard} a malicious app
394     * could inject code into your process. Most applications should use this:
395     * <pre>   {@code
396     *
397     *     File dexCache = getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
398     * }</pre>
399     * If the {@code dexCache} is null, this method will consult the {@code
400     * dexmaker.dexcache} system property. If that exists, it will be used for
401     * the dex cache. If it doesn't exist, this method will attempt to guess
402     * the application's private data directory as a last resort. If that fails,
403     * this method will fail with an unchecked exception. You can avoid the
404     * exception by either providing a non-null value or setting the system
405     * property.
406     *
407     * @param parent the parent ClassLoader to be used when loading our
408     *     generated types
409     * @param dexCache the destination directory where generated and optimized
410     *     dex files will be written. If null, this class will try to guess the
411     *     application's private data dir.
412     */
413    public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException {
414        return generateAndLoad(parent, parent, dexCache);
415    }
416
417    public ClassLoader generateAndLoad(ClassLoader classLoader, ClassLoader parent, File dexCache)
418            throws IOException {
419        if (dexCache == null) {
420            String property = System.getProperty("dexmaker.dexcache");
421            if (property != null) {
422                dexCache = new File(property);
423            } else {
424                dexCache = new AppDataDirGuesser().guess();
425                if (dexCache == null) {
426                    throw new IllegalArgumentException("dexcache == null (and no default could be"
427                            + " found; consider setting the 'dexmaker.dexcache' system property)");
428                }
429            }
430        }
431
432        File result = new File(dexCache, generateFileName());
433        // Check that the file exists. If it does, return a DexClassLoader and skip all
434        // the dex bytecode generation.
435        if (result.exists()) {
436            return generateClassLoader(classLoader, result, dexCache, parent);
437        }
438
439        byte[] dex = generate();
440
441        /*
442         * This implementation currently dumps the dex to the filesystem. It
443         * jars the emitted .dex for the benefit of Gingerbread and earlier
444         * devices, which can't load .dex files directly.
445         *
446         * TODO: load the dex from memory where supported.
447         */
448        result.createNewFile();
449        JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
450        JarEntry entry = new JarEntry(DexFormat.DEX_IN_JAR_NAME);
451        entry.setSize(dex.length);
452        jarOut.putNextEntry(entry);
453        jarOut.write(dex);
454        jarOut.closeEntry();
455        jarOut.close();
456        return generateClassLoader(classLoader, result, dexCache, parent);
457    }
458
459    private static class TypeDeclaration {
460        private final TypeId<?> type;
461
462        /** declared state */
463        private boolean declared;
464        private int flags;
465        private TypeId<?> supertype;
466        private String sourceFile;
467        private TypeList interfaces;
468
469        private final Map<FieldId, FieldDeclaration> fields = new LinkedHashMap<>();
470        private final Map<MethodId, MethodDeclaration> methods = new LinkedHashMap<>();
471
472        TypeDeclaration(TypeId<?> type) {
473            this.type = type;
474        }
475
476        ClassDefItem toClassDefItem() {
477            if (!declared) {
478                throw new IllegalStateException("Undeclared type " + type + " declares members: "
479                        + fields.keySet() + " " + methods.keySet());
480            }
481
482            DexOptions dexOptions = new DexOptions();
483            dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
484
485            CstType thisType = type.constant;
486
487            ClassDefItem out = new ClassDefItem(thisType, flags, supertype.constant,
488                    interfaces.ropTypes, new CstString(sourceFile));
489
490            for (MethodDeclaration method : methods.values()) {
491                EncodedMethod encoded = method.toEncodedMethod(dexOptions);
492                if (method.isDirect()) {
493                    out.addDirectMethod(encoded);
494                } else {
495                    out.addVirtualMethod(encoded);
496                }
497            }
498            for (FieldDeclaration field : fields.values()) {
499                EncodedField encoded = field.toEncodedField();
500                if (field.isStatic()) {
501                    out.addStaticField(encoded, Constants.getConstant(field.staticValue));
502                } else {
503                    out.addInstanceField(encoded);
504                }
505            }
506
507            return out;
508        }
509    }
510
511    static class FieldDeclaration {
512        final FieldId<?, ?> fieldId;
513        private final int accessFlags;
514        private final Object staticValue;
515
516        FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) {
517            if ((accessFlags & STATIC) == 0 && staticValue != null) {
518                throw new IllegalArgumentException("instance fields may not have a value");
519            }
520            this.fieldId = fieldId;
521            this.accessFlags = accessFlags;
522            this.staticValue = staticValue;
523        }
524
525        EncodedField toEncodedField() {
526            return new EncodedField(fieldId.constant, accessFlags);
527        }
528
529        public boolean isStatic() {
530            return (accessFlags & STATIC) != 0;
531        }
532    }
533
534    static class MethodDeclaration {
535        final MethodId<?, ?> method;
536        private final int flags;
537        private final Code code;
538
539        public MethodDeclaration(MethodId<?, ?> method, int flags) {
540            this.method = method;
541            this.flags = flags;
542            this.code = new Code(this);
543        }
544
545        boolean isStatic() {
546            return (flags & STATIC) != 0;
547        }
548
549        boolean isDirect() {
550            return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0;
551        }
552
553        EncodedMethod toEncodedMethod(DexOptions dexOptions) {
554            RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0);
555            LocalVariableInfo locals = null;
556            DalvCode dalvCode = RopTranslator.translate(
557                    ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions);
558            return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY);
559        }
560    }
561}
562