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