blob: 755c9fa2be91f2a5ef35b24cd7cca2f5d8491c4e [file] [log] [blame]
Jesse Wilson579d7732012-01-03 16:12:39 -05001/*
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
Paul Duffinb8a58962017-03-15 14:14:35 +000017package com.android.dx;
Jesse Wilson579d7732012-01-03 16:12:39 -050018
Paul Duffinb8a58962017-03-15 14:14:35 +000019import com.android.dex.DexFormat;
Jesse Wilson579d7732012-01-03 16:12:39 -050020import 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;
Jesse Wilson56242282012-01-09 17:30:53 -050028import com.android.dx.rop.code.AccessFlags;
Jesse Wilson579d7732012-01-03 16:12:39 -050029import 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;
Philip P. Moltmann171f0972017-11-20 09:38:54 -080034
Jesse Wilson579d7732012-01-03 16:12:39 -050035import java.io.File;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.lang.reflect.InvocationTargetException;
Jesse Wilson23abc2f2012-01-06 14:58:00 -050039import java.lang.reflect.Modifier;
Andrew Yousef054604d2015-03-12 13:13:43 -070040import java.util.Arrays;
41import java.util.Iterator;
Jesse Wilson579d7732012-01-03 16:12:39 -050042import java.util.LinkedHashMap;
43import java.util.Map;
Andrew Yousef054604d2015-03-12 13:13:43 -070044import java.util.Set;
Jesse Wilson579d7732012-01-03 16:12:39 -050045import java.util.jar.JarEntry;
46import java.util.jar.JarOutputStream;
47
Paul Duffinb8a58962017-03-15 14:14:35 +000048import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR;
49import static java.lang.reflect.Modifier.PRIVATE;
50import static java.lang.reflect.Modifier.STATIC;
51
Philip P. Moltmann314cb2e2018-05-21 19:15:56 +000052import android.util.Log;
53
Jesse Wilson579d7732012-01-03 16:12:39 -050054/**
Paul Duffinb8a58962017-03-15 14:14:35 +000055 * Generates a <strong>D</strong>alvik <strong>EX</strong>ecutable (dex)
Jesse Wilsonb0f6ea82012-01-07 12:06:58 -050056 * file for execution on Android. Dex files define classes and interfaces,
Jesse Wilson0e49fb92012-01-06 11:14:53 -050057 * 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
Jesse Wilson008290a2012-01-06 15:07:12 -050075 * equivalent to the following Java source: <pre> {@code
Jesse Wilson0e49fb92012-01-06 11:14:53 -050076 *
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 *
Jesse Wilson008290a2012-01-06 15:07:12 -0500128 * <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
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500134 *
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
Jesse Wilson008290a2012-01-06 15:07:12 -0500141 * 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
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500146 *
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 *
Jesse Wilson008290a2012-01-06 15:07:12 -0500157 * <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.
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500163 *
Jesse Wilsonb0f6ea82012-01-07 12:06:58 -0500164 * <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
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500166 *
167 * code.loadConstant(constant1, 1);
168 * code.loadConstant(constant2, 2);
Jesse Wilson23abc2f2012-01-06 14:58:00 -0500169 * Label baseCase = new Label();
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500170 * 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 *
Jesse Wilson008290a2012-01-06 15:07:12 -0500181 * <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
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500183 * the generated code into the current process. This only works when the current
Jesse Wilson3e7a2232012-01-10 12:26:29 -0500184 * 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
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500188 *
189 * ClassLoader loader = dexMaker.generateAndLoad(
Jesse Wilson5692b3b2012-01-10 12:29:52 -0500190 * FibonacciMaker.class.getClassLoader(), getDataDirectory());
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500191 * }</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>
Jesse Wilson579d7732012-01-03 16:12:39 -0500199 */
Jesse Wilsonab220f02012-01-05 15:18:59 -0500200public final class DexMaker {
Philip P. Moltmann314cb2e2018-05-21 19:15:56 +0000201 private static final String LOG_TAG = DexMaker.class.getSimpleName();
202
Paul Duffinb8a58962017-03-15 14:14:35 +0000203 private final Map<TypeId<?>, TypeDeclaration> types = new LinkedHashMap<>();
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800204 private ClassLoader sharedClassLoader;
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700205 private DexFile outputDex;
Philip P. Moltmann314cb2e2018-05-21 19:15:56 +0000206 private boolean markAsTrusted;
Jesse Wilson579d7732012-01-03 16:12:39 -0500207
Jesse Wilson3e7a2232012-01-10 12:26:29 -0500208 /**
209 * Creates a new {@code DexMaker} instance, which can be used to create a
210 * single dex file.
211 */
212 public DexMaker() {
213 }
214
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700215 TypeDeclaration getTypeDeclaration(TypeId<?> type) {
Jesse Wilson579d7732012-01-03 16:12:39 -0500216 TypeDeclaration result = types.get(type);
217 if (result == null) {
218 result = new TypeDeclaration(type);
219 types.put(type, result);
220 }
221 return result;
222 }
223
224 /**
Jesse Wilson23abc2f2012-01-06 14:58:00 -0500225 * Declares {@code type}.
226 *
227 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
228 * Modifier#FINAL} and {@link Modifier#ABSTRACT}.
Jesse Wilson579d7732012-01-03 16:12:39 -0500229 */
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500230 public void declare(TypeId<?> type, String sourceFile, int flags,
231 TypeId<?> supertype, TypeId<?>... interfaces) {
Jesse Wilson579d7732012-01-03 16:12:39 -0500232 TypeDeclaration declaration = getTypeDeclaration(type);
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500233 int supportedFlags = Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT;
234 if ((flags & ~supportedFlags) != 0) {
235 throw new IllegalArgumentException("Unexpected flag: "
236 + Integer.toHexString(flags));
237 }
Jesse Wilson579d7732012-01-03 16:12:39 -0500238 if (declaration.declared) {
239 throw new IllegalStateException("already declared: " + type);
240 }
241 declaration.declared = true;
242 declaration.flags = flags;
243 declaration.supertype = supertype;
244 declaration.sourceFile = sourceFile;
245 declaration.interfaces = new TypeList(interfaces);
246 }
247
248 /**
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500249 * Declares a method or constructor.
Jesse Wilson23abc2f2012-01-06 14:58:00 -0500250 *
251 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
252 * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500253 * {@link Modifier#FINAL} and {@link Modifier#SYNCHRONIZED}.
Jesse Wilson56242282012-01-09 17:30:53 -0500254 * <p><strong>Warning:</strong> the {@link Modifier#SYNCHRONIZED} flag
255 * is insufficient to generate a synchronized method. You must also use
256 * {@link Code#monitorEnter} and {@link Code#monitorExit} to acquire
257 * a monitor.
Jesse Wilson90699b92012-01-03 16:47:14 -0500258 */
Jesse Wilson579d7732012-01-03 16:12:39 -0500259 public Code declare(MethodId<?, ?> method, int flags) {
260 TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType);
261 if (typeDeclaration.methods.containsKey(method)) {
262 throw new IllegalStateException("already declared: " + method);
263 }
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500264
265 int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
266 | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED;
267 if ((flags & ~supportedFlags) != 0) {
268 throw new IllegalArgumentException("Unexpected flag: "
269 + Integer.toHexString(flags));
270 }
271
Jesse Wilson56242282012-01-09 17:30:53 -0500272 // replace the SYNCHRONIZED flag with the DECLARED_SYNCHRONIZED flag
273 if ((flags & Modifier.SYNCHRONIZED) != 0) {
274 flags = (flags & ~Modifier.SYNCHRONIZED) | AccessFlags.ACC_DECLARED_SYNCHRONIZED;
275 }
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500276
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800277 if (method.isConstructor() || method.isStaticInitializer()) {
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500278 flags |= ACC_CONSTRUCTOR;
279 }
280
Jesse Wilson579d7732012-01-03 16:12:39 -0500281 MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags);
282 typeDeclaration.methods.put(method, methodDeclaration);
283 return methodDeclaration.code;
284 }
285
286 /**
Jesse Wilson23abc2f2012-01-06 14:58:00 -0500287 * Declares a field.
288 *
289 * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
290 * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
291 * {@link Modifier#FINAL}, {@link Modifier#VOLATILE}, and {@link
292 * Modifier#TRANSIENT}.
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500293 * @param staticValue a constant representing the initial value for the
294 * static field, possibly null. This must be null if this field is
295 * non-static.
Jesse Wilson579d7732012-01-03 16:12:39 -0500296 */
297 public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) {
298 TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType);
299 if (typeDeclaration.fields.containsKey(fieldId)) {
300 throw new IllegalStateException("already declared: " + fieldId);
301 }
Jesse Wilsonc0271e92012-01-10 11:17:32 -0500302
303 int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
304 | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT;
305 if ((flags & ~supportedFlags) != 0) {
306 throw new IllegalArgumentException("Unexpected flag: "
307 + Integer.toHexString(flags));
308 }
309
310 if ((flags & Modifier.STATIC) == 0 && staticValue != null) {
311 throw new IllegalArgumentException("staticValue is non-null, but field is not static");
312 }
313
Jesse Wilson579d7732012-01-03 16:12:39 -0500314 FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue);
315 typeDeclaration.fields.put(fieldId, fieldDeclaration);
316 }
317
318 /**
Jesse Wilson23abc2f2012-01-06 14:58:00 -0500319 * Generates a dex file and returns its bytes.
Jesse Wilson579d7732012-01-03 16:12:39 -0500320 */
321 public byte[] generate() {
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700322 if (outputDex == null) {
323 DexOptions options = new DexOptions();
324 options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
325 outputDex = new DexFile(options);
326 }
Jesse Wilson579d7732012-01-03 16:12:39 -0500327
328 for (TypeDeclaration typeDeclaration : types.values()) {
329 outputDex.add(typeDeclaration.toClassDefItem());
330 }
331
332 try {
333 return outputDex.toDex(null, false);
334 } catch (IOException e) {
335 throw new RuntimeException(e);
336 }
337 }
338
Andrew Yousef054604d2015-03-12 13:13:43 -0700339 // Generate a file name for the jar by taking a checksum of MethodIds and
340 // parent class types.
341 private String generateFileName() {
342 int checksum = 1;
343
344 Set<TypeId<?>> typesKeySet = types.keySet();
345 Iterator<TypeId<?>> it = typesKeySet.iterator();
346 int[] checksums = new int[typesKeySet.size()];
347 int i = 0;
348
349 while (it.hasNext()) {
350 TypeId<?> typeId = it.next();
351 TypeDeclaration decl = getTypeDeclaration(typeId);
352 Set<MethodId> methodSet = decl.methods.keySet();
353 if (decl.supertype != null) {
354 checksums[i++] = 31 * decl.supertype.hashCode() + methodSet.hashCode();
355 }
356 }
357 Arrays.sort(checksums);
358
359 for (int sum : checksums) {
360 checksum *= 31;
361 checksum += sum;
362 }
363
364 return "Generated_" + checksum +".jar";
365 }
366
Philip P. Moltmann314cb2e2018-05-21 19:15:56 +0000367 /**
368 * Set shared class loader to use.
369 *
370 * <p>If a class wants to call package private methods of another class they need to share a
371 * class loader. One common case for this requirement is a mock class wanting to mock package
372 * private methods of the original class.
373 *
374 * @param classLoader the class loader the new class should be loaded by
375 */
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800376 public void setSharedClassLoader(ClassLoader classLoader) {
377 this.sharedClassLoader = classLoader;
378 }
379
Philip P. Moltmann314cb2e2018-05-21 19:15:56 +0000380 public void markAsTrusted() {
381 this.markAsTrusted = true;
382 }
383
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800384 private ClassLoader generateClassLoader(File result, File dexCache, ClassLoader parent) {
Andrew Yousef054604d2015-03-12 13:13:43 -0700385 try {
Philip P. Moltmann314cb2e2018-05-21 19:15:56 +0000386 // Try to load the class so that it can call hidden APIs. This is required for spying
387 // on system classes as real-methods of these classes might call blacklisted APIs
388 if (markAsTrusted) {
389 try {
390 if (sharedClassLoader != null) {
391 ClassLoader loader = parent != null ? parent : sharedClassLoader;
392 loader.getClass().getMethod("addDexPath", String.class,
393 Boolean.TYPE).invoke(loader, result.getPath(), true);
394 return loader;
395 } else {
396 return (ClassLoader) Class.forName("dalvik.system.BaseDexClassLoader")
397 .getConstructor(String.class, File.class, String.class,
398 ClassLoader.class, Boolean.TYPE)
399 .newInstance(result.getPath(), dexCache.getAbsoluteFile(), null,
400 parent, true);
401 }
402 } catch (InvocationTargetException e) {
403 if (e.getCause() instanceof SecurityException) {
404 Log.i(LOG_TAG, "Cannot allow to call blacklisted super methods. This might "
405 + "break spying on system classes.", e.getCause());
406 } else {
407 throw e;
408 }
409 }
410 }
411
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800412 if (sharedClassLoader != null) {
413 ClassLoader loader = parent != null ? parent : sharedClassLoader;
Paul Duffindb20bbc2017-03-15 16:15:29 +0000414 loader.getClass().getMethod("addDexPath", String.class).invoke(loader,
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800415 result.getPath());
Paul Duffindb20bbc2017-03-15 16:15:29 +0000416 return loader;
417 } else {
418 return (ClassLoader) Class.forName("dalvik.system.DexClassLoader")
419 .getConstructor(String.class, String.class, String.class, ClassLoader.class)
420 .newInstance(result.getPath(), dexCache.getAbsolutePath(), null, parent);
421 }
Andrew Yousef054604d2015-03-12 13:13:43 -0700422 } catch (ClassNotFoundException e) {
423 throw new UnsupportedOperationException("load() requires a Dalvik VM", e);
424 } catch (InvocationTargetException e) {
425 throw new RuntimeException(e.getCause());
426 } catch (InstantiationException e) {
427 throw new AssertionError();
428 } catch (NoSuchMethodException e) {
429 throw new AssertionError();
430 } catch (IllegalAccessException e) {
431 throw new AssertionError();
432 }
433 }
434
Jesse Wilson579d7732012-01-03 16:12:39 -0500435 /**
Jesse Wilson23abc2f2012-01-06 14:58:00 -0500436 * Generates a dex file and loads its types into the current process.
Jesse Wilson579d7732012-01-03 16:12:39 -0500437 *
Jesse Wilson73cfa442012-01-11 15:33:57 -0500438 * <h3>Picking a dex cache directory</h3>
439 * The {@code dexCache} should be an application-private directory. If
440 * you pass a world-writable directory like {@code /sdcard} a malicious app
441 * could inject code into your process. Most applications should use this:
442 * <pre> {@code
Jesse Wilson579d7732012-01-03 16:12:39 -0500443 *
Jesse Wilson73cfa442012-01-11 15:33:57 -0500444 * File dexCache = getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
445 * }</pre>
446 * If the {@code dexCache} is null, this method will consult the {@code
447 * dexmaker.dexcache} system property. If that exists, it will be used for
448 * the dex cache. If it doesn't exist, this method will attempt to guess
449 * the application's private data directory as a last resort. If that fails,
450 * this method will fail with an unchecked exception. You can avoid the
451 * exception by either providing a non-null value or setting the system
452 * property.
Jesse Wilson579d7732012-01-03 16:12:39 -0500453 *
Jesse Wilson73cfa442012-01-11 15:33:57 -0500454 * @param parent the parent ClassLoader to be used when loading our
455 * generated types
456 * @param dexCache the destination directory where generated and optimized
457 * dex files will be written. If null, this class will try to guess the
458 * application's private data dir.
Jesse Wilson579d7732012-01-03 16:12:39 -0500459 */
Jesse Wilson73cfa442012-01-11 15:33:57 -0500460 public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException {
461 if (dexCache == null) {
462 String property = System.getProperty("dexmaker.dexcache");
463 if (property != null) {
464 dexCache = new File(property);
465 } else {
466 dexCache = new AppDataDirGuesser().guess();
467 if (dexCache == null) {
468 throw new IllegalArgumentException("dexcache == null (and no default could be"
469 + " found; consider setting the 'dexmaker.dexcache' system property)");
470 }
471 }
472 }
473
Andrew Yousef054604d2015-03-12 13:13:43 -0700474 File result = new File(dexCache, generateFileName());
475 // Check that the file exists. If it does, return a DexClassLoader and skip all
476 // the dex bytecode generation.
477 if (result.exists()) {
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800478 return generateClassLoader(result, dexCache, parent);
Andrew Yousef054604d2015-03-12 13:13:43 -0700479 }
480
Jesse Wilson579d7732012-01-03 16:12:39 -0500481 byte[] dex = generate();
482
483 /*
484 * This implementation currently dumps the dex to the filesystem. It
485 * jars the emitted .dex for the benefit of Gingerbread and earlier
486 * devices, which can't load .dex files directly.
487 *
488 * TODO: load the dex from memory where supported.
489 */
Andrew Yousef054604d2015-03-12 13:13:43 -0700490 result.createNewFile();
Jesse Wilson579d7732012-01-03 16:12:39 -0500491 JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
Andreas Gampe5999dde2015-03-06 15:03:47 -0800492 JarEntry entry = new JarEntry(DexFormat.DEX_IN_JAR_NAME);
493 entry.setSize(dex.length);
494 jarOut.putNextEntry(entry);
Jesse Wilson579d7732012-01-03 16:12:39 -0500495 jarOut.write(dex);
496 jarOut.closeEntry();
497 jarOut.close();
Philip P. Moltmann171f0972017-11-20 09:38:54 -0800498 return generateClassLoader(result, dexCache, parent);
Jesse Wilson579d7732012-01-03 16:12:39 -0500499 }
500
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700501 DexFile getDexFile() {
502 if (outputDex == null) {
503 DexOptions options = new DexOptions();
504 options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
505 outputDex = new DexFile(options);
506 }
507 return outputDex;
508 }
509
510 static class TypeDeclaration {
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500511 private final TypeId<?> type;
Jesse Wilson579d7732012-01-03 16:12:39 -0500512
513 /** declared state */
514 private boolean declared;
515 private int flags;
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500516 private TypeId<?> supertype;
Jesse Wilson579d7732012-01-03 16:12:39 -0500517 private String sourceFile;
518 private TypeList interfaces;
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700519 private ClassDefItem classDefItem;
Jesse Wilson579d7732012-01-03 16:12:39 -0500520
Paul Duffinb8a58962017-03-15 14:14:35 +0000521 private final Map<FieldId, FieldDeclaration> fields = new LinkedHashMap<>();
522 private final Map<MethodId, MethodDeclaration> methods = new LinkedHashMap<>();
Jesse Wilson579d7732012-01-03 16:12:39 -0500523
Jesse Wilson0e49fb92012-01-06 11:14:53 -0500524 TypeDeclaration(TypeId<?> type) {
Jesse Wilson579d7732012-01-03 16:12:39 -0500525 this.type = type;
526 }
527
528 ClassDefItem toClassDefItem() {
529 if (!declared) {
530 throw new IllegalStateException("Undeclared type " + type + " declares members: "
531 + fields.keySet() + " " + methods.keySet());
532 }
533
534 DexOptions dexOptions = new DexOptions();
535 dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
536
537 CstType thisType = type.constant;
538
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700539 if (classDefItem == null) {
540 classDefItem = new ClassDefItem(thisType, flags, supertype.constant,
541 interfaces.ropTypes, new CstString(sourceFile));
Jesse Wilson579d7732012-01-03 16:12:39 -0500542
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700543 for (MethodDeclaration method : methods.values()) {
544 EncodedMethod encoded = method.toEncodedMethod(dexOptions);
545 if (method.isDirect()) {
546 classDefItem.addDirectMethod(encoded);
547 } else {
548 classDefItem.addVirtualMethod(encoded);
549 }
Jesse Wilson579d7732012-01-03 16:12:39 -0500550 }
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700551 for (FieldDeclaration field : fields.values()) {
552 EncodedField encoded = field.toEncodedField();
553 if (field.isStatic()) {
554 classDefItem.addStaticField(encoded, Constants.getConstant(field.staticValue));
555 } else {
556 classDefItem.addInstanceField(encoded);
557 }
Jesse Wilson579d7732012-01-03 16:12:39 -0500558 }
559 }
560
Philip P. Moltmannd4a20562018-03-13 13:11:07 -0700561 return classDefItem;
Jesse Wilson579d7732012-01-03 16:12:39 -0500562 }
563 }
564
565 static class FieldDeclaration {
566 final FieldId<?, ?> fieldId;
567 private final int accessFlags;
568 private final Object staticValue;
569
570 FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) {
Jesse Wilson90699b92012-01-03 16:47:14 -0500571 if ((accessFlags & STATIC) == 0 && staticValue != null) {
Jesse Wilson579d7732012-01-03 16:12:39 -0500572 throw new IllegalArgumentException("instance fields may not have a value");
573 }
574 this.fieldId = fieldId;
575 this.accessFlags = accessFlags;
576 this.staticValue = staticValue;
577 }
578
579 EncodedField toEncodedField() {
580 return new EncodedField(fieldId.constant, accessFlags);
581 }
582
583 public boolean isStatic() {
Jesse Wilson90699b92012-01-03 16:47:14 -0500584 return (accessFlags & STATIC) != 0;
Jesse Wilson579d7732012-01-03 16:12:39 -0500585 }
586 }
587
588 static class MethodDeclaration {
589 final MethodId<?, ?> method;
590 private final int flags;
591 private final Code code;
592
593 public MethodDeclaration(MethodId<?, ?> method, int flags) {
594 this.method = method;
595 this.flags = flags;
596 this.code = new Code(this);
597 }
598
599 boolean isStatic() {
Jesse Wilson90699b92012-01-03 16:47:14 -0500600 return (flags & STATIC) != 0;
Jesse Wilson579d7732012-01-03 16:12:39 -0500601 }
602
603 boolean isDirect() {
Jesse Wilson90699b92012-01-03 16:47:14 -0500604 return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0;
Jesse Wilson579d7732012-01-03 16:12:39 -0500605 }
606
607 EncodedMethod toEncodedMethod(DexOptions dexOptions) {
608 RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0);
609 LocalVariableInfo locals = null;
610 DalvCode dalvCode = RopTranslator.translate(
611 ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions);
612 return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY);
613 }
614 }
615}