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 29 package org.jf.dexlib.Code.Analysis; 30 31 import org.jf.dexlib.*; 32 import org.jf.dexlib.Util.AccessFlags; 33 import org.jf.dexlib.Util.ExceptionWithContext; 34 import org.jf.dexlib.Util.SparseArray; 35 36 import javax.annotation.Nonnull; 37 import javax.annotation.Nullable; 38 import java.io.File; 39 import java.util.*; 40 import java.util.regex.Matcher; 41 import java.util.regex.Pattern; 42 43 import static org.jf.dexlib.ClassDataItem.EncodedField; 44 import static org.jf.dexlib.ClassDataItem.EncodedMethod; 45 46 public 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