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