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