1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2010 Ben Gruver (JesusFreke)
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29package org.jf.dexlib.Code.Analysis;
30
31import org.jf.dexlib.*;
32import org.jf.dexlib.Util.AccessFlags;
33import org.jf.dexlib.Util.ExceptionWithContext;
34import org.jf.dexlib.Util.SparseArray;
35
36import javax.annotation.Nonnull;
37import javax.annotation.Nullable;
38import java.io.File;
39import java.util.*;
40import java.util.regex.Matcher;
41import java.util.regex.Pattern;
42
43import static org.jf.dexlib.ClassDataItem.EncodedField;
44import static org.jf.dexlib.ClassDataItem.EncodedMethod;
45
46public class ClassPath {
47    private static ClassPath theClassPath = null;
48
49    /**
50     * The current version of dalvik in master(AOSP) has a slight change to the way the
51     * virtual tables are computed. This should be set to true to use the new logic.
52     * TODO: set this based on api level, once it's present in a released version of Android
53     */
54    private boolean checkPackagePrivateAccess;
55
56    private final HashMap<String, ClassDef> classDefs;
57    protected ClassDef javaLangObjectClassDef; //cached ClassDef for Ljava/lang/Object;
58
59    // Contains the classes that we haven't loaded yet
60    private HashMap<String, UnresolvedClassInfo> unloadedClasses;
61
62    private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
63
64    /**
65     * Initialize the class path using the dependencies from an odex file
66     * @param classPathDirs The directories to search for boot class path files
67     * @param extraBootClassPathEntries any extra entries that should be added after the entries that are read
68     * from the odex file
69     * @param dexFilePath The path of the dex file (used for error reporting purposes only)
70     * @param dexFile The DexFile to load - it must represents an odex file
71     */
72    public static void InitializeClassPathFromOdex(String[] classPathDirs, String[] extraBootClassPathEntries,
73                                                   String dexFilePath, DexFile dexFile,
74                                                   boolean checkPackagePrivateAccess) {
75        if (!dexFile.isOdex()) {
76            throw new ExceptionWithContext("Cannot use InitialiazeClassPathFromOdex with a non-odex DexFile");
77        }
78
79        if (theClassPath != null) {
80            throw new ExceptionWithContext("Cannot initialize ClassPath multiple times");
81        }
82
83        OdexDependencies odexDependencies = dexFile.getOdexDependencies();
84
85        String[] bootClassPath = new String[odexDependencies.getDependencyCount()];
86        for (int i=0; i<bootClassPath.length; i++) {
87            String dependency = odexDependencies.getDependency(i);
88
89            if (dependency.endsWith(".odex")) {
90                int slashIndex = dependency.lastIndexOf("/");
91
92                if (slashIndex != -1) {
93                    dependency = dependency.substring(slashIndex+1);
94                }
95            } else if (dependency.endsWith("@classes.dex")) {
96                Matcher m = dalvikCacheOdexPattern.matcher(dependency);
97
98                if (!m.find()) {
99                    throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", dependency));
100                }
101
102                dependency = m.group(1);
103            } else {
104                throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", dependency));
105            }
106
107            bootClassPath[i] = dependency;
108        }
109
110        theClassPath = new ClassPath();
111        theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile,
112                checkPackagePrivateAccess);
113    }
114
115    /**
116     * Initialize the class path using the given boot class path entries
117     * @param classPathDirs The directories to search for boot class path files
118     * @param bootClassPath A list of the boot class path entries to search for and load
119     * @param dexFilePath The path of the dex file (used for error reporting purposes only)
120     * @param dexFile the DexFile to load
121     * classes
122     */
123    public static void InitializeClassPath(String[] classPathDirs, String[] bootClassPath,
124                                           String[] extraBootClassPathEntries, String dexFilePath, DexFile dexFile,
125                                           boolean checkPackagePrivateAccess) {
126        if (theClassPath != null) {
127            throw new ExceptionWithContext("Cannot initialize ClassPath multiple times");
128        }
129
130        theClassPath = new ClassPath();
131        theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile,
132                checkPackagePrivateAccess);
133    }
134
135    private ClassPath() {
136        classDefs = new HashMap<String, ClassDef>();
137    }
138
139    private void initClassPath(String[] classPathDirs, String[] bootClassPath, String[] extraBootClassPathEntries,
140                               String dexFilePath, DexFile dexFile, boolean checkPackagePrivateAccess) {
141        this.checkPackagePrivateAccess = checkPackagePrivateAccess;
142        unloadedClasses = new LinkedHashMap<String, UnresolvedClassInfo>();
143
144        if (bootClassPath != null) {
145            for (String bootClassPathEntry: bootClassPath) {
146                loadBootClassPath(classPathDirs, bootClassPathEntry);
147            }
148        }
149
150        if (extraBootClassPathEntries != null) {
151            for (String bootClassPathEntry: extraBootClassPathEntries) {
152                loadBootClassPath(classPathDirs, bootClassPathEntry);
153            }
154        }
155
156        if (dexFile != null) {
157            loadDexFile(dexFilePath, dexFile);
158        }
159
160        javaLangObjectClassDef = getClassDef("Ljava/lang/Object;", false);
161
162        for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) {
163            ClassDef classDef = new PrimitiveClassDef(primitiveType);
164            classDefs.put(primitiveType, classDef);
165        }
166    }
167
168    private void loadBootClassPath(String[] classPathDirs, String bootClassPathEntry) {
169        for (String classPathDir: classPathDirs) {
170            File file = null;
171            DexFile dexFile = null;
172
173            int extIndex = bootClassPathEntry.lastIndexOf(".");
174
175            String baseEntry;
176            if (extIndex == -1) {
177                baseEntry = bootClassPathEntry;
178            } else {
179                baseEntry = bootClassPathEntry.substring(0, extIndex);
180            }
181
182            for (String ext: new String[]{"", ".odex", ".jar", ".apk", ".zip"}) {
183                if (ext.length() == 0) {
184                    file = new File(classPathDir, bootClassPathEntry);
185                } else {
186                    file = new File(classPathDir, baseEntry + ext);
187                }
188
189                if (file.exists()) {
190                    if (!file.canRead()) {
191                        System.err.println(String.format("warning: cannot open %s for reading. Will continue " +
192                                "looking.", file.getPath()));
193                        continue;
194                    }
195
196                    try {
197                        dexFile = new DexFile(file, false, true);
198                    } catch (DexFile.NoClassesDexException ex) {
199                        continue;
200                    } catch (Exception ex) {
201                        throw ExceptionWithContext.withContext(ex, "Error while reading boot class path entry \"" +
202                        bootClassPathEntry + "\".");
203                    }
204                }
205            }
206            if (dexFile == null) {
207                continue;
208            }
209
210            try {
211                loadDexFile(file.getPath(), dexFile);
212            } catch (Exception ex) {
213                throw ExceptionWithContext.withContext(ex,
214                        String.format("Error while loading boot classpath entry %s", bootClassPathEntry));
215            }
216            return;
217        }
218        throw new ExceptionWithContext(String.format("Cannot locate boot class path file %s", bootClassPathEntry));
219    }
220
221    private void loadDexFile(String dexFilePath, DexFile dexFile) {
222        for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) {
223            try {
224                UnresolvedClassInfo unresolvedClassInfo = new UnresolvedClassInfo(dexFilePath, classDefItem);
225
226                if (!unloadedClasses.containsKey(unresolvedClassInfo.classType)) {
227                    unloadedClasses.put(unresolvedClassInfo.classType, unresolvedClassInfo);
228                }
229            } catch (Exception ex) {
230                throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s",
231                        classDefItem.getClassType().getTypeDescriptor()));
232            }
233        }
234    }
235
236    /**
237     * This method loads the given class (and any dependent classes, as needed), removing them from unloadedClasses
238     * @param classType the class to load
239     * @return the newly loaded ClassDef object for the given class, or null if the class cannot be found
240     */
241    @Nullable
242    private static ClassDef loadClassDef(String classType) {
243        ClassDef classDef = null;
244
245        UnresolvedClassInfo classInfo = theClassPath.unloadedClasses.get(classType);
246        if (classInfo == null) {
247            return null;
248        }
249
250        try {
251            classDef = new ClassDef(classInfo);
252            theClassPath.classDefs.put(classDef.classType, classDef);
253        } catch (Exception ex) {
254            throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s from file %s",
255                    classInfo.classType, classInfo.dexFilePath));
256        }
257        theClassPath.unloadedClasses.remove(classType);
258
259        return classDef;
260    }
261
262    @Nonnull
263    public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef)  {
264        ClassDef classDef = theClassPath.classDefs.get(classType);
265        if (classDef == null) {
266            //if it's an array class, try to create it
267            if (classType.charAt(0) == '[') {
268                return theClassPath.createArrayClassDef(classType);
269            } else {
270                try {
271                    classDef = loadClassDef(classType);
272                    if (classDef == null) {
273                        throw new ExceptionWithContext(
274                                String.format("Could not find definition for class %s", classType));
275                    }
276                } catch (Exception ex) {
277                    RuntimeException exWithContext = ExceptionWithContext.withContext(ex,
278                            String.format("Error while loading ClassPath class %s", classType));
279                    if (createUnresolvedClassDef) {
280                        //TODO: add warning message
281                        return theClassPath.createUnresolvedClassDef(classType);
282                    } else {
283                        throw exWithContext;
284                    }
285                }
286            }
287        }
288        return classDef;
289    }
290
291    public static ClassDef getClassDef(String classType) {
292        return getClassDef(classType, true);
293    }
294
295    public static ClassDef getClassDef(TypeIdItem classType) {
296        return getClassDef(classType.getTypeDescriptor());
297    }
298
299    public static ClassDef getClassDef(TypeIdItem classType, boolean creatUnresolvedClassDef) {
300        return getClassDef(classType.getTypeDescriptor(), creatUnresolvedClassDef);
301    }
302
303    //256 [ characters
304    private static final String arrayPrefix = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" +
305        "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" +
306        "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[";
307    private static ClassDef getArrayClassDefByElementClassAndDimension(ClassDef classDef, int arrayDimension) {
308        return getClassDef(arrayPrefix.substring(256 - arrayDimension) + classDef.classType);
309    }
310
311    private static ClassDef unresolvedObjectClassDef = null;
312    public static ClassDef getUnresolvedObjectClassDef() {
313        if (unresolvedObjectClassDef == null) {
314            unresolvedObjectClassDef = new UnresolvedClassDef("Ljava/lang/Object;");
315        }
316        return unresolvedObjectClassDef;
317    }
318
319    private ClassDef createUnresolvedClassDef(String classType)  {
320        assert classType.charAt(0) == 'L';
321
322        UnresolvedClassDef unresolvedClassDef = new UnresolvedClassDef(classType);
323        classDefs.put(classType, unresolvedClassDef);
324        return unresolvedClassDef;
325    }
326
327    private ClassDef createArrayClassDef(String arrayClassName) {
328        assert arrayClassName != null;
329        assert arrayClassName.charAt(0) == '[';
330
331        ArrayClassDef arrayClassDef = new ArrayClassDef(arrayClassName);
332        if (arrayClassDef.elementClass == null) {
333            return null;
334        }
335
336        classDefs.put(arrayClassName, arrayClassDef);
337        return arrayClassDef;
338    }
339
340    public static ClassDef getCommonSuperclass(ClassDef class1, ClassDef class2) {
341        if (class1 == class2) {
342            return class1;
343        }
344
345        if (class1 == null) {
346            return class2;
347        }
348
349        if (class2 == null) {
350            return class1;
351        }
352
353        //TODO: do we want to handle primitive types here? I don't think so.. (if not, add assert)
354
355        if (class2.isInterface) {
356            if (class1.implementsInterface(class2)) {
357                return class2;
358            }
359            return theClassPath.javaLangObjectClassDef;
360        }
361
362        if (class1.isInterface) {
363            if (class2.implementsInterface(class1)) {
364                return class1;
365            }
366            return theClassPath.javaLangObjectClassDef;
367        }
368
369        if (class1 instanceof ArrayClassDef && class2 instanceof ArrayClassDef) {
370            return getCommonArraySuperclass((ArrayClassDef)class1, (ArrayClassDef)class2);
371        }
372
373        //we've got two non-array reference types. Find the class depth of each, and then move up the longer one
374        //so that both classes are at the same class depth, and then move each class up until they match
375
376        //we don't strictly need to keep track of the class depth separately, but it's probably slightly faster
377        //to do so, rather than calling getClassDepth() many times
378        int class1Depth = class1.getClassDepth();
379        int class2Depth = class2.getClassDepth();
380
381        while (class1Depth > class2Depth) {
382            class1 = class1.superclass;
383            class1Depth--;
384        }
385
386        while (class2Depth > class1Depth) {
387            class2 = class2.superclass;
388            class2Depth--;
389        }
390
391        while (class1Depth > 0) {
392            if (class1 == class2) {
393                return class1;
394            }
395            class1 = class1.superclass;
396            class1Depth--;
397            class2 = class2.superclass;
398            class2Depth--;
399        }
400
401        return class1;
402    }
403
404    private static ClassDef getCommonArraySuperclass(ArrayClassDef class1, ArrayClassDef class2) {
405        assert class1 != class2;
406
407        //If one of the arrays is a primitive array, then the only option is to return java.lang.Object
408        //TODO: might it be possible to merge something like int[] and short[] into int[]? (I don't think so..)
409        if (class1.elementClass instanceof PrimitiveClassDef || class2.elementClass instanceof PrimitiveClassDef) {
410            return theClassPath.javaLangObjectClassDef;
411        }
412
413        //if the two arrays have the same number of dimensions, then we should return an array class with the
414        //same number of dimensions, for the common superclass of the 2 element classes
415        if (class1.arrayDimensions == class2.arrayDimensions) {
416            ClassDef commonElementClass;
417            if (class1.elementClass instanceof UnresolvedClassDef ||
418                class2.elementClass instanceof UnresolvedClassDef) {
419                commonElementClass = ClassPath.getUnresolvedObjectClassDef();
420            } else {
421                commonElementClass = getCommonSuperclass(class1.elementClass, class2.elementClass);
422            }
423            return getArrayClassDefByElementClassAndDimension(commonElementClass, class1.arrayDimensions);
424        }
425
426        //something like String[][][] and String[][] should be merged to Object[][]
427        //this also holds when the element classes aren't the same (but are both reference types)
428        int dimensions = Math.min(class1.arrayDimensions, class2.arrayDimensions);
429        return getArrayClassDefByElementClassAndDimension(theClassPath.javaLangObjectClassDef, dimensions);
430    }
431
432    public static class ArrayClassDef extends ClassDef {
433        private final ClassDef elementClass;
434        private final int arrayDimensions;
435
436        protected ArrayClassDef(String arrayClassType) {
437            super(arrayClassType, ClassDef.ArrayClassDef);
438            assert arrayClassType.charAt(0) == '[';
439
440            int i=0;
441            while (arrayClassType.charAt(i) == '[') i++;
442
443            String elementClassType = arrayClassType.substring(i);
444
445            if (i>256) {
446                throw new ExceptionWithContext("Error while creating array class for element type " + elementClassType +
447                        " with " + i + " dimensions. The maximum number of dimensions is 256");
448            }
449
450            try {
451                elementClass = ClassPath.getClassDef(arrayClassType.substring(i));
452            } catch (Exception ex) {
453                throw ExceptionWithContext.withContext(ex, "Error while creating array class " + arrayClassType);
454            }
455            arrayDimensions = i;
456        }
457
458        /**
459         * Returns the "base" element class of the array.
460         *
461         * For example, for a multi-dimensional array of strings ([[Ljava/lang/String;), this method would return
462         * Ljava/lang/String;
463         * @return the "base" element class of the array
464         */
465        public ClassDef getBaseElementClass() {
466            return elementClass;
467        }
468
469        /**
470         * Returns the "immediate" element class of the array.
471         *
472         * For example, for a multi-dimensional array of stings with 2 dimensions ([[Ljava/lang/String;), this method
473         * would return [Ljava/lang/String;
474         * @return the immediate element class of the array
475         */
476        public ClassDef getImmediateElementClass() {
477            if (arrayDimensions == 1) {
478                return elementClass;
479            }
480            return getArrayClassDefByElementClassAndDimension(elementClass, arrayDimensions - 1);
481        }
482
483        public int getArrayDimensions() {
484            return arrayDimensions;
485        }
486
487        @Override
488        public boolean extendsClass(ClassDef superclassDef) {
489            if (!(superclassDef instanceof ArrayClassDef)) {
490                if (superclassDef == ClassPath.theClassPath.javaLangObjectClassDef) {
491                    return true;
492                } else if (superclassDef.isInterface) {
493                    return this.implementsInterface(superclassDef);
494                }
495                return false;
496            }
497
498            ArrayClassDef arraySuperclassDef = (ArrayClassDef)superclassDef;
499            if (this.arrayDimensions == arraySuperclassDef.arrayDimensions) {
500                ClassDef baseElementClass = arraySuperclassDef.getBaseElementClass();
501
502                if (baseElementClass.isInterface) {
503                    return true;
504                }
505
506                return baseElementClass.extendsClass(arraySuperclassDef.getBaseElementClass());
507            } else if (this.arrayDimensions > arraySuperclassDef.arrayDimensions) {
508                ClassDef baseElementClass = arraySuperclassDef.getBaseElementClass();
509                if (baseElementClass.isInterface) {
510                    return true;
511                }
512
513                if (baseElementClass == ClassPath.theClassPath.javaLangObjectClassDef) {
514                    return true;
515                }
516                return false;
517            }
518            return false;
519        }
520    }
521
522    public static class PrimitiveClassDef extends ClassDef {
523        protected PrimitiveClassDef(String primitiveClassType) {
524            super(primitiveClassType, ClassDef.PrimitiveClassDef);
525            assert primitiveClassType.charAt(0) != 'L' && primitiveClassType.charAt(0) != '[';
526        }
527    }
528
529    public static class UnresolvedClassDef extends ClassDef {
530        protected UnresolvedClassDef(String unresolvedClassDef) {
531            super(unresolvedClassDef, ClassDef.UnresolvedClassDef);
532            assert unresolvedClassDef.charAt(0) == 'L';
533        }
534
535        protected ValidationException unresolvedValidationException() {
536            return new ValidationException(String.format("class %s cannot be resolved.", this.getClassType()));
537        }
538
539        public ClassDef getSuperclass() {
540            return theClassPath.javaLangObjectClassDef;
541        }
542
543        public int getClassDepth() {
544            throw unresolvedValidationException();
545        }
546
547        public boolean isInterface() {
548            throw unresolvedValidationException();
549        }
550
551         public boolean extendsClass(ClassDef superclassDef) {
552            if (superclassDef != theClassPath.javaLangObjectClassDef && superclassDef != this) {
553                throw unresolvedValidationException();
554            }
555            return true;
556        }
557
558        public boolean implementsInterface(ClassDef interfaceDef) {
559            throw unresolvedValidationException();
560        }
561
562        public boolean hasVirtualMethod(String method) {
563            if (!super.hasVirtualMethod(method)) {
564                throw unresolvedValidationException();
565            }
566            return true;
567        }
568    }
569
570    public static class FieldDef {
571        public final String definingClass;
572        public final String name;
573        public final String type;
574
575        public FieldDef(String definingClass, String name, String type) {
576            this.definingClass = definingClass;
577            this.name = name;
578            this.type = type;
579        }
580    }
581
582    public static class ClassDef implements Comparable<ClassDef> {
583        private final String classType;
584        private final ClassDef superclass;
585        /**
586         * This is a list of all of the interfaces that a class implements, either directly or indirectly. It includes
587         * all interfaces implemented by the superclass, and all super-interfaces of any implemented interface. The
588         * intention is to make it easier to determine whether the class implements a given interface or not.
589         */
590        private final TreeSet<ClassDef> implementedInterfaces;
591
592        private final boolean isInterface;
593
594        private final int classDepth;
595
596        // classes can only be public or package-private. Internally, any private/protected inner class is actually
597        // package-private.
598        private final boolean isPublic;
599
600        private final VirtualMethod[] vtable;
601
602        //this maps a method name of the form method(III)Ljava/lang/String; to an integer
603        //If the value is non-negative, it is a vtable index
604        //If it is -1, it is a non-static direct method,
605        //If it is -2, it is a static method
606        private final HashMap<String, Integer> methodLookup;
607
608        private final SparseArray<FieldDef> instanceFields;
609
610        public final static int ArrayClassDef = 0;
611        public final static int PrimitiveClassDef = 1;
612        public final static int UnresolvedClassDef = 2;
613
614        private final static int DirectMethod = -1;
615        private final static int StaticMethod = -2;
616
617        /**
618         * The following fields are used only during the initial loading of classes, and are set to null afterwards
619         * TODO: free these
620         */
621
622        //This is only the virtual methods that this class declares itself.
623        private VirtualMethod[] virtualMethods;
624        //this is a list of all the interfaces that the class implements directory, or any super interfaces of those
625        //interfaces. It is generated in such a way that it is ordered in the same way as dalvik's ClassObject.iftable,
626        private LinkedHashMap<String, ClassDef> interfaceTable;
627
628        /**
629         * This constructor is used for the ArrayClassDef, PrimitiveClassDef and UnresolvedClassDef subclasses
630         * @param classType the class type
631         * @param classFlavor one of ArrayClassDef, PrimitiveClassDef or UnresolvedClassDef
632         */
633        protected ClassDef(String classType, int classFlavor) {
634            if (classFlavor == ArrayClassDef) {
635                assert classType.charAt(0) == '[';
636                this.classType = classType;
637                this.superclass = ClassPath.theClassPath.javaLangObjectClassDef;
638                implementedInterfaces = new TreeSet<ClassDef>();
639                implementedInterfaces.add(ClassPath.getClassDef("Ljava/lang/Cloneable;"));
640                implementedInterfaces.add(ClassPath.getClassDef("Ljava/io/Serializable;"));
641                isInterface = false;
642                isPublic = true;
643
644                vtable = superclass.vtable;
645                methodLookup = superclass.methodLookup;
646
647                instanceFields = superclass.instanceFields;
648                classDepth = 1; //1 off from java.lang.Object
649
650                virtualMethods = null;
651                interfaceTable = null;
652            } else if (classFlavor == PrimitiveClassDef) {
653                //primitive type
654                assert classType.charAt(0) != '[' && classType.charAt(0) != 'L';
655
656                this.classType = classType;
657                this.superclass = null;
658                implementedInterfaces = null;
659                isInterface = false;
660                isPublic = true;
661                vtable = null;
662                methodLookup = null;
663                instanceFields = null;
664                classDepth = 0; //TODO: maybe use -1 to indicate not applicable?
665
666                virtualMethods = null;
667                interfaceTable = null;
668            } else /*if (classFlavor == UnresolvedClassDef)*/ {
669                assert classType.charAt(0) == 'L';
670                this.classType = classType;
671                this.superclass = ClassPath.getClassDef("Ljava/lang/Object;");
672                implementedInterfaces = new TreeSet<ClassDef>();
673                isInterface = false;
674                isPublic = true;
675
676                vtable = superclass.vtable;
677                methodLookup = superclass.methodLookup;
678
679                instanceFields = superclass.instanceFields;
680                classDepth = 1; //1 off from java.lang.Object
681
682                virtualMethods = null;
683                interfaceTable = null;
684            }
685        }
686
687        protected ClassDef(UnresolvedClassInfo classInfo)  {
688            classType = classInfo.classType;
689            isPublic = classInfo.isPublic;
690            isInterface = classInfo.isInterface;
691
692            superclass = loadSuperclass(classInfo);
693            if (superclass == null) {
694                classDepth = 0;
695            } else {
696                classDepth = superclass.classDepth + 1;
697            }
698
699            implementedInterfaces = loadAllImplementedInterfaces(classInfo);
700
701            //TODO: we can probably get away with only creating the interface table for interface types
702            interfaceTable = loadInterfaceTable(classInfo);
703            virtualMethods = classInfo.virtualMethods;
704            vtable = loadVtable(classInfo);
705
706            int directMethodCount = 0;
707            if (classInfo.directMethods != null) {
708                directMethodCount = classInfo.directMethods.length;
709            }
710            methodLookup = new HashMap<String, Integer>((int)Math.ceil(((vtable.length + directMethodCount)/ .7f)), .75f);
711            for (int i=0; i<vtable.length; i++) {
712                methodLookup.put(vtable[i].method, i);
713            }
714            if (directMethodCount > 0) {
715                for (int i=0; i<classInfo.directMethods.length; i++) {
716                    if (classInfo.staticMethods[i]) {
717                        methodLookup.put(classInfo.directMethods[i], StaticMethod);
718                    } else {
719                        methodLookup.put(classInfo.directMethods[i], DirectMethod);
720                    }
721                }
722            }
723
724            instanceFields = loadFields(classInfo);
725        }
726
727        public String getClassType() {
728            return classType;
729        }
730
731        public ClassDef getSuperclass() {
732            return superclass;
733        }
734
735        public int getClassDepth() {
736            return classDepth;
737        }
738
739        public boolean isInterface() {
740            return this.isInterface;
741        }
742
743        public boolean isPublic() {
744            return this.isPublic;
745        }
746
747        public boolean extendsClass(ClassDef superclassDef) {
748            if (superclassDef == null) {
749                return false;
750            }
751
752            if (this == superclassDef) {
753                return true;
754            }
755
756            if (superclassDef instanceof UnresolvedClassDef) {
757                throw ((UnresolvedClassDef)superclassDef).unresolvedValidationException();
758            }
759
760            int superclassDepth = superclassDef.classDepth;
761            ClassDef ancestor = this;
762            while (ancestor.classDepth > superclassDepth) {
763                ancestor = ancestor.getSuperclass();
764            }
765
766            return ancestor == superclassDef;
767        }
768
769        /**
770         * Returns true if this class implements the given interface. This searches the interfaces that this class
771         * directly implements, any interface implemented by this class's superclasses, and any super-interface of
772         * any of these interfaces.
773         * @param interfaceDef the interface
774         * @return true if this class implements the given interface
775         */
776        public boolean implementsInterface(ClassDef interfaceDef) {
777            assert !(interfaceDef instanceof UnresolvedClassDef);
778            return implementedInterfaces.contains(interfaceDef);
779        }
780
781        public boolean hasVirtualMethod(String method) {
782            Integer val = methodLookup.get(method);
783            if (val == null || val < 0) {
784                return false;
785            }
786            return true;
787        }
788
789        public int getMethodType(String method) {
790            Integer val = methodLookup.get(method);
791            if (val == null) {
792                return -1;
793            }
794            if (val >= 0) {
795                return DeodexUtil.Virtual;
796            }
797            if (val == DirectMethod) {
798                return DeodexUtil.Direct;
799            }
800            if (val == StaticMethod) {
801                return DeodexUtil.Static;
802            }
803            throw new RuntimeException("Unexpected method type");
804        }
805
806        public FieldDef getInstanceField(int fieldOffset) {
807            return this.instanceFields.get(fieldOffset, null);
808        }
809
810        public String getVirtualMethod(int vtableIndex) {
811            if (vtableIndex < 0 || vtableIndex >= vtable.length) {
812                return null;
813            }
814            return this.vtable[vtableIndex].method;
815        }
816
817        private void swap(byte[] fieldTypes, FieldDef[] fields, int position1, int position2) {
818            byte tempType = fieldTypes[position1];
819            fieldTypes[position1] = fieldTypes[position2];
820            fieldTypes[position2] = tempType;
821
822            FieldDef tempField = fields[position1];
823            fields[position1] = fields[position2];
824            fields[position2] = tempField;
825        }
826
827        private ClassDef loadSuperclass(UnresolvedClassInfo classInfo) {
828            if (classInfo.classType.equals("Ljava/lang/Object;")) {
829                if (classInfo.superclassType != null) {
830                    throw new ExceptionWithContext("Invalid superclass " +
831                            classInfo.superclassType + " for Ljava/lang/Object;. " +
832                            "The Object class cannot have a superclass");
833                }
834                return null;
835            } else {
836                String superclassType = classInfo.superclassType;
837                if (superclassType == null) {
838                    throw new ExceptionWithContext(classInfo.classType + " has no superclass");
839                }
840
841                ClassDef superclass;
842                try {
843                    superclass = ClassPath.getClassDef(superclassType);
844                } catch (Exception ex) {
845                    throw ExceptionWithContext.withContext(ex,
846                            String.format("Could not find superclass %s", superclassType));
847                }
848
849                if (!isInterface && superclass.isInterface) {
850                    throw new ValidationException("Class " + classType + " has the interface " + superclass.classType +
851                            " as its superclass");
852                }
853                if (isInterface && !superclass.isInterface && superclass !=
854                        ClassPath.theClassPath.javaLangObjectClassDef) {
855                    throw new ValidationException("Interface " + classType + " has the non-interface class " +
856                            superclass.classType + " as its superclass");
857                }
858
859                return superclass;
860            }
861        }
862
863        private TreeSet<ClassDef> loadAllImplementedInterfaces(UnresolvedClassInfo classInfo) {
864            assert classType != null;
865            assert classType.equals("Ljava/lang/Object;") || superclass != null;
866            assert classInfo != null;
867
868            TreeSet<ClassDef> implementedInterfaceSet = new TreeSet<ClassDef>();
869
870            if (superclass != null) {
871                for (ClassDef interfaceDef: superclass.implementedInterfaces) {
872                    implementedInterfaceSet.add(interfaceDef);
873                }
874            }
875
876
877            if (classInfo.interfaces != null) {
878                for (String interfaceType: classInfo.interfaces) {
879                    ClassDef interfaceDef;
880                    try {
881                        interfaceDef = ClassPath.getClassDef(interfaceType);
882                    } catch (Exception ex) {
883                        throw ExceptionWithContext.withContext(ex,
884                                String.format("Could not find interface %s", interfaceType));
885                    }
886                    assert interfaceDef.isInterface();
887                    implementedInterfaceSet.add(interfaceDef);
888
889                    interfaceDef = interfaceDef.getSuperclass();
890                    while (!interfaceDef.getClassType().equals("Ljava/lang/Object;")) {
891                        assert interfaceDef.isInterface();
892                        implementedInterfaceSet.add(interfaceDef);
893                        interfaceDef = interfaceDef.getSuperclass();
894                    }
895                }
896            }
897
898            return implementedInterfaceSet;
899        }
900
901        private LinkedHashMap<String, ClassDef> loadInterfaceTable(UnresolvedClassInfo classInfo) {
902            if (classInfo.interfaces == null) {
903                return null;
904            }
905
906            LinkedHashMap<String, ClassDef> interfaceTable = new LinkedHashMap<String, ClassDef>();
907
908            for (String interfaceType: classInfo.interfaces) {
909                if (!interfaceTable.containsKey(interfaceType)) {
910                    ClassDef interfaceDef;
911                    try {
912                        interfaceDef = ClassPath.getClassDef(interfaceType);
913                    } catch (Exception ex) {
914                        throw ExceptionWithContext.withContext(ex,
915                                String.format("Could not find interface %s", interfaceType));
916                    }
917                    interfaceTable.put(interfaceType, interfaceDef);
918
919                    if (interfaceDef.interfaceTable != null) {
920                        for (ClassDef superInterface: interfaceDef.interfaceTable.values()) {
921                            if (!interfaceTable.containsKey(superInterface.classType)) {
922                                interfaceTable.put(superInterface.classType, superInterface);
923                            }
924                        }
925                    }
926                }
927            }
928
929            return interfaceTable;
930        }
931
932        //TODO: check the case when we have a package private method that overrides an interface method
933        private VirtualMethod[] loadVtable(UnresolvedClassInfo classInfo) {
934            //TODO: it might be useful to keep track of which class's implementation is used for each virtual method. In other words, associate the implementing class type with each vtable entry
935            List<VirtualMethod> virtualMethodList = new LinkedList<VirtualMethod>();
936
937            //copy the virtual methods from the superclass
938            int methodIndex = 0;
939            if (superclass != null) {
940                for (int i=0; i<superclass.vtable.length; i++) {
941                    virtualMethodList.add(superclass.vtable[i]);
942                }
943
944                assert superclass.instanceFields != null;
945            }
946
947
948            //iterate over the virtual methods in the current class, and only add them when we don't already have the
949            //method (i.e. if it was implemented by the superclass)
950            if (!this.isInterface) {
951                if (classInfo.virtualMethods != null) {
952                    addToVtable(classInfo.virtualMethods, virtualMethodList);
953                }
954
955                if (interfaceTable != null) {
956                    for (ClassDef interfaceDef: interfaceTable.values()) {
957                        if (interfaceDef.virtualMethods == null) {
958                            continue;
959                        }
960
961                        addToVtable(interfaceDef.virtualMethods, virtualMethodList);
962                    }
963                }
964            }
965
966            VirtualMethod[] vtable = new VirtualMethod[virtualMethodList.size()];
967            for (int i=0; i<virtualMethodList.size(); i++) {
968                vtable[i] = virtualMethodList.get(i);
969            }
970
971            return vtable;
972        }
973
974        private void addToVtable(VirtualMethod[] localMethods, List<VirtualMethod> vtable) {
975            for (VirtualMethod virtualMethod: localMethods) {
976                boolean found = false;
977                for (int i=0; i<vtable.size(); i++) {
978                    VirtualMethod superMethod = vtable.get(i);
979                    if (superMethod.method.equals(virtualMethod.method)) {
980                        if (!ClassPath.theClassPath.checkPackagePrivateAccess || this.canAccess(superMethod)) {
981                            found = true;
982                            vtable.set(i, virtualMethod);
983                            break;
984                        }
985                    }
986                }
987                if (!found) {
988                    vtable.add(virtualMethod);
989                }
990            }
991        }
992
993        private boolean canAccess(VirtualMethod virtualMethod) {
994            if (!virtualMethod.isPackagePrivate) {
995                return true;
996            }
997
998            String otherPackage = getPackage(virtualMethod.containingClass);
999            String ourPackage = getPackage(this.classType);
1000            return otherPackage.equals(ourPackage);
1001        }
1002
1003        private String getPackage(String classType) {
1004            int lastSlash = classType.lastIndexOf('/');
1005            if (lastSlash < 0) {
1006                return "";
1007            }
1008            return classType.substring(1, lastSlash);
1009        }
1010
1011        private int getNextFieldOffset() {
1012            if (instanceFields == null || instanceFields.size() == 0) {
1013                return 8;
1014            }
1015
1016            int lastItemIndex = instanceFields.size()-1;
1017            int fieldOffset = instanceFields.keyAt(lastItemIndex);
1018            FieldDef lastField = instanceFields.valueAt(lastItemIndex);
1019
1020            switch (lastField.type.charAt(0)) {
1021                case 'J':
1022                case 'D':
1023                    return fieldOffset + 8;
1024                default:
1025                    return fieldOffset + 4;
1026            }
1027        }
1028
1029        private SparseArray<FieldDef> loadFields(UnresolvedClassInfo classInfo) {
1030            //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
1031            //arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
1032            //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
1033
1034            final byte REFERENCE = 0;
1035            final byte WIDE = 1;
1036            final byte OTHER = 2;
1037
1038            FieldDef[] fields = null;
1039            //the "type" for each field in fields. 0=reference,1=wide,2=other
1040            byte[] fieldTypes = null;
1041
1042            if (classInfo.instanceFields != null) {
1043                fields = new FieldDef[classInfo.instanceFields.length];
1044                fieldTypes = new byte[fields.length];
1045
1046                for (int i=0; i<fields.length; i++) {
1047                    String[] fieldInfo = classInfo.instanceFields[i];
1048
1049                    String fieldName = fieldInfo[0];
1050                    String fieldType = fieldInfo[1];
1051
1052                    fieldTypes[i] = getFieldType(fieldType);
1053                    fields[i] = new FieldDef(classInfo.classType, fieldName, fieldType);
1054                }
1055            }
1056
1057            if (fields == null) {
1058                fields = new FieldDef[0];
1059                fieldTypes = new byte[0];
1060            }
1061
1062            //The first operation is to move all of the reference fields to the front. To do this, find the first
1063            //non-reference field, then find the last reference field, swap them and repeat
1064            int back = fields.length - 1;
1065            int front;
1066            for (front = 0; front<fields.length; front++) {
1067                if (fieldTypes[front] != REFERENCE) {
1068                    while (back > front) {
1069                        if (fieldTypes[back] == REFERENCE) {
1070                            swap(fieldTypes, fields, front, back--);
1071                            break;
1072                        }
1073                        back--;
1074                    }
1075                }
1076
1077                if (fieldTypes[front] != REFERENCE) {
1078                    break;
1079                }
1080            }
1081
1082
1083            int startFieldOffset = 8;
1084            if (this.superclass != null) {
1085                startFieldOffset = this.superclass.getNextFieldOffset();
1086            }
1087
1088            int fieldIndexMod;
1089            if ((startFieldOffset % 8) == 0) {
1090                fieldIndexMod = 0;
1091            } else {
1092                fieldIndexMod = 1;
1093            }
1094
1095            //next, we need to group all the wide fields after the reference fields. But the wide fields have to be
1096            //8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
1097            //is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
1098            //If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
1099            if (front < fields.length && (front % 2) != fieldIndexMod) {
1100                if (fieldTypes[front] == WIDE) {
1101                    //we need to swap in a 32-bit field, so the wide fields will be correctly aligned
1102                    back = fields.length - 1;
1103                    while (back > front) {
1104                        if (fieldTypes[back] == OTHER) {
1105                            swap(fieldTypes, fields, front++, back);
1106                            break;
1107                        }
1108                        back--;
1109                    }
1110                } else {
1111                    //there's already a 32-bit field here that we can use
1112                    front++;
1113                }
1114            }
1115
1116            //do the swap thing for wide fields
1117            back = fields.length - 1;
1118            for (; front<fields.length; front++) {
1119                if (fieldTypes[front] != WIDE) {
1120                    while (back > front) {
1121                        if (fieldTypes[back] == WIDE) {
1122                            swap(fieldTypes, fields, front, back--);
1123                            break;
1124                        }
1125                        back--;
1126                    }
1127                }
1128
1129                if (fieldTypes[front] != WIDE) {
1130                    break;
1131                }
1132            }
1133
1134            int superFieldCount = 0;
1135            if (superclass != null) {
1136                superFieldCount = superclass.instanceFields.size();
1137            }
1138
1139            //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
1140            int totalFieldCount = superFieldCount + fields.length;
1141            SparseArray<FieldDef> instanceFields = new SparseArray<FieldDef>(totalFieldCount);
1142
1143            int fieldOffset;
1144
1145            if (superclass != null && superFieldCount > 0) {
1146                for (int i=0; i<superFieldCount; i++) {
1147                    instanceFields.append(superclass.instanceFields.keyAt(i), superclass.instanceFields.valueAt(i));
1148                }
1149
1150                fieldOffset = instanceFields.keyAt(superFieldCount-1);
1151
1152                FieldDef lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1);
1153                char fieldType = lastSuperField.type.charAt(0);
1154                if (fieldType == 'J' || fieldType == 'D') {
1155                    fieldOffset += 8;
1156                } else {
1157                    fieldOffset += 4;
1158                }
1159            } else {
1160                //the field values start at 8 bytes into the DataObject dalvik structure
1161                fieldOffset = 8;
1162            }
1163
1164            boolean gotDouble = false;
1165            for (int i=0; i<fields.length; i++) {
1166                FieldDef field = fields[i];
1167
1168                //add padding to align the wide fields, if needed
1169                if (fieldTypes[i] == WIDE && !gotDouble) {
1170                    if (!gotDouble) {
1171                        if (fieldOffset % 8 != 0) {
1172                            assert fieldOffset % 8 == 4;
1173                            fieldOffset += 4;
1174                        }
1175                        gotDouble = true;
1176                    }
1177                }
1178
1179                instanceFields.append(fieldOffset, field);
1180                if (fieldTypes[i] == WIDE) {
1181                    fieldOffset += 8;
1182                } else {
1183                    fieldOffset += 4;
1184                }
1185            }
1186            return instanceFields;
1187        }
1188
1189        private byte getFieldType(String fieldType) {
1190            switch (fieldType.charAt(0)) {
1191                case '[':
1192                case 'L':
1193                    return 0; //REFERENCE
1194                case 'J':
1195                case 'D':
1196                    return 1; //WIDE
1197                default:
1198                    return 2; //OTHER
1199            }
1200        }
1201
1202        @Override
1203        public boolean equals(Object o) {
1204            if (this == o) return true;
1205            if (!(o instanceof ClassDef)) return false;
1206
1207            ClassDef classDef = (ClassDef) o;
1208
1209            return classType.equals(classDef.classType);
1210        }
1211
1212        @Override
1213        public int hashCode() {
1214            return classType.hashCode();
1215        }
1216
1217        public int compareTo(ClassDef classDef) {
1218            return classType.compareTo(classDef.classType);
1219        }
1220    }
1221
1222    private static class VirtualMethod {
1223        public String containingClass;
1224        public String method;
1225        public boolean isPackagePrivate;
1226    }
1227
1228    /**
1229     * This aggregates the basic information about a class in an easy-to-use format, without requiring references
1230     * to any other class.
1231     */
1232    private static class UnresolvedClassInfo {
1233        public final String dexFilePath;
1234        public final String classType;
1235        public final boolean isPublic;
1236        public final boolean isInterface;
1237        public final String superclassType;
1238        public final String[] interfaces;
1239        public final boolean[] staticMethods;
1240        public final String[] directMethods;
1241        public final VirtualMethod[] virtualMethods;
1242        public final String[][] instanceFields;
1243
1244        public UnresolvedClassInfo(String dexFilePath, ClassDefItem classDefItem) {
1245            this.dexFilePath = dexFilePath;
1246
1247            classType = classDefItem.getClassType().getTypeDescriptor();
1248
1249            isPublic = (classDefItem.getAccessFlags() & AccessFlags.PUBLIC.getValue()) != 0;
1250            isInterface = (classDefItem.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
1251
1252            TypeIdItem superclassType = classDefItem.getSuperclass();
1253            if (superclassType == null) {
1254                this.superclassType = null;
1255            } else {
1256                this.superclassType = superclassType.getTypeDescriptor();
1257            }
1258
1259            interfaces = loadInterfaces(classDefItem);
1260
1261            ClassDataItem classDataItem = classDefItem.getClassData();
1262            if (classDataItem != null) {
1263                boolean[][] _staticMethods = new boolean[1][];
1264                directMethods = loadDirectMethods(classDataItem, _staticMethods);
1265                staticMethods = _staticMethods[0];
1266                virtualMethods = loadVirtualMethods(classDataItem);
1267                instanceFields = loadInstanceFields(classDataItem);
1268            } else {
1269                staticMethods = null;
1270                directMethods = null;
1271                virtualMethods = null;
1272                instanceFields = null;
1273            }
1274        }
1275
1276        private String[] loadInterfaces(ClassDefItem classDefItem) {
1277            TypeListItem typeList = classDefItem.getInterfaces();
1278            if (typeList != null) {
1279                List<TypeIdItem> types = typeList.getTypes();
1280                if (types != null && types.size() > 0) {
1281                    String[] interfaces = new String[types.size()];
1282                    for (int i=0; i<interfaces.length; i++) {
1283                        interfaces[i] = types.get(i).getTypeDescriptor();
1284                    }
1285                    return interfaces;
1286                }
1287            }
1288            return null;
1289        }
1290
1291        private String[] loadDirectMethods(ClassDataItem classDataItem, boolean[][] _staticMethods) {
1292            List<EncodedMethod> encodedMethods = classDataItem.getDirectMethods();
1293            if (encodedMethods.size() > 0) {
1294                boolean[] staticMethods = new boolean[encodedMethods.size()];
1295                String[] directMethods = new String[encodedMethods.size()];
1296
1297                for (int i=0; i<encodedMethods.size(); i++) {
1298                    EncodedMethod encodedMethod = encodedMethods.get(i);
1299
1300                    if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) != 0) {
1301                        staticMethods[i] = true;
1302                    }
1303                    directMethods[i] = encodedMethod.method.getShortMethodString();
1304                }
1305                _staticMethods[0] = staticMethods;
1306                return directMethods;
1307            }
1308            return null;
1309        }
1310
1311        private VirtualMethod[] loadVirtualMethods(ClassDataItem classDataItem) {
1312            List<EncodedMethod> encodedMethods = classDataItem.getVirtualMethods();
1313            if (encodedMethods.size() > 0) {
1314                VirtualMethod[] virtualMethods = new VirtualMethod[encodedMethods.size()];
1315                for (int i=0; i<encodedMethods.size(); i++) {
1316                    virtualMethods[i] = new VirtualMethod();
1317                    EncodedMethod encodedMethod = encodedMethods.get(i);
1318
1319                    virtualMethods[i].isPackagePrivate = methodIsPackagePrivate(encodedMethod.accessFlags);
1320                    virtualMethods[i].containingClass = classDataItem.getParentType().getTypeDescriptor();
1321                    virtualMethods[i].method = encodedMethods.get(i).method.getShortMethodString();
1322                }
1323                return virtualMethods;
1324            }
1325            return null;
1326        }
1327
1328        private static boolean methodIsPackagePrivate(int accessFlags) {
1329            return (accessFlags & (AccessFlags.PRIVATE.getValue() |
1330                                   AccessFlags.PROTECTED.getValue() |
1331                                   AccessFlags.PUBLIC.getValue())) == 0;
1332        }
1333
1334        private String[][] loadInstanceFields(ClassDataItem classDataItem) {
1335            List<EncodedField> encodedFields = classDataItem.getInstanceFields();
1336            if (encodedFields.size() > 0) {
1337                String[][] instanceFields = new String[encodedFields.size()][2];
1338                for (int i=0; i<encodedFields.size(); i++) {
1339                    EncodedField encodedField = encodedFields.get(i);
1340                    instanceFields[i][0] = encodedField.field.getFieldName().getStringValue();
1341                    instanceFields[i][1] = encodedField.field.getFieldType().getTypeDescriptor();
1342                }
1343                return instanceFields;
1344            }
1345            return null;
1346        }
1347    }
1348}
1349