• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.dx;
18 
19 import com.android.dex.DexFormat;
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;
28 import com.android.dx.rop.code.AccessFlags;
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;
34 
35 import java.io.File;
36 import java.io.FileOutputStream;
37 import java.io.IOException;
38 import java.lang.reflect.InvocationTargetException;
39 import java.lang.reflect.Modifier;
40 import java.util.Arrays;
41 import java.util.Iterator;
42 import java.util.LinkedHashMap;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.jar.JarEntry;
46 import java.util.jar.JarOutputStream;
47 
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 
52 /**
53  * Generates a <strong>D</strong>alvik <strong>EX</strong>ecutable (dex)
54  * file for execution on Android. Dex files define classes and interfaces,
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
73  * equivalent to the following Java source: <pre> {@code
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  *
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
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
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
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  *
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.
161  *
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
164  *
165  *   code.loadConstant(constant1, 1);
166  *   code.loadConstant(constant2, 2);
167  *   Label baseCase = new Label();
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  *
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
181  * the generated code into the current process. This only works when the current
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
186  *
187  *   ClassLoader loader = dexMaker.generateAndLoad(
188  *       FibonacciMaker.class.getClassLoader(), getDataDirectory());
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>
197  */
198 public final class DexMaker {
199     private final Map<TypeId<?>, TypeDeclaration> types = new LinkedHashMap<>();
200 
201     // Only warn about not being able to deal with blacklisted methods once. Often this is no
202     // problem and warning on every class load is too spammy.
203     private static boolean didWarnBlacklistedMethods;
204     private static boolean didWarnNonBaseDexClassLoader;
205 
206     private ClassLoader sharedClassLoader;
207     private DexFile outputDex;
208     private boolean markAsTrusted;
209 
210     /**
211      * Creates a new {@code DexMaker} instance, which can be used to create a
212      * single dex file.
213      */
DexMaker()214     public DexMaker() {
215     }
216 
getTypeDeclaration(TypeId<?> type)217     TypeDeclaration getTypeDeclaration(TypeId<?> type) {
218         TypeDeclaration result = types.get(type);
219         if (result == null) {
220             result = new TypeDeclaration(type);
221             types.put(type, result);
222         }
223         return result;
224     }
225 
226     /**
227      * Declares {@code type}.
228      *
229      * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
230      *     Modifier#FINAL} and {@link Modifier#ABSTRACT}.
231      */
declare(TypeId<?> type, String sourceFile, int flags, TypeId<?> supertype, TypeId<?>... interfaces)232     public void declare(TypeId<?> type, String sourceFile, int flags,
233             TypeId<?> supertype, TypeId<?>... interfaces) {
234         TypeDeclaration declaration = getTypeDeclaration(type);
235         int supportedFlags = Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT
236                 | AccessFlags.ACC_SYNTHETIC;
237         if ((flags & ~supportedFlags) != 0) {
238             throw new IllegalArgumentException("Unexpected flag: "
239                     + Integer.toHexString(flags));
240         }
241         if (declaration.declared) {
242             throw new IllegalStateException("already declared: " + type);
243         }
244         declaration.declared = true;
245         declaration.flags = flags;
246         declaration.supertype = supertype;
247         declaration.sourceFile = sourceFile;
248         declaration.interfaces = new TypeList(interfaces);
249     }
250 
251     /**
252      * Declares a method or constructor.
253      *
254      * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
255      *     Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
256      *     {@link Modifier#FINAL} and {@link Modifier#SYNCHRONIZED}.
257      *     <p><strong>Warning:</strong> the {@link Modifier#SYNCHRONIZED} flag
258      *     is insufficient to generate a synchronized method. You must also use
259      *     {@link Code#monitorEnter} and {@link Code#monitorExit} to acquire
260      *     a monitor.
261      */
declare(MethodId<?, ?> method, int flags)262     public Code declare(MethodId<?, ?> method, int flags) {
263         TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType);
264         if (typeDeclaration.methods.containsKey(method)) {
265             throw new IllegalStateException("already declared: " + method);
266         }
267 
268         int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
269                 | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED
270                 | AccessFlags.ACC_SYNTHETIC | AccessFlags.ACC_BRIDGE;
271         if ((flags & ~supportedFlags) != 0) {
272             throw new IllegalArgumentException("Unexpected flag: "
273                     + Integer.toHexString(flags));
274         }
275 
276         // replace the SYNCHRONIZED flag with the DECLARED_SYNCHRONIZED flag
277         if ((flags & Modifier.SYNCHRONIZED) != 0) {
278             flags = (flags & ~Modifier.SYNCHRONIZED) | AccessFlags.ACC_DECLARED_SYNCHRONIZED;
279         }
280 
281         if (method.isConstructor() || method.isStaticInitializer()) {
282             flags |= ACC_CONSTRUCTOR;
283         }
284 
285         MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags);
286         typeDeclaration.methods.put(method, methodDeclaration);
287         return methodDeclaration.code;
288     }
289 
290     /**
291      * Declares a field.
292      *
293      * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
294      *     Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
295      *     {@link Modifier#FINAL}, {@link Modifier#VOLATILE}, and {@link
296      *     Modifier#TRANSIENT}.
297      * @param staticValue a constant representing the initial value for the
298      *     static field, possibly null. This must be null if this field is
299      *     non-static.
300      */
declare(FieldId<?, ?> fieldId, int flags, Object staticValue)301     public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) {
302         TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType);
303         if (typeDeclaration.fields.containsKey(fieldId)) {
304             throw new IllegalStateException("already declared: " + fieldId);
305         }
306 
307         int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
308                 | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT
309                 | AccessFlags.ACC_SYNTHETIC;
310         if ((flags & ~supportedFlags) != 0) {
311             throw new IllegalArgumentException("Unexpected flag: "
312                     + Integer.toHexString(flags));
313         }
314 
315         if ((flags & Modifier.STATIC) == 0 && staticValue != null) {
316             throw new IllegalArgumentException("staticValue is non-null, but field is not static");
317         }
318 
319         FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue);
320         typeDeclaration.fields.put(fieldId, fieldDeclaration);
321     }
322 
323     /**
324      * Generates a dex file and returns its bytes.
325      */
generate()326     public byte[] generate() {
327         if (outputDex == null) {
328             DexOptions options = new DexOptions();
329             options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
330             outputDex = new DexFile(options);
331         }
332 
333         for (TypeDeclaration typeDeclaration : types.values()) {
334             outputDex.add(typeDeclaration.toClassDefItem());
335         }
336 
337         try {
338             return outputDex.toDex(null, false);
339         } catch (IOException e) {
340             throw new RuntimeException(e);
341         }
342     }
343 
344     // Generate a file name for the jar by taking a checksum of MethodIds and
345     // parent class types.
generateFileName()346     private String generateFileName() {
347         int checksum = 1;
348 
349         Set<TypeId<?>> typesKeySet = types.keySet();
350         Iterator<TypeId<?>> it = typesKeySet.iterator();
351         int[] checksums = new int[typesKeySet.size()];
352         int i = 0;
353 
354         while (it.hasNext()) {
355             TypeId<?> typeId = it.next();
356             TypeDeclaration decl = getTypeDeclaration(typeId);
357             Set<MethodId> methodSet = decl.methods.keySet();
358             if (decl.supertype != null) {
359                 int sum = 31 * decl.supertype.hashCode() + decl.interfaces.hashCode();
360                 checksums[i++] = 31 * sum + methodSet.hashCode();
361             }
362         }
363         Arrays.sort(checksums);
364 
365         for (int sum : checksums) {
366             checksum *= 31;
367             checksum += sum;
368         }
369 
370         return "Generated_" + checksum +".jar";
371     }
372 
373     /**
374      * Set shared class loader to use.
375      *
376      * <p>If a class wants to call package private methods of another class they need to share a
377      * class loader. One common case for this requirement is a mock class wanting to mock package
378      * private methods of the original class.
379      *
380      * <p>If the classLoader is not a subclass of {@code dalvik.system.BaseDexClassLoader} this
381      * option is ignored.
382      *
383      * @param classLoader the class loader the new class should be loaded by
384      */
setSharedClassLoader(ClassLoader classLoader)385     public void setSharedClassLoader(ClassLoader classLoader) {
386         this.sharedClassLoader = classLoader;
387     }
388 
markAsTrusted()389     public void markAsTrusted() {
390         this.markAsTrusted = true;
391     }
392 
generateClassLoader(File result, File dexCache, ClassLoader parent)393     private ClassLoader generateClassLoader(File result, File dexCache, ClassLoader parent) {
394         try {
395             boolean shareClassLoader = sharedClassLoader != null;
396 
397             ClassLoader preferredClassLoader = null;
398             if (parent != null) {
399                 preferredClassLoader = parent;
400             } else if (sharedClassLoader != null) {
401                 preferredClassLoader = sharedClassLoader;
402             }
403 
404             Class baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
405 
406             if (shareClassLoader) {
407                 if (!baseDexClassLoaderClass.isAssignableFrom(preferredClassLoader.getClass())) {
408                     if (!preferredClassLoader.getClass().getName().equals(
409                             "java.lang.BootClassLoader")) {
410                         if (!didWarnNonBaseDexClassLoader) {
411                             System.err.println("Cannot share classloader as shared classloader '"
412                                     + preferredClassLoader + "' is not a subclass of '"
413                                     + baseDexClassLoaderClass
414                                     + "'");
415                             didWarnNonBaseDexClassLoader = true;
416                         }
417                     }
418 
419                     shareClassLoader = false;
420                 }
421             }
422 
423             // Try to load the class so that it can call hidden APIs. This is required for spying
424             // on system classes as real-methods of these classes might call blacklisted APIs
425             if (markAsTrusted) {
426                 try {
427                     if (shareClassLoader) {
428                         preferredClassLoader.getClass().getMethod("addDexPath", String.class,
429                                 Boolean.TYPE).invoke(preferredClassLoader, result.getPath(), true);
430                         return preferredClassLoader;
431                     } else {
432                         return (ClassLoader) baseDexClassLoaderClass
433                                 .getConstructor(String.class, File.class, String.class,
434                                         ClassLoader.class, Boolean.TYPE)
435                                 .newInstance(result.getPath(), dexCache.getAbsoluteFile(), null,
436                                         preferredClassLoader, true);
437                     }
438                 } catch (InvocationTargetException e) {
439                     if (e.getCause() instanceof SecurityException) {
440                         if (!didWarnBlacklistedMethods) {
441                             System.err.println("Cannot allow to call blacklisted super methods. "
442                                     + "This might break spying on system classes." + e.getCause());
443                             didWarnBlacklistedMethods = true;
444                         }
445                     } else {
446                         throw e;
447                     }
448                 }
449             }
450 
451             if (shareClassLoader) {
452                 preferredClassLoader.getClass().getMethod("addDexPath", String.class).invoke(
453                         preferredClassLoader, result.getPath());
454                 return preferredClassLoader;
455             } else {
456                 return (ClassLoader) Class.forName("dalvik.system.DexClassLoader")
457                         .getConstructor(String.class, String.class, String.class, ClassLoader.class)
458                         .newInstance(result.getPath(), dexCache.getAbsolutePath(), null,
459                                 preferredClassLoader);
460             }
461         } catch (ClassNotFoundException e) {
462             throw new UnsupportedOperationException("load() requires a Dalvik VM", e);
463         } catch (InvocationTargetException e) {
464             throw new RuntimeException(e.getCause());
465         } catch (InstantiationException e) {
466             throw new AssertionError();
467         } catch (NoSuchMethodException e) {
468             throw new AssertionError();
469         } catch (IllegalAccessException e) {
470             throw new AssertionError();
471         }
472     }
473 
474     /**
475      * Generates a dex file and loads its types into the current process.
476      *
477      * <h3>Picking a dex cache directory</h3>
478      * The {@code dexCache} should be an application-private directory. If
479      * you pass a world-writable directory like {@code /sdcard} a malicious app
480      * could inject code into your process. Most applications should use this:
481      * <pre>   {@code
482      *
483      *     File dexCache = getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
484      * }</pre>
485      * If the {@code dexCache} is null, this method will consult the {@code
486      * dexmaker.dexcache} system property. If that exists, it will be used for
487      * the dex cache. If it doesn't exist, this method will attempt to guess
488      * the application's private data directory as a last resort. If that fails,
489      * this method will fail with an unchecked exception. You can avoid the
490      * exception by either providing a non-null value or setting the system
491      * property.
492      *
493      * @param parent the parent ClassLoader to be used when loading our
494      *     generated types (if set, overrides
495      *     {@link #setSharedClassLoader(ClassLoader) shared class loader}.
496      * @param dexCache the destination directory where generated and optimized
497      *     dex files will be written. If null, this class will try to guess the
498      *     application's private data dir.
499      */
generateAndLoad(ClassLoader parent, File dexCache)500     public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException {
501         if (dexCache == null) {
502             String property = System.getProperty("dexmaker.dexcache");
503             if (property != null) {
504                 dexCache = new File(property);
505             } else {
506                 dexCache = new AppDataDirGuesser().guess();
507                 if (dexCache == null) {
508                     throw new IllegalArgumentException("dexcache == null (and no default could be"
509                             + " found; consider setting the 'dexmaker.dexcache' system property)");
510                 }
511             }
512         }
513 
514         File result = new File(dexCache, generateFileName());
515         // Check that the file exists. If it does, return a DexClassLoader and skip all
516         // the dex bytecode generation.
517         if (result.exists()) {
518             return generateClassLoader(result, dexCache, parent);
519         }
520 
521         byte[] dex = generate();
522 
523         /*
524          * This implementation currently dumps the dex to the filesystem. It
525          * jars the emitted .dex for the benefit of Gingerbread and earlier
526          * devices, which can't load .dex files directly.
527          *
528          * TODO: load the dex from memory where supported.
529          */
530         result.createNewFile();
531         JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
532         JarEntry entry = new JarEntry(DexFormat.DEX_IN_JAR_NAME);
533         entry.setSize(dex.length);
534         jarOut.putNextEntry(entry);
535         jarOut.write(dex);
536         jarOut.closeEntry();
537         jarOut.close();
538         return generateClassLoader(result, dexCache, parent);
539     }
540 
getDexFile()541     DexFile getDexFile() {
542         if (outputDex == null) {
543             DexOptions options = new DexOptions();
544             options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
545             outputDex = new DexFile(options);
546         }
547         return outputDex;
548     }
549 
550     static class TypeDeclaration {
551         private final TypeId<?> type;
552 
553         /** declared state */
554         private boolean declared;
555         private int flags;
556         private TypeId<?> supertype;
557         private String sourceFile;
558         private TypeList interfaces;
559         private ClassDefItem classDefItem;
560 
561         private final Map<FieldId, FieldDeclaration> fields = new LinkedHashMap<>();
562         private final Map<MethodId, MethodDeclaration> methods = new LinkedHashMap<>();
563 
TypeDeclaration(TypeId<?> type)564         TypeDeclaration(TypeId<?> type) {
565             this.type = type;
566         }
567 
toClassDefItem()568         ClassDefItem toClassDefItem() {
569             if (!declared) {
570                 throw new IllegalStateException("Undeclared type " + type + " declares members: "
571                         + fields.keySet() + " " + methods.keySet());
572             }
573 
574             DexOptions dexOptions = new DexOptions();
575             dexOptions.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
576 
577             CstType thisType = type.constant;
578 
579             if (classDefItem == null) {
580                 classDefItem = new ClassDefItem(thisType, flags, supertype.constant,
581                         interfaces.ropTypes, new CstString(sourceFile));
582 
583                 for (MethodDeclaration method : methods.values()) {
584                     EncodedMethod encoded = method.toEncodedMethod(dexOptions);
585                     if (method.isDirect()) {
586                         classDefItem.addDirectMethod(encoded);
587                     } else {
588                         classDefItem.addVirtualMethod(encoded);
589                     }
590                 }
591                 for (FieldDeclaration field : fields.values()) {
592                     EncodedField encoded = field.toEncodedField();
593                     if (field.isStatic()) {
594                         classDefItem.addStaticField(encoded, Constants.getConstant(field.staticValue));
595                     } else {
596                         classDefItem.addInstanceField(encoded);
597                     }
598                 }
599             }
600 
601             return classDefItem;
602         }
603     }
604 
605     static class FieldDeclaration {
606         final FieldId<?, ?> fieldId;
607         private final int accessFlags;
608         private final Object staticValue;
609 
FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue)610         FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) {
611             if ((accessFlags & STATIC) == 0 && staticValue != null) {
612                 throw new IllegalArgumentException("instance fields may not have a value");
613             }
614             this.fieldId = fieldId;
615             this.accessFlags = accessFlags;
616             this.staticValue = staticValue;
617         }
618 
toEncodedField()619         EncodedField toEncodedField() {
620             return new EncodedField(fieldId.constant, accessFlags);
621         }
622 
isStatic()623         public boolean isStatic() {
624             return (accessFlags & STATIC) != 0;
625         }
626     }
627 
628     static class MethodDeclaration {
629         final MethodId<?, ?> method;
630         private final int flags;
631         private final Code code;
632 
MethodDeclaration(MethodId<?, ?> method, int flags)633         public MethodDeclaration(MethodId<?, ?> method, int flags) {
634             this.method = method;
635             this.flags = flags;
636             this.code = new Code(this);
637         }
638 
isStatic()639         boolean isStatic() {
640             return (flags & STATIC) != 0;
641         }
642 
isDirect()643         boolean isDirect() {
644             return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0;
645         }
646 
toEncodedMethod(DexOptions dexOptions)647         EncodedMethod toEncodedMethod(DexOptions dexOptions) {
648             RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0);
649             LocalVariableInfo locals = null;
650             DalvCode dalvCode = RopTranslator.translate(
651                     ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions);
652             return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY);
653         }
654     }
655 }
656