ClassPath.java revision 7e24a9f010eeeff54f7ca0cb589a75cc251fabdd
1package org.jf.dexlib.Code.Analysis;
2
3import org.jf.dexlib.*;
4import static org.jf.dexlib.ClassDataItem.EncodedMethod;
5import static org.jf.dexlib.ClassDataItem.EncodedField;
6
7import org.jf.dexlib.Util.AccessFlags;
8import org.jf.dexlib.Util.ExceptionWithContext;
9import org.jf.dexlib.Util.SparseArray;
10
11import java.io.File;
12import java.util.*;
13
14public class ClassPath {
15    private static ClassPath theClassPath = null;
16
17    private final HashMap<String, ClassDef> classDefs;
18    protected ClassDef javaLangObjectClassDef; //Ljava/lang/Object;
19
20    public static void InitializeClassPath(String[] bootClassPath, DexFile dexFile) {
21        if (theClassPath != null) {
22            throw new ExceptionWithContext("Cannot initialize ClassPath multiple times");
23        }
24
25        theClassPath = new ClassPath();
26        theClassPath.initClassPath(bootClassPath, dexFile);
27    }
28
29    private ClassPath() {
30        classDefs = new HashMap<String, ClassDef>();
31    }
32
33    private void initClassPath(String[] bootClassPath, DexFile dexFile) {
34        if (bootClassPath == null || bootClassPath.length == 0) {
35            throw new ExceptionWithContext("No BOOTCLASSPATH entries were given");
36        }
37
38        for (String bootClassPathEntry: bootClassPath) {
39            loadBootClassPath(bootClassPathEntry);
40        }
41
42        loadDexFile(dexFile);
43
44        for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) {
45            ClassDef classDef = new PrimitiveClassDef(primitiveType);
46            classDefs.put(primitiveType, classDef);
47        }
48    }
49
50    private void loadBootClassPath(String bootClassPathEntry) {
51        File file = new File(bootClassPathEntry);
52
53        if (!file.exists()) {
54            throw new ExceptionWithContext("ClassPath entry \"" + bootClassPathEntry + "\" does not exist.");
55        }
56
57        if (!file.canRead()) {
58            throw new ExceptionWithContext("Cannot read ClassPath entry \"" + bootClassPathEntry + "\".");
59        }
60
61        DexFile dexFile;
62        try {
63            dexFile = new DexFile(file);
64        } catch (Exception ex) {
65            throw ExceptionWithContext.withContext(ex, "Error while reading ClassPath entry \"" +
66                    bootClassPathEntry + "\".");
67        }
68
69        loadDexFile(dexFile);
70    }
71
72    private void loadDexFile(DexFile dexFile) {
73        for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) {
74            //TODO: need to check if the class already exists. (and if so, what to do about it?)
75            ClassDef classDef = new ClassDef(classDefItem);
76            classDefs.put(classDef.getClassType(), classDef);
77
78            if (classDefItem.getClassType().getTypeDescriptor().equals("Ljava/lang/Object;")) {
79                theClassPath.javaLangObjectClassDef = classDef;
80            }
81            /*classDef.dumpVtable();
82            classDef.dumpFields();*/
83        }
84    }
85
86    private static class ClassNotFoundException extends ExceptionWithContext {
87        public ClassNotFoundException(String message) {
88            super(message);
89        }
90    }
91
92    public static ClassDef getClassDef(String classType)  {
93        ClassDef classDef = theClassPath.classDefs.get(classType);
94        if (classDef == null) {
95            //if it's an array class, try to create it
96            if (classType.charAt(0) == '[') {
97                return theClassPath.createArrayClassDef(classType);
98            } else {
99                throw new ClassNotFoundException("Class " + classType + " cannot be found");
100            }
101        }
102        return classDef;
103    }
104
105    public static ClassDef getClassDef(TypeIdItem classType) {
106        return getClassDef(classType.getTypeDescriptor());
107    }
108
109    //256 [ characters
110    private static final String arrayPrefix = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" +
111        "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" +
112        "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[";
113    private static ClassDef getArrayClassDefByElementClassAndDimension(ClassDef classDef, int arrayDimension) {
114        return getClassDef(arrayPrefix.substring(256 - arrayDimension) + classDef.classType);
115    }
116
117    private static ClassDef createArrayClassDef(String arrayClassName) {
118        assert arrayClassName != null;
119        assert arrayClassName.charAt(0) == '[';
120
121        ArrayClassDef arrayClassDef = new ArrayClassDef(arrayClassName);
122        if (arrayClassDef.elementClass == null) {
123            return null;
124        }
125
126        theClassPath.classDefs.put(arrayClassName, arrayClassDef);
127        return arrayClassDef;
128    }
129
130    public static ClassDef getCommonSuperclass(ClassDef class1, ClassDef class2) {
131        if (class1 == class2) {
132            return class1;
133        }
134
135        if (class1 == null) {
136            return class2;
137        }
138
139        if (class2 == null) {
140            return class1;
141        }
142
143        //TODO: do we want to handle primitive types here? I don't think so.. (if not, add assert)
144
145        if (!class1.isInterface && class2.isInterface) {
146            if (class1.implementsInterface(class2)) {
147                return class2;
148            }
149            return theClassPath.javaLangObjectClassDef;
150        }
151
152        if (!class2.isInterface && class1.isInterface) {
153            if (class2.implementsInterface(class1)) {
154                return class1;
155            }
156            return theClassPath.javaLangObjectClassDef;
157        }
158
159        if (class1 instanceof ArrayClassDef && class2 instanceof ArrayClassDef) {
160            return getCommonArraySuperclass((ArrayClassDef)class1, (ArrayClassDef)class2);
161        }
162
163        //we've got two non-array reference types. Find the class depth of each, and then move up the longer one
164        //so that both classes are at the same class depth, and then move each class up until they match
165
166        //we don't strictly need to keep track of the class depth separately, but it's probably slightly faster
167        //to do so, rather than calling getClassDepth() many times
168        int class1Depth = class1.getClassDepth();
169        int class2Depth = class2.getClassDepth();
170
171        while (class1Depth > class2Depth) {
172            class1 = class1.superclass;
173            class1Depth--;
174        }
175
176        while (class2Depth > class1Depth) {
177            class2 = class2.superclass;
178            class2Depth--;
179        }
180
181        while (class1Depth > 0) {
182            if (class1 == class2) {
183                return class1;
184            }
185            class1 = class1.superclass;
186            class1Depth--;
187            class2 = class2.superclass;
188            class2Depth--;
189        }
190
191        return class1;
192    }
193
194    private static ClassDef getCommonArraySuperclass(ArrayClassDef class1, ArrayClassDef class2) {
195        assert class1 != class2;
196
197        //If one of the arrays is a primitive array, then the only option is to return java.lang.Object
198        //TODO: might it be possible to merge something like int[] and short[] into int[]? (I don't think so..)
199        if (class1.elementClass instanceof PrimitiveClassDef || class2.elementClass instanceof PrimitiveClassDef) {
200            return theClassPath.javaLangObjectClassDef;
201        }
202
203        //if the two arrays have the same number of dimensions, then we should return an array class with the
204        //same number of dimensions, for the common superclass of the 2 element classes
205        if (class1.arrayDimensions == class2.arrayDimensions) {
206            ClassDef commonElementClass = getCommonSuperclass(class1.elementClass, class2.elementClass);
207            return getArrayClassDefByElementClassAndDimension(commonElementClass, class1.arrayDimensions);
208        }
209
210        //something like String[][][] and String[][] should be merged to Object[][]
211        //this also holds when the element classes aren't the same (but are both reference types)
212        int dimensions = Math.min(class1.arrayDimensions, class2.arrayDimensions);
213        return getArrayClassDefByElementClassAndDimension(theClassPath.javaLangObjectClassDef, dimensions);
214    }
215
216    public static class ArrayClassDef extends ClassDef {
217        private final ClassDef elementClass;
218        private final int arrayDimensions;
219
220        protected ArrayClassDef(String arrayClassType) {
221            super(arrayClassType, true);
222            assert arrayClassType.charAt(0) == '[';
223
224            int i=0;
225            while (arrayClassType.charAt(i) == '[') i++;
226
227            String elementClassType = arrayClassType.substring(i);
228
229            if (i>256) {
230                throw new ExceptionWithContext("Error while creating array class for element type " + elementClassType +
231                        " with " + i + " dimensions. The maximum number of dimensions is 256");
232            }
233
234            try {
235                elementClass = ClassPath.getClassDef(arrayClassType.substring(i));
236            } catch (ClassNotFoundException ex) {
237                throw ExceptionWithContext.withContext(ex, "Error while creating array class " + arrayClassType);
238            }
239            arrayDimensions = i;
240        }
241
242        /**
243         * Returns the "base" element class of the array.
244         *
245         * For example, for a multi-dimensional array of strings ([[Ljava/lang/String;), this method would return
246         * Ljava/lang/String;
247         * @return
248         */
249        public ClassDef getBaseElementClass() {
250            return elementClass;
251        }
252
253        /**
254         * Returns the "immediate" element class of the array.
255         *
256         * For example, for a multi-dimensional array of stings with 2 dimensions ([[Ljava/lang/String;), this method
257         * would return [Ljava/lang/String;
258         * @return
259         */
260        public ClassDef getImmediateElementClass() {
261            if (arrayDimensions == 1) {
262                return elementClass;
263            }
264            return getArrayClassDefByElementClassAndDimension(elementClass, arrayDimensions - 1);
265        }
266
267        public int getArrayDimensions() {
268            return arrayDimensions;
269        }
270    }
271
272    public static class PrimitiveClassDef extends ClassDef {
273        protected PrimitiveClassDef(String primitiveClassType) {
274            super(primitiveClassType, false);
275        }
276    }
277
278    public static class ClassDef implements Comparable<ClassDef> {
279        private final String classType;
280        private final ClassDef superclass;
281        /**
282         * This is a list of all of the interfaces that a class implements, either directly or indirectly. It includes
283         * all interfaces implemented by the superclass, and all super-interfaces of any implemented interface. The
284         * intention is to make it easier to determine whether the class implements a given interface or not.
285         */
286        private final TreeSet<ClassDef> implementedInterfaces;
287
288        private final boolean isInterface;
289
290        private final int classDepth;
291
292        private final String[] vtable;
293        private final HashMap<String, Integer> virtualMethodLookup;
294
295        private final SparseArray<String> instanceFields;
296        private final HashMap<String, Integer> instanceFieldLookup;
297
298        /**
299         * This constructor is used for the ArrayClassDef and PrimitiveClassDef subclasses
300         * @param classType the class type
301         * @param isArrayType whether this is an array ClassDef or a primitive ClassDef
302         */
303        protected ClassDef(String classType, boolean isArrayType) {
304            if (isArrayType) {
305                assert (classType.charAt(0) == '[');
306                this.classType = classType;
307                this.superclass = ClassPath.theClassPath.javaLangObjectClassDef;
308                implementedInterfaces = new TreeSet<ClassDef>();
309                implementedInterfaces.add(ClassPath.getClassDef("Ljava/lang/Cloneable;"));
310                implementedInterfaces.add(ClassPath.getClassDef("Ljava/io/Serializable;"));
311                isInterface = false;
312
313                vtable = superclass.vtable;
314                virtualMethodLookup = superclass.virtualMethodLookup;
315
316                instanceFields = superclass.instanceFields;
317                instanceFieldLookup = superclass.instanceFieldLookup;
318                classDepth = 1; //1 off from java.lang.Object
319            } else {
320                //primitive type
321                this.classType = classType;
322                this.superclass = null;
323                implementedInterfaces = null;
324                isInterface = false;
325                vtable = null;
326                virtualMethodLookup = null;
327                instanceFields = null;
328                instanceFieldLookup = null;
329                classDepth = 0; //TODO: maybe use -1 to indicate not applicable?
330            }
331        }
332
333        protected ClassDef(ClassDefItem classDefItem)  {
334            classType = classDefItem.getClassType().getTypeDescriptor();
335
336            isInterface = (classDefItem.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
337
338            superclass = loadSuperclass(classDefItem);
339            if (superclass == null) {
340                classDepth = 0;
341            } else {
342                classDepth = superclass.classDepth + 1;
343            }
344
345            implementedInterfaces = loadAllImplementedInterfaces(classDefItem);
346
347            vtable = loadVtable(classDefItem);
348            virtualMethodLookup = new HashMap<String, Integer>((int)Math.ceil(vtable.length / .7f), .75f);
349            for (int i=0; i<vtable.length; i++) {
350                virtualMethodLookup.put(vtable[i], i);
351            }
352
353            instanceFields = loadFields(classDefItem);
354            instanceFieldLookup = new HashMap<String, Integer>((int)Math.ceil(instanceFields.size() / .7f), .75f);
355            for (int i=0; i<instanceFields.size(); i++) {
356                instanceFieldLookup.put(instanceFields.get(i), i);
357            }
358        }
359
360        public String getClassType() {
361            return classType;
362        }
363
364        public ClassDef getSuperclass() {
365            return superclass;
366        }
367
368        public int getClassDepth() {
369            return classDepth;
370        }
371
372        public boolean isInterface() {
373            return this.isInterface;
374        }
375
376        public boolean extendsClass(ClassDef superclassDef) {
377            if (superclassDef == null) {
378                return false;
379            }
380
381            if (this == superclassDef) {
382                return true;
383            }
384
385            int superclassDepth = superclassDef.classDepth;
386            ClassDef ancestor = this;
387            while (ancestor.classDepth > superclassDepth) {
388                ancestor = ancestor.getSuperclass();
389            }
390
391            return ancestor == superclassDef;
392        }
393
394        /**
395         * Returns true if this class implements the given interface. This searches the interfaces that this class
396         * directly implements, any interface implemented by this class's superclasses, and any super-interface of
397         * any of these interfaces.
398         * @param interfaceDef the interface
399         * @return true if this class implements the given interface
400         */
401        public boolean implementsInterface(ClassDef interfaceDef) {
402            return implementedInterfaces.contains(interfaceDef);
403        }
404
405        public boolean hasVirtualMethod(String method) {
406            return virtualMethodLookup.containsKey(method);
407        }
408
409        //TODO: GROT
410        /*public void dumpVtable() {
411            System.out.println(classType + " methods:");
412            int i=0;
413            for (String method: vtable) {
414                System.out.println(i + ":\t" + method);
415                i++;
416            }
417        }*/
418
419        //TODO: GROT
420        /*public void dumpFields() {
421            System.out.println(classType + " fields:");
422            for (int i=0; i<instanceFields.size(); i++) {
423                int fieldOffset = instanceFields.keyAt(i);
424                System.out.println(fieldOffset + ":\t" + instanceFields.valueAt(i));
425            }
426        }*/
427
428        private void swap(byte[] fieldTypes, String[] fields, int position1, int position2) {
429            byte tempType = fieldTypes[position1];
430            fieldTypes[position1] = fieldTypes[position2];
431            fieldTypes[position2] = tempType;
432
433            String tempField = fields[position1];
434            fields[position1] = fields[position2];
435            fields[position2] = tempField;
436        }
437
438        private ClassDef loadSuperclass(ClassDefItem classDefItem) {
439            if (classDefItem.getClassType().getTypeDescriptor().equals("Ljava/lang/Object;")) {
440                if (classDefItem.getSuperclass() != null) {
441                    throw new ExceptionWithContext("Invalid superclass " +
442                            classDefItem.getSuperclass().getTypeDescriptor() + " for Ljava/lang/Object;. " +
443                            "The Object class cannot have a superclass");
444                }
445                return null;
446            } else {
447                TypeIdItem superClass = classDefItem.getSuperclass();
448                if (superClass == null) {
449                    throw new ExceptionWithContext(classDefItem.getClassType().getTypeDescriptor() +
450                            " has no superclass");
451                }
452
453                ClassDef superclass = ClassPath.getClassDef(superClass.getTypeDescriptor());
454
455                if (!isInterface && superclass.isInterface) {
456                    throw new ValidationException("Class " + classType + " has the interface " + superclass.classType +
457                            " as its superclass");
458                }
459                if (isInterface && !superclass.isInterface && superclass !=
460                        ClassPath.theClassPath.javaLangObjectClassDef) {
461                    throw new ValidationException("Interface " + classType + " has the non-interface class " +
462                            superclass.classType + " as its superclass");
463                }
464
465                return superclass;
466            }
467        }
468
469        private TreeSet<ClassDef> loadAllImplementedInterfaces(ClassDefItem classDefItem) {
470            assert classType != null;
471            assert classType.equals("Ljava/lang/Object;") || superclass != null;
472            assert classDefItem != null;
473
474            TreeSet<ClassDef> implementedInterfaceSet = new TreeSet<ClassDef>();
475
476            if (superclass != null) {
477                for (ClassDef interfaceDef: superclass.implementedInterfaces) {
478                    implementedInterfaceSet.add(interfaceDef);
479                }
480            }
481
482            TypeListItem interfaces = classDefItem.getInterfaces();
483            if (interfaces != null) {
484                for (TypeIdItem interfaceType: interfaces.getTypes()) {
485                    ClassDef interfaceDef = ClassPath.getClassDef(interfaceType.getTypeDescriptor());
486                    assert interfaceDef.isInterface;
487                    implementedInterfaceSet.add(interfaceDef);
488
489                    interfaceDef = interfaceDef.getSuperclass();
490                    while (!interfaceDef.getClassType().equals("Ljava/lang/Object;")) {
491                        assert interfaceDef.isInterface;
492                        implementedInterfaceSet.add(interfaceDef);
493                        interfaceDef = interfaceDef.getSuperclass();
494                    }
495                }
496            }
497
498            return implementedInterfaceSet;
499        }
500
501        private String[] loadVtable(ClassDefItem classDefItem) {
502            //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
503            List<String> virtualMethodList = new LinkedList<String>();
504            //use a temp hash table, so that we can construct the final lookup with an appropriate
505            //capacity, based on the number of virtual methods
506            HashMap<String, Integer> tempVirtualMethodLookup = new HashMap<String, Integer>();
507
508            //copy the virtual methods from the superclass
509            int methodIndex = 0;
510            if (superclass != null) {
511                for (String method: superclass.vtable) {
512                    virtualMethodList.add(method);
513                    tempVirtualMethodLookup.put(method, methodIndex++);
514                }
515
516                assert superclass.instanceFields != null;
517            }
518
519
520            //iterate over the virtual methods in the current class, and only add them when we don't already have the
521            //method (i.e. if it was implemented by the superclass)
522            ClassDataItem classDataItem = classDefItem.getClassData();
523            if (classDataItem != null) {
524                EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods();
525                if (virtualMethods != null) {
526                    for (EncodedMethod virtualMethod: virtualMethods) {
527                        String methodString = virtualMethod.method.getMethodString();
528                        if (tempVirtualMethodLookup.get(methodString) == null) {
529                            virtualMethodList.add(methodString);
530                        }
531                    }
532                }
533            }
534
535            String[] vtable = new String[virtualMethodList.size()];
536            for (int i=0; i<virtualMethodList.size(); i++) {
537                vtable[i] = virtualMethodList.get(i);
538            }
539
540            return vtable;
541        }
542
543        private SparseArray<String> loadFields(ClassDefItem classDefItem) {
544            //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
545            //arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
546
547            final byte REFERENCE = 0;
548            final byte WIDE = 1;
549            final byte OTHER = 2;
550
551            ClassDataItem classDataItem = classDefItem.getClassData();
552
553            String[] fields = null;
554            //the "type" for each field in fields. 0=reference,1=wide,2=other
555            byte[] fieldTypes = null;
556
557            if (classDataItem != null) {
558                EncodedField[] encodedFields = classDataItem.getInstanceFields();
559                if (encodedFields != null) {
560                    fields = new String[encodedFields.length];
561                    fieldTypes = new byte[encodedFields.length];
562
563                    for (int i=0; i<encodedFields.length; i++) {
564                        EncodedField encodedField = encodedFields[i];
565                        String fieldType = encodedField.field.getFieldType().getTypeDescriptor();
566                        String field = String.format("%s:%s", encodedField.field.getFieldName().getStringValue(),
567                                fieldType);
568                        fieldTypes[i] = getFieldType(field);
569                        fields[i] = field;
570                    }
571                }
572            }
573
574            if (fields == null) {
575                fields = new String[0];
576                fieldTypes = new byte[0];
577            }
578
579            //The first operation is to move all of the reference fields to the front. To do this, find the first
580            //non-reference field, then find the last reference field, swap them and repeat
581            int back = fields.length - 1;
582            int front;
583            for (front = 0; front<fields.length; front++) {
584                if (fieldTypes[front] != REFERENCE) {
585                    while (back > front) {
586                        if (fieldTypes[back] == REFERENCE) {
587                            swap(fieldTypes, fields, front, back--);
588                            break;
589                        }
590                        back--;
591                    }
592                }
593
594                if (fieldTypes[front] != REFERENCE) {
595                    break;
596                }
597            }
598
599            //next, we need to group all the wide fields after the reference fields. But the wide fields have to be
600            //8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
601            //is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
602            //If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
603            if (front < fields.length && (front % 2) != 0) {
604                if (fieldTypes[front] == WIDE) {
605                    //we need to swap in a 32-bit field, so the wide fields will be correctly aligned
606                    back = fields.length - 1;
607                    while (back > front) {
608                        if (fieldTypes[back] == OTHER) {
609                            swap(fieldTypes, fields, front++, back);
610                            break;
611                        }
612                        back--;
613                    }
614                } else {
615                    //there's already a 32-bit field here that we can use
616                    front++;
617                }
618            }
619
620            //do the swap thing for wide fields
621            back = fields.length - 1;
622            for (; front<fields.length; front++) {
623                if (fieldTypes[front] != WIDE) {
624                    while (back > front) {
625                        if (fieldTypes[back] == WIDE) {
626                            swap(fieldTypes, fields, front, back--);
627                            break;
628                        }
629                        back--;
630                    }
631                }
632
633                if (fieldTypes[front] != WIDE) {
634                    break;
635                }
636            }
637
638            int superFieldCount = 0;
639            if (superclass != null) {
640                superclass.instanceFields.size();
641            }
642
643            //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
644            int totalFieldCount = superFieldCount + fields.length;
645            SparseArray<String> instanceFields = new SparseArray<String>(totalFieldCount);
646
647            int fieldOffset;
648
649            if (superclass != null && superFieldCount > 0) {
650                for (int i=0; i<superFieldCount; i++) {
651                    instanceFields.append(superclass.instanceFields.keyAt(i), superclass.instanceFields.valueAt(i));
652                }
653
654                fieldOffset = instanceFields.keyAt(superFieldCount-1);
655
656                String lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1);
657                assert lastSuperField.indexOf(':') >= 0;
658                assert lastSuperField.indexOf(':') < superFieldCount-1; //the ':' shouldn't be the last char
659                char fieldType = lastSuperField.charAt(lastSuperField.indexOf(':') + 1);
660                if (fieldType == 'J' || fieldType == 'D') {
661                    fieldOffset += 8;
662                } else {
663                    fieldOffset += 4;
664                }
665            } else {
666                //the field values start at 8 bytes into the DataObject dalvik structure
667                fieldOffset = 8;
668            }
669
670            boolean gotDouble = false;
671            for (int i=0; i<fields.length; i++) {
672                String field = fields[i];
673
674                //add padding to align the wide fields, if needed
675                if (fieldTypes[i] == WIDE && !gotDouble) {
676                    if (!gotDouble) {
677                        if (fieldOffset % 8 != 0) {
678                            assert fieldOffset % 8 == 4;
679                            fieldOffset += 4;
680                        }
681                        gotDouble = true;
682                    }
683                }
684
685                instanceFields.append(fieldOffset, field);
686                if (fieldTypes[i] == WIDE) {
687                    fieldOffset += 8;
688                } else {
689                    fieldOffset += 4;
690                }
691            }
692            return instanceFields;
693        }
694
695        private byte getFieldType(String field) {
696            int sepIndex = field.indexOf(':');
697
698            //we could use sepIndex >= field.length()-1 instead, but that's too easy to mistake for an off-by-one error
699            if (sepIndex < 0 || sepIndex == field.length()-1 || sepIndex >= field.length()) {
700                assert false;
701                throw new ExceptionWithContext("Invalid field format: " + field);
702            }
703            switch (field.charAt(sepIndex+1)) {
704                case '[':
705                case 'L':
706                    return 0; //REFERENCE
707                case 'J':
708                case 'D':
709                    return 1; //WIDE
710                default:
711                    return 2; //OTHER
712            }
713        }
714
715        @Override
716        public boolean equals(Object o) {
717            if (this == o) return true;
718            if (!(o instanceof ClassDef)) return false;
719
720            ClassDef classDef = (ClassDef) o;
721
722            return classType.equals(classDef.classType);
723        }
724
725        @Override
726        public int hashCode() {
727            return classType.hashCode();
728        }
729
730        public int compareTo(ClassDef classDef) {
731            return classType.compareTo(classDef.classType);
732        }
733    }
734}
735