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