DexMaker.java revision ab220f004db90fa94ef9349ca1adde5f89012e8d
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 * Define types, fields and methods.
48 */
49public final class DexMaker {
50    private final Map<Type<?>, TypeDeclaration> types
51            = new LinkedHashMap<Type<?>, TypeDeclaration>();
52
53    private TypeDeclaration getTypeDeclaration(Type<?> type) {
54        TypeDeclaration result = types.get(type);
55        if (result == null) {
56            result = new TypeDeclaration(type);
57            types.put(type, result);
58        }
59        return result;
60    }
61
62    // TODO: describe the legal flags without referring to a non-public API AccessFlags
63
64    /**
65     * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#CLASS_FLAGS}.
66     */
67    public void declare(Type<?> type, String sourceFile, int flags,
68            Type<?> supertype, Type<?>... interfaces) {
69        TypeDeclaration declaration = getTypeDeclaration(type);
70        if (declaration.declared) {
71            throw new IllegalStateException("already declared: " + type);
72        }
73        declaration.declared = true;
74        declaration.flags = flags;
75        declaration.supertype = supertype;
76        declaration.sourceFile = sourceFile;
77        declaration.interfaces = new TypeList(interfaces);
78    }
79
80    /**
81     * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#METHOD_FLAGS}.
82     */
83    public Code declareConstructor(MethodId<?, ?> method, int flags) {
84        return declare(method, flags | ACC_CONSTRUCTOR);
85    }
86
87    /**
88     * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#METHOD_FLAGS}.
89     */
90    public Code declare(MethodId<?, ?> method, int flags) {
91        TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType);
92        if (typeDeclaration.methods.containsKey(method)) {
93            throw new IllegalStateException("already declared: " + method);
94        }
95        MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags);
96        typeDeclaration.methods.put(method, methodDeclaration);
97        return methodDeclaration.code;
98    }
99
100    /**
101     * @param flags any flags masked by {@link AccessFlags#FIELD_FLAGS}.
102     */
103    public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) {
104        TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType);
105        if (typeDeclaration.fields.containsKey(fieldId)) {
106            throw new IllegalStateException("already declared: " + fieldId);
107        }
108        FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue);
109        typeDeclaration.fields.put(fieldId, fieldDeclaration);
110    }
111
112    /**
113     * Returns a .dex formatted file.
114     */
115    public byte[] generate() {
116        DexOptions options = new DexOptions();
117        options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
118        DexFile outputDex = new DexFile(options);
119
120        for (TypeDeclaration typeDeclaration : types.values()) {
121            outputDex.add(typeDeclaration.toClassDefItem());
122        }
123
124        try {
125            return outputDex.toDex(null, false);
126        } catch (IOException e) {
127            throw new RuntimeException(e);
128        }
129    }
130
131    /**
132     * Loads the generated types into the current process.
133     *
134     * <p>All parameters are optional, you may pass {@code null} and suitable
135     * defaults will be used.
136     *
137     * <p>If you opt to provide your own output directories, take care to
138     * ensure that they are not world-readable, otherwise a malicious app will
139     * be able to inject code to run.  A suitable parameter for these output
140     * directories would be something like this:
141     * {@code getApplicationContext().getDir("dx", Context.MODE_PRIVATE); }
142     *
143     * @param parent the parent ClassLoader to be used when loading
144     *     our generated types
145     * @param dexOutputDir the destination directory wherein we will write
146     *     emitted .dex files before they end up in the cache directory
147     * @param dexOptCacheDir where optimized .dex files are to be written
148     */
149    public ClassLoader load(ClassLoader parent, File dexOutputDir, File dexOptCacheDir)
150            throws IOException {
151        byte[] dex = generate();
152
153        /*
154         * This implementation currently dumps the dex to the filesystem. It
155         * jars the emitted .dex for the benefit of Gingerbread and earlier
156         * devices, which can't load .dex files directly.
157         *
158         * TODO: load the dex from memory where supported.
159         */
160        File result = File.createTempFile("Generated", ".jar", dexOutputDir);
161        result.deleteOnExit();
162        JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
163        jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME));
164        jarOut.write(dex);
165        jarOut.closeEntry();
166        jarOut.close();
167        try {
168            return (ClassLoader) Class.forName("dalvik.system.DexClassLoader")
169                    .getConstructor(String.class, String.class, String.class, ClassLoader.class)
170                    .newInstance(result.getPath(), dexOptCacheDir.getAbsolutePath(), null, parent);
171        } catch (ClassNotFoundException e) {
172            throw new UnsupportedOperationException("load() requires a Dalvik VM", e);
173        } catch (InvocationTargetException e) {
174            throw new RuntimeException(e.getCause());
175        } catch (InstantiationException e) {
176            throw new AssertionError();
177        } catch (NoSuchMethodException e) {
178            throw new AssertionError();
179        } catch (IllegalAccessException e) {
180            throw new AssertionError();
181        }
182    }
183
184    private static class TypeDeclaration {
185        private final Type<?> type;
186
187        /** declared state */
188        private boolean declared;
189        private int flags;
190        private Type<?> supertype;
191        private String sourceFile;
192        private TypeList interfaces;
193
194        private final Map<FieldId, FieldDeclaration> fields
195                = new LinkedHashMap<FieldId, FieldDeclaration>();
196        private final Map<MethodId, MethodDeclaration> methods
197                = new LinkedHashMap<MethodId, MethodDeclaration>();
198
199        TypeDeclaration(Type<?> type) {
200            this.type = type;
201        }
202
203        ClassDefItem toClassDefItem() {
204            if (!declared) {
205                throw new IllegalStateException("Undeclared type " + type + " declares members: "
206                        + fields.keySet() + " " + methods.keySet());
207            }
208
209            DexOptions dexOptions = new DexOptions();
210            dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
211
212            CstType thisType = type.constant;
213
214            ClassDefItem out = new ClassDefItem(thisType, flags, supertype.constant,
215                    interfaces.ropTypes, new CstString(sourceFile));
216
217            for (MethodDeclaration method : methods.values()) {
218                EncodedMethod encoded = method.toEncodedMethod(dexOptions);
219                if (method.isDirect()) {
220                    out.addDirectMethod(encoded);
221                } else {
222                    out.addVirtualMethod(encoded);
223                }
224            }
225            for (FieldDeclaration field : fields.values()) {
226                EncodedField encoded = field.toEncodedField();
227                if (field.isStatic()) {
228                    out.addStaticField(encoded, Constants.getConstant(field.staticValue));
229                } else {
230                    out.addInstanceField(encoded);
231                }
232            }
233
234            return out;
235        }
236    }
237
238    static class FieldDeclaration {
239        final FieldId<?, ?> fieldId;
240        private final int accessFlags;
241        private final Object staticValue;
242
243        FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) {
244            if ((accessFlags & STATIC) == 0 && staticValue != null) {
245                throw new IllegalArgumentException("instance fields may not have a value");
246            }
247            this.fieldId = fieldId;
248            this.accessFlags = accessFlags;
249            this.staticValue = staticValue;
250        }
251
252        EncodedField toEncodedField() {
253            return new EncodedField(fieldId.constant, accessFlags);
254        }
255
256        public boolean isStatic() {
257            return (accessFlags & STATIC) != 0;
258        }
259    }
260
261    static class MethodDeclaration {
262        final MethodId<?, ?> method;
263        private final int flags;
264        private final Code code;
265
266        public MethodDeclaration(MethodId<?, ?> method, int flags) {
267            this.method = method;
268            this.flags = flags;
269            this.code = new Code(this);
270        }
271
272        boolean isStatic() {
273            return (flags & STATIC) != 0;
274        }
275
276        boolean isDirect() {
277            return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0;
278        }
279
280        EncodedMethod toEncodedMethod(DexOptions dexOptions) {
281            RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0);
282            LocalVariableInfo locals = null;
283            DalvCode dalvCode = RopTranslator.translate(
284                    ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions);
285            return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY);
286        }
287    }
288}
289