aboutsummaryrefslogtreecommitdiff
path: root/dexmaker/src/main/java/com/android/dx/DexMaker.java
blob: a039bcdbd53b872f5d17fdb5127468bd07dbac60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx;

import com.android.dex.DexFormat;
import com.android.dx.dex.DexOptions;
import com.android.dx.dex.code.DalvCode;
import com.android.dx.dex.code.PositionList;
import com.android.dx.dex.code.RopTranslator;
import com.android.dx.dex.file.ClassDefItem;
import com.android.dx.dex.file.DexFile;
import com.android.dx.dex.file.EncodedField;
import com.android.dx.dex.file.EncodedMethod;
import com.android.dx.rop.code.AccessFlags;
import com.android.dx.rop.code.LocalVariableInfo;
import com.android.dx.rop.code.RopMethod;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.StdTypeList;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;

import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR;
import static java.lang.reflect.Modifier.*;

/**
 * Generates a <strong>D</strong>alvik <strong>EX</strong>ecutable (dex)
 * file for execution on Android. Dex files define classes and interfaces,
 * including their member methods and fields, executable code, and debugging
 * information. They also define annotations, though this API currently has no
 * facility to create a dex file that contains annotations.
 *
 * <p>This library is intended to satisfy two use cases:
 * <ul>
 *   <li><strong>For runtime code generation.</strong> By embedding this library
 *       in your Android application, you can dynamically generate and load
 *       executable code. This approach takes advantage of the fact that the
 *       host environment and target environment are both Android.
 *   <li><strong>For compile time code generation.</strong> You may use this
 *       library as a part of a compiler that targets Android. In this scenario
 *       the generated dex file must be installed on an Android device before it
 *       can be executed.
 * </ul>
 *
 * <h3>Example: Fibonacci</h3>
 * To illustrate how this API is used, we'll use DexMaker to generate a class
 * equivalent to the following Java source: <pre> {@code
 *
 * package com.publicobject.fib;
 *
 * public class Fibonacci {
 *   public static int fib(int i) {
 *     if (i < 2) {
 *       return i;
 *     }
 *     return fib(i - 1) + fib(i - 2);
 *   }
 * }}</pre>
 *
 * <p>We start by creating a {@link TypeId} to identify the generated {@code
 * Fibonacci} class. DexMaker identifies types by their internal names like
 * {@code Ljava/lang/Object;} rather than their Java identifiers like {@code
 * java.lang.Object}. <pre>   {@code
 *
 *   TypeId<?> fibonacci = TypeId.get("Lcom/google/dexmaker/examples/Fibonacci;");
 * }</pre>
 *
 * <p>Next we declare the class. It allows us to specify the type's source file
 * for stack traces, its modifiers, its superclass, and the interfaces it
 * implements. In this case, {@code Fibonacci} is a public class that extends
 * from {@code Object}: <pre>   {@code
 *
 *   String fileName = "Fibonacci.generated";
 *   DexMaker dexMaker = new DexMaker();
 *   dexMaker.declare(fibonacci, fileName, Modifier.PUBLIC, TypeId.OBJECT);
 * }</pre>
 * It is illegal to declare members of a class without also declaring the class
 * itself.
 *
 * <p>To make it easier to go from our Java method to dex instructions, we'll
 * manually translate it to pseudocode fit for an assembler. We need to replace
 * control flow like {@code if()} blocks and {@code for()} loops with labels and
 * branches. We'll also avoid performing multiple operations in one statement,
 * using local variables to hold intermediate values as necessary:
 * <pre>   {@code
 *
 *   int constant1 = 1;
 *   int constant2 = 2;
 *   if (i < constant2) goto baseCase;
 *   int a = i - constant1;
 *   int b = i - constant2;
 *   int c = fib(a);
 *   int d = fib(b);
 *   int result = c + d;
 *   return result;
 * baseCase:
 *   return i;
 * }</pre>
 *
 * <p>We look up the {@code MethodId} for the method on the declaring type. This
 * takes the method's return type (possibly {@link TypeId#VOID}), its name and
 * its parameters types. Next we declare the method, specifying its modifiers by
 * bitwise ORing constants from {@link java.lang.reflect.Modifier}. The declare
 * call returns a {@link Code} object, which we'll use to define the method's
 * instructions. <pre>   {@code
 *
 *   MethodId<?, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT);
 *   Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC);
 * }</pre>
 *
 * <p>One limitation of {@code DexMaker}'s API is that it requires all local
 * variables to be created before any instructions are emitted. Use {@link
 * Code#newLocal newLocal()} to create a new local variable. The method's
 * parameters are exposed as locals using {@link Code#getParameter
 * getParameter()}. For non-static methods the {@code this} pointer is exposed
 * using {@link Code#getThis getThis()}. Here we declare all of the local
 * variables that we'll need for our {@code fib()} method: <pre>   {@code
 *
 *   Local<Integer> i = code.getParameter(0, TypeId.INT);
 *   Local<Integer> constant1 = code.newLocal(TypeId.INT);
 *   Local<Integer> constant2 = code.newLocal(TypeId.INT);
 *   Local<Integer> a = code.newLocal(TypeId.INT);
 *   Local<Integer> b = code.newLocal(TypeId.INT);
 *   Local<Integer> c = code.newLocal(TypeId.INT);
 *   Local<Integer> d = code.newLocal(TypeId.INT);
 *   Local<Integer> result = code.newLocal(TypeId.INT);
 * }</pre>
 *
 * <p>Notice that {@link Local} has a type parameter of {@code Integer}. This is
 * useful for generating code that works with existing types like {@code String}
 * and {@code Integer}, but it can be a hindrance when generating code that
 * involves new types. For this reason you may prefer to use raw types only and
 * add {@code @SuppressWarnings("unsafe")} on your calling code. This will yield
 * the same result but you won't get IDE support if you make a type error.
 *
 * <p>We're ready to start defining our method's instructions. The {@link Code}
 * class catalogs the available instructions and their use. <pre>   {@code
 *
 *   code.loadConstant(constant1, 1);
 *   code.loadConstant(constant2, 2);
 *   Label baseCase = new Label();
 *   code.compare(Comparison.LT, baseCase, i, constant2);
 *   code.op(BinaryOp.SUBTRACT, a, i, constant1);
 *   code.op(BinaryOp.SUBTRACT, b, i, constant2);
 *   code.invokeStatic(fib, c, a);
 *   code.invokeStatic(fib, d, b);
 *   code.op(BinaryOp.ADD, result, c, d);
 *   code.returnValue(result);
 *   code.mark(baseCase);
 *   code.returnValue(i);
 * }</pre>
 *
 * <p>We're done defining the dex file. We just need to write it to the
 * filesystem or load it into the current process. For this example we'll load
 * the generated code into the current process. This only works when the current
 * process is running on Android. We use {@link #generateAndLoad
 * generateAndLoad()} which takes the class loader that will be used as our
 * generated code's parent class loader. It also requires a directory where
 * temporary files can be written. <pre>   {@code
 *
 *   ClassLoader loader = dexMaker.generateAndLoad(
 *       FibonacciMaker.class.getClassLoader(), getDataDirectory());
 * }</pre>
 * Finally we'll use reflection to lookup our generated class on its class
 * loader and invoke its {@code fib()} method: <pre>   {@code
 *
 *   Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci");
 *   Method fibMethod = fibonacciClass.getMethod("fib", int.class);
 *   System.out.println(fibMethod.invoke(null, 8));
 * }</pre>
 */
public final class DexMaker {
    private final Map<TypeId<?>, TypeDeclaration> types = new LinkedHashMap<>();

    // Only warn about not being able to deal with blacklisted methods once. Often this is no
    // problem and warning on every class load is too spammy.
    private static boolean didWarnBlacklistedMethods;
    private static boolean didWarnNonBaseDexClassLoader;

    private ClassLoader sharedClassLoader;
    private DexFile outputDex;
    private boolean markAsTrusted;

    /**
     * Creates a new {@code DexMaker} instance, which can be used to create a
     * single dex file.
     */
    public DexMaker() {
    }

    TypeDeclaration getTypeDeclaration(TypeId<?> type) {
        TypeDeclaration result = types.get(type);
        if (result == null) {
            result = new TypeDeclaration(type);
            types.put(type, result);
        }
        return result;
    }

    /**
     * Declares {@code type}.
     *
     * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
     *     Modifier#FINAL} and {@link Modifier#ABSTRACT}.
     */
    public void declare(TypeId<?> type, String sourceFile, int flags,
                        TypeId<?> supertype, TypeId<?>... interfaces) {
        TypeDeclaration declaration = getTypeDeclaration(type);
        int supportedFlags = Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT
                | AccessFlags.ACC_SYNTHETIC;
        if ((flags & ~supportedFlags) != 0) {
            throw new IllegalArgumentException("Unexpected flag: "
                    + Integer.toHexString(flags));
        }
        if (declaration.declared) {
            throw new IllegalStateException("already declared: " + type);
        }
        declaration.declared = true;
        declaration.flags = flags;
        declaration.supertype = supertype;
        declaration.sourceFile = sourceFile;
        declaration.interfaces = new TypeList(interfaces);
    }

    /**
     * Declares a method or constructor.
     *
     * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
     *     Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
     *     {@link Modifier#FINAL} and {@link Modifier#SYNCHRONIZED}.
     *     <p><strong>Warning:</strong> the {@link Modifier#SYNCHRONIZED} flag
     *     is insufficient to generate a synchronized method. You must also use
     *     {@link Code#monitorEnter} and {@link Code#monitorExit} to acquire
     *     a monitor.
     */
    public Code declare(MethodId<?, ?> method, int flags) {
        TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType);
        if (typeDeclaration.methods.containsKey(method)) {
            throw new IllegalStateException("already declared: " + method);
        }

        int supportedFlags = Modifier.ABSTRACT | Modifier.NATIVE | Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
                | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED
                | AccessFlags.ACC_SYNTHETIC | AccessFlags.ACC_BRIDGE;
        if ((flags & ~supportedFlags) != 0) {
            throw new IllegalArgumentException("Unexpected flag: "
                    + Integer.toHexString(flags));
        }

        // replace the SYNCHRONIZED flag with the DECLARED_SYNCHRONIZED flag
        if ((flags & Modifier.SYNCHRONIZED) != 0) {
            flags = (flags & ~Modifier.SYNCHRONIZED) | AccessFlags.ACC_DECLARED_SYNCHRONIZED;
        }

        if (method.isConstructor() || method.isStaticInitializer()) {
            flags |= ACC_CONSTRUCTOR;
        }

        MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags);
        typeDeclaration.methods.put(method, methodDeclaration);
        return methodDeclaration.code;
    }

    /**
     * Declares a field.
     *
     * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
     *     Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
     *     {@link Modifier#FINAL}, {@link Modifier#VOLATILE}, and {@link
     *     Modifier#TRANSIENT}.
     * @param staticValue a constant representing the initial value for the
     *     static field, possibly null. This must be null if this field is
     *     non-static.
     */
    public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) {
        TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType);
        if (typeDeclaration.fields.containsKey(fieldId)) {
            throw new IllegalStateException("already declared: " + fieldId);
        }

        int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
                | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT
                | AccessFlags.ACC_SYNTHETIC;
        if ((flags & ~supportedFlags) != 0) {
            throw new IllegalArgumentException("Unexpected flag: "
                    + Integer.toHexString(flags));
        }

        if ((flags & Modifier.STATIC) == 0 && staticValue != null) {
            throw new IllegalArgumentException("staticValue is non-null, but field is not static");
        }

        FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue);
        typeDeclaration.fields.put(fieldId, fieldDeclaration);
    }

    /**
     * Generates a dex file and returns its bytes.
     */
    public byte[] generate() {
        if (outputDex == null) {
            DexOptions options = new DexOptions();
            options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
            outputDex = new DexFile(options);
        }

        for (TypeDeclaration typeDeclaration : types.values()) {
            outputDex.add(typeDeclaration.toClassDefItem());
        }

        try {
            return outputDex.toDex(null, false);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // Generate a file name for the jar by taking a checksum of MethodIds and
    // parent class types.
    private String generateFileName() {
        int checksum = 1;

        Set<TypeId<?>> typesKeySet = types.keySet();
        Iterator<TypeId<?>> it = typesKeySet.iterator();
        int[] checksums = new int[typesKeySet.size()];
        int i = 0;

        while (it.hasNext()) {
            TypeId<?> typeId = it.next();
            TypeDeclaration decl = getTypeDeclaration(typeId);
            Set<MethodId> methodSet = decl.methods.keySet();
            if (decl.supertype != null) {
                int sum = 31 * decl.supertype.hashCode() + decl.interfaces.hashCode();
                checksums[i++] = 31 * sum + methodSet.hashCode();
            }
        }
        Arrays.sort(checksums);

        for (int sum : checksums) {
            checksum *= 31;
            checksum += sum;
        }

        return "Generated_" + checksum +".jar";
    }

    /**
     * Set shared class loader to use.
     *
     * <p>If a class wants to call package private methods of another class they need to share a
     * class loader. One common case for this requirement is a mock class wanting to mock package
     * private methods of the original class.
     *
     * <p>If the classLoader is not a subclass of {@code dalvik.system.BaseDexClassLoader} this
     * option is ignored.
     *
     * @param classLoader the class loader the new class should be loaded by
     */
    public void setSharedClassLoader(ClassLoader classLoader) {
        this.sharedClassLoader = classLoader;
    }

    public void markAsTrusted() {
        this.markAsTrusted = true;
    }

    private ClassLoader generateClassLoader(File result, File dexCache, ClassLoader parent) {
        try {
            boolean shareClassLoader = sharedClassLoader != null;

            ClassLoader preferredClassLoader = null;
            if (parent != null) {
                preferredClassLoader = parent;
            } else if (sharedClassLoader != null) {
                preferredClassLoader = sharedClassLoader;
            }

            Class baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");

            if (shareClassLoader) {
                if (!baseDexClassLoaderClass.isAssignableFrom(preferredClassLoader.getClass())) {
                    if (!preferredClassLoader.getClass().getName().equals(
                            "java.lang.BootClassLoader")) {
                        if (!didWarnNonBaseDexClassLoader) {
                            System.err.println("Cannot share classloader as shared classloader '"
                                    + preferredClassLoader + "' is not a subclass of '"
                                    + baseDexClassLoaderClass
                                    + "'");
                            didWarnNonBaseDexClassLoader = true;
                        }
                    }

                    shareClassLoader = false;
                }
            }

            // Try to load the class so that it can call hidden APIs. This is required for spying
            // on system classes as real-methods of these classes might call blacklisted APIs
            if (markAsTrusted) {
                try {
                    if (shareClassLoader) {
                        preferredClassLoader.getClass().getMethod("addDexPath", String.class,
                                Boolean.TYPE).invoke(preferredClassLoader, result.getPath(), true);
                        return preferredClassLoader;
                    } else {
                        return (ClassLoader) baseDexClassLoaderClass
                                .getConstructor(String.class, File.class, String.class,
                                        ClassLoader.class, Boolean.TYPE)
                                .newInstance(result.getPath(), dexCache.getAbsoluteFile(), null,
                                        preferredClassLoader, true);
                    }
                } catch (InvocationTargetException e) {
                    if (e.getCause() instanceof SecurityException) {
                        if (!didWarnBlacklistedMethods) {
                            System.err.println("Cannot allow to call blacklisted super methods. "
                                    + "This might break spying on system classes." + e.getCause());
                            didWarnBlacklistedMethods = true;
                        }
                    } else {
                        throw e;
                    }
                }
            }

            if (shareClassLoader) {
                preferredClassLoader.getClass().getMethod("addDexPath", String.class).invoke(
                        preferredClassLoader, result.getPath());
                return preferredClassLoader;
            } else {
                return (ClassLoader) Class.forName("dalvik.system.DexClassLoader")
                        .getConstructor(String.class, String.class, String.class, ClassLoader.class)
                        .newInstance(result.getPath(), dexCache.getAbsolutePath(), null,
                                preferredClassLoader);
            }
        } catch (ClassNotFoundException e) {
            throw new UnsupportedOperationException("load() requires a Dalvik VM", e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e.getCause());
        } catch (InstantiationException e) {
            throw new AssertionError();
        } catch (NoSuchMethodException e) {
            throw new AssertionError();
        } catch (IllegalAccessException e) {
            throw new AssertionError();
        }
    }

    /**
     * Generates a dex file and loads its types into the current process.
     *
     * <h3>Picking a dex cache directory</h3>
     * The {@code dexCache} should be an application-private directory. If
     * you pass a world-writable directory like {@code /sdcard} a malicious app
     * could inject code into your process. Most applications should use this:
     * <pre>   {@code
     *
     *     File dexCache = getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
     * }</pre>
     * If the {@code dexCache} is null, this method will consult the {@code
     * dexmaker.dexcache} system property. If that exists, it will be used for
     * the dex cache. If it doesn't exist, this method will attempt to guess
     * the application's private data directory as a last resort. If that fails,
     * this method will fail with an unchecked exception. You can avoid the
     * exception by either providing a non-null value or setting the system
     * property.
     *
     * @param parent the parent ClassLoader to be used when loading our
     *     generated types (if set, overrides
     *     {@link #setSharedClassLoader(ClassLoader) shared class loader}.
     * @param dexCache the destination directory where generated and optimized
     *     dex files will be written. If null, this class will try to guess the
     *     application's private data dir.
     */
    public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException {
        if (dexCache == null) {
            String property = System.getProperty("dexmaker.dexcache");
            if (property != null) {
                dexCache = new File(property);
            } else {
                dexCache = new AppDataDirGuesser().guess();
                if (dexCache == null) {
                    throw new IllegalArgumentException("dexcache == null (and no default could be"
                            + " found; consider setting the 'dexmaker.dexcache' system property)");
                }
            }
        }

        File result = new File(dexCache, generateFileName());
        // Check that the file exists. If it does, return a DexClassLoader and skip all
        // the dex bytecode generation.
        if (result.exists()) {
            if (!result.canWrite()) {
                return generateClassLoader(result, dexCache, parent);
            } else {
                // Old writable files should be ignored and re-generated
                result.delete();
            }
        }

        byte[] dex = generate();

        /*
         * This implementation currently dumps the dex to the filesystem. It
         * jars the emitted .dex for the benefit of Gingerbread and earlier
         * devices, which can't load .dex files directly.
         *
         * TODO: load the dex from memory where supported.
         */

        JarOutputStream jarOut =
                new JarOutputStream(new BufferedOutputStream(new FileOutputStream(result)));
        result.setReadOnly();
        try {
            JarEntry entry = new JarEntry(DexFormat.DEX_IN_JAR_NAME);
            entry.setSize(dex.length);
            jarOut.putNextEntry(entry);
            try {
                jarOut.write(dex);
            } finally {
                jarOut.closeEntry();
            }
        } finally {
            jarOut.close();
        }

        return generateClassLoader(result, dexCache, parent);
    }

    DexFile getDexFile() {
        if (outputDex == null) {
            DexOptions options = new DexOptions();
            options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
            outputDex = new DexFile(options);
        }
        return outputDex;
    }

    static class TypeDeclaration {
        private final TypeId<?> type;

        /** declared state */
        private boolean declared;
        private int flags;
        private TypeId<?> supertype;
        private String sourceFile;
        private TypeList interfaces;
        private ClassDefItem classDefItem;

        private final Map<FieldId, FieldDeclaration> fields = new LinkedHashMap<>();
        private final Map<MethodId, MethodDeclaration> methods = new LinkedHashMap<>();

        TypeDeclaration(TypeId<?> type) {
            this.type = type;
        }

        ClassDefItem toClassDefItem() {
            if (!declared) {
                throw new IllegalStateException("Undeclared type " + type + " declares members: "
                        + fields.keySet() + " " + methods.keySet());
            }

            DexOptions dexOptions = new DexOptions();
            dexOptions.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;

            CstType thisType = type.constant;

            if (classDefItem == null) {
                classDefItem = new ClassDefItem(thisType, flags, supertype.constant,
                        interfaces.ropTypes, new CstString(sourceFile));

                for (MethodDeclaration method : methods.values()) {
                    EncodedMethod encoded = method.toEncodedMethod(dexOptions);
                    if (method.isDirect()) {
                        classDefItem.addDirectMethod(encoded);
                    } else {
                        classDefItem.addVirtualMethod(encoded);
                    }
                }
                for (FieldDeclaration field : fields.values()) {
                    EncodedField encoded = field.toEncodedField();
                    if (field.isStatic()) {
                        classDefItem.addStaticField(encoded, Constants.getConstant(field.staticValue));
                    } else {
                        classDefItem.addInstanceField(encoded);
                    }
                }
            }

            return classDefItem;
        }
    }

    static class FieldDeclaration {
        final FieldId<?, ?> fieldId;
        private final int accessFlags;
        private final Object staticValue;

        FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) {
            if ((accessFlags & STATIC) == 0 && staticValue != null) {
                throw new IllegalArgumentException("instance fields may not have a value");
            }
            this.fieldId = fieldId;
            this.accessFlags = accessFlags;
            this.staticValue = staticValue;
        }

        EncodedField toEncodedField() {
            return new EncodedField(fieldId.constant, accessFlags);
        }

        public boolean isStatic() {
            return (accessFlags & STATIC) != 0;
        }
    }

    static class MethodDeclaration {
        final MethodId<?, ?> method;
        private final int flags;
        private final Code code;

        public MethodDeclaration(MethodId<?, ?> method, int flags) {
            this.method = method;
            this.flags = flags;
            this.code = new Code(this);
        }

        boolean isStatic() {
            return (flags & STATIC) != 0;
        }

        boolean isDirect() {
            return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0;
        }

        EncodedMethod toEncodedMethod(DexOptions dexOptions) {
            if((flags & ABSTRACT) != 0 || (flags & NATIVE) != 0){
                return new EncodedMethod(method.constant, flags, null, StdTypeList.EMPTY);
            }

            RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0);
            LocalVariableInfo locals = null;
            DalvCode dalvCode = RopTranslator.translate(
                    ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions);
            return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY);
        }
    }
}