ClassProto.java revision 490a8b2cd01ab7e441ba7017ee74f4778c97abec
1/* 2 * Copyright 2013, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32package org.jf.dexlib2.analysis; 33 34import com.google.common.base.Predicate; 35import com.google.common.base.Predicates; 36import com.google.common.collect.FluentIterable; 37import com.google.common.collect.Iterables; 38import com.google.common.collect.Lists; 39import com.google.common.collect.Maps; 40import org.jf.dexlib2.AccessFlags; 41import org.jf.dexlib2.analysis.util.TypeProtoUtils; 42import org.jf.dexlib2.iface.ClassDef; 43import org.jf.dexlib2.iface.Field; 44import org.jf.dexlib2.iface.Method; 45import org.jf.dexlib2.iface.reference.FieldReference; 46import org.jf.dexlib2.iface.reference.MethodReference; 47import org.jf.dexlib2.util.FieldUtil; 48import org.jf.util.ExceptionWithContext; 49import org.jf.util.SparseArray; 50 51import javax.annotation.Nonnull; 52import javax.annotation.Nullable; 53import java.util.*; 54 55/** 56 * A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields 57 * and their offsets. 58 */ 59public class ClassProto implements TypeProto { 60 @Nonnull protected final ClassPath classPath; 61 @Nonnull protected final String type; 62 @Nullable protected ClassDef classDef; 63 @Nullable protected LinkedHashMap<String, ClassDef> interfaces; 64 @Nullable protected Method[] vtable; 65 @Nullable protected SparseArray<FieldReference> instanceFields; 66 protected boolean interfacesFullyResolved = true; 67 68 public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) { 69 if (type.charAt(0) != 'L') { 70 throw new ExceptionWithContext("Cannot construct ClassProto for non reference type: %s", type); 71 } 72 this.classPath = classPath; 73 this.type = type; 74 } 75 76 @Override public String toString() { return type; } 77 @Nonnull @Override public ClassPath getClassPath() { return classPath; } 78 @Nonnull @Override public String getType() { return type; } 79 80 @Nonnull 81 public ClassDef getClassDef() { 82 if (classDef == null) { 83 classDef = classPath.getClassDef(type); 84 } 85 return classDef; 86 } 87 88 @Nonnull 89 Method[] getVtable() { 90 if (vtable == null) { 91 vtable = loadVtable(); 92 } 93 return vtable; 94 } 95 96 @Nonnull 97 SparseArray<FieldReference> getInstanceFields() { 98 if (instanceFields == null) { 99 instanceFields = loadFields(); 100 } 101 return instanceFields; 102 } 103 104 /** 105 * Returns true if this class is an interface. 106 * 107 * If this class is not defined, then this will throw an UnresolvedClassException 108 * 109 * @return True if this class is an interface 110 */ 111 public boolean isInterface() { 112 ClassDef classDef = getClassDef(); 113 return (classDef.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0; 114 } 115 116 @Nonnull 117 protected LinkedHashMap<String, ClassDef> getInterfaces() { 118 if (interfaces != null) { 119 return interfaces; 120 } 121 122 interfaces = Maps.newLinkedHashMap(); 123 124 try { 125 for (String interfaceType: getClassDef().getInterfaces()) { 126 if (!interfaces.containsKey(interfaceType)) { 127 ClassDef interfaceDef; 128 try { 129 interfaceDef = classPath.getClassDef(interfaceType); 130 interfaces.put(interfaceType, interfaceDef); 131 } catch (UnresolvedClassException ex) { 132 interfaces.put(interfaceType, null); 133 interfacesFullyResolved = false; 134 } 135 136 ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType); 137 for (String superInterface: interfaceProto.getInterfaces().keySet()) { 138 if (!interfaces.containsKey(superInterface)) { 139 interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface)); 140 } 141 } 142 if (!interfaceProto.interfacesFullyResolved) { 143 interfacesFullyResolved = false; 144 } 145 } 146 } 147 } catch (UnresolvedClassException ex) { 148 interfacesFullyResolved = false; 149 } 150 151 // now add self and super class interfaces, required for common super class lookup 152 // we don't really need ClassDef's for that, so let's just use null 153 154 if (isInterface() && !interfaces.containsKey(getType())) { 155 interfaces.put(getType(), null); 156 } 157 158 try { 159 String superclass = getSuperclass(); 160 if (superclass != null) { 161 ClassProto superclassProto = (ClassProto) classPath.getClass(superclass); 162 for (String superclassInterface: superclassProto.getInterfaces().keySet()) { 163 if (!interfaces.containsKey(superclassInterface)) { 164 interfaces.put(superclassInterface, null); 165 } 166 } 167 if (!superclassProto.interfacesFullyResolved) { 168 interfacesFullyResolved = false; 169 } 170 } 171 } catch (UnresolvedClassException ex) { 172 interfacesFullyResolved = false; 173 } 174 175 return interfaces; 176 } 177 178 @Nonnull 179 protected Iterable<ClassDef> getDirectInterfaces() { 180 if (!interfacesFullyResolved) { 181 throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType()); 182 } 183 184 return FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull()); 185 } 186 187 /** 188 * Checks if this class implements the given interface. 189 * 190 * If the interfaces of this class cannot be fully resolved then this 191 * method will either return true or throw an UnresolvedClassException 192 * 193 * @param iface The interface to check for 194 * @return true if this class implements the given interface, otherwise false 195 */ 196 @Override 197 public boolean implementsInterface(@Nonnull String iface) { 198 if (getInterfaces().containsKey(iface)) { 199 return true; 200 } 201 if (!interfacesFullyResolved) { 202 throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType()); 203 } 204 return false; 205 } 206 207 @Nullable @Override 208 public String getSuperclass() { 209 return getClassDef().getSuperclass(); 210 } 211 212 /** 213 * This is a helper method for getCommonSuperclass 214 * 215 * It checks if this class is an interface, and if so, if other implements it. 216 * 217 * If this class is undefined, we go ahead and check if it is listed in other's interfaces. If not, we throw an 218 * UndefinedClassException 219 * 220 * If the interfaces of other cannot be fully resolved, we check the interfaces that can be resolved. If not found, 221 * we throw an UndefinedClassException 222 * 223 * @param other The class to check the interfaces of 224 * @return true if this class is an interface (or is undefined) other implements this class 225 * 226 */ 227 private boolean checkInterface(@Nonnull ClassProto other) { 228 boolean isResolved = true; 229 boolean isInterface = true; 230 try { 231 isInterface = isInterface(); 232 } catch (UnresolvedClassException ex) { 233 isResolved = false; 234 // if we don't know if this class is an interface or not, 235 // we can still try to call other.implementsInterface(this) 236 } 237 if (isInterface) { 238 try { 239 if (other.implementsInterface(getType())) { 240 return true; 241 } 242 } catch (UnresolvedClassException ex) { 243 // There are 2 possibilities here, depending on whether we were able to resolve this class. 244 // 1. If this class is resolved, then we know it is an interface class. The other class either 245 // isn't defined, or its interfaces couldn't be fully resolved. 246 // In this case, we throw an UnresolvedClassException 247 // 2. If this class is not resolved, we had tried to call implementsInterface anyway. We don't 248 // know for sure if this class is an interface or not. We return false, and let processing 249 // continue in getCommonSuperclass 250 if (isResolved) { 251 throw ex; 252 } 253 } 254 } 255 return false; 256 } 257 258 @Override @Nonnull 259 public TypeProto getCommonSuperclass(@Nonnull TypeProto other) { 260 // use the other type's more specific implementation 261 if (!(other instanceof ClassProto)) { 262 return other.getCommonSuperclass(this); 263 } 264 265 if (this == other || getType().equals(other.getType())) { 266 return this; 267 } 268 269 if (this.getType().equals("Ljava/lang/Object;")) { 270 return this; 271 } 272 273 if (other.getType().equals("Ljava/lang/Object;")) { 274 return other; 275 } 276 277 boolean gotException = false; 278 try { 279 if (checkInterface((ClassProto)other)) { 280 return this; 281 } 282 } catch (UnresolvedClassException ex) { 283 gotException = true; 284 } 285 286 try { 287 if (((ClassProto)other).checkInterface(this)) { 288 return other; 289 } 290 } catch (UnresolvedClassException ex) { 291 gotException = true; 292 } 293 if (gotException) { 294 return classPath.getUnknownClass(); 295 } 296 297 List<TypeProto> thisChain = Lists.<TypeProto>newArrayList(this); 298 Iterables.addAll(thisChain, TypeProtoUtils.getSuperclassChain(this)); 299 300 List<TypeProto> otherChain = Lists.newArrayList(other); 301 Iterables.addAll(otherChain, TypeProtoUtils.getSuperclassChain(other)); 302 303 // reverse them, so that the first entry is either Ljava/lang/Object; or Ujava/lang/Object; 304 thisChain = Lists.reverse(thisChain); 305 otherChain = Lists.reverse(otherChain); 306 307 for (int i=Math.min(thisChain.size(), otherChain.size())-1; i>=0; i--) { 308 TypeProto typeProto = thisChain.get(i); 309 if (typeProto.getType().equals(otherChain.get(i).getType())) { 310 return typeProto; 311 } 312 } 313 314 return classPath.getUnknownClass(); 315 } 316 317 @Override 318 @Nullable 319 public FieldReference getFieldByOffset(int fieldOffset) { 320 if (getInstanceFields().size() == 0) { 321 return null; 322 } 323 return getInstanceFields().get(fieldOffset); 324 } 325 326 @Override 327 @Nullable 328 public MethodReference getMethodByVtableIndex(int vtableIndex) { 329 if (vtableIndex < 0 || vtableIndex >= getVtable().length) { 330 return null; 331 } 332 return getVtable()[vtableIndex]; 333 } 334 335 @Nonnull 336 private SparseArray<FieldReference> loadFields() { 337 //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to 338 //arrange fields, so that we end up with the same field offsets (which is needed for deodexing). 339 //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets() 340 341 final byte REFERENCE = 0; 342 final byte WIDE = 1; 343 final byte OTHER = 2; 344 345 ArrayList<Field> loadedFields = getInstanceFields(getClassDef()); 346 Field[] fields = new Field[loadedFields.size()]; 347 //the "type" for each field in fields. 0=reference,1=wide,2=other 348 byte[] fieldTypes = new byte[fields.length]; 349 for (int i=0;i<fields.length;i++) { 350 fields[i] = loadedFields.get(i); 351 fieldTypes[i] = getFieldType(fields[i].getType()); 352 } 353 354 //The first operation is to move all of the reference fields to the front. To do this, find the first 355 //non-reference field, then find the last reference field, swap them and repeat 356 int back = fields.length - 1; 357 int front; 358 for (front = 0; front<fields.length; front++) { 359 if (fieldTypes[front] != REFERENCE) { 360 while (back > front) { 361 if (fieldTypes[back] == REFERENCE) { 362 swap(fieldTypes, fields, front, back--); 363 break; 364 } 365 back--; 366 } 367 } 368 369 if (fieldTypes[front] != REFERENCE) { 370 break; 371 } 372 } 373 374 int startFieldOffset = 8; 375 String superclassType = getSuperclass(); 376 ClassProto superclass = null; 377 if (superclassType != null) { 378 superclass = (ClassProto) classPath.getClass(superclassType); 379 if (superclass != null) { 380 startFieldOffset = superclass.getNextFieldOffset(); 381 } 382 } 383 384 int fieldIndexMod; 385 if ((startFieldOffset % 8) == 0) { 386 fieldIndexMod = 0; 387 } else { 388 fieldIndexMod = 1; 389 } 390 391 //next, we need to group all the wide fields after the reference fields. But the wide fields have to be 392 //8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field 393 //is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in. 394 //If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets 395 if (front < fields.length && (front % 2) != fieldIndexMod) { 396 if (fieldTypes[front] == WIDE) { 397 //we need to swap in a 32-bit field, so the wide fields will be correctly aligned 398 back = fields.length - 1; 399 while (back > front) { 400 if (fieldTypes[back] == OTHER) { 401 swap(fieldTypes, fields, front++, back); 402 break; 403 } 404 back--; 405 } 406 } else { 407 //there's already a 32-bit field here that we can use 408 front++; 409 } 410 } 411 412 //do the swap thing for wide fields 413 back = fields.length - 1; 414 for (; front<fields.length; front++) { 415 if (fieldTypes[front] != WIDE) { 416 while (back > front) { 417 if (fieldTypes[back] == WIDE) { 418 swap(fieldTypes, fields, front, back--); 419 break; 420 } 421 back--; 422 } 423 } 424 425 if (fieldTypes[front] != WIDE) { 426 break; 427 } 428 } 429 430 int superFieldCount = 0; 431 if (superclass != null) { 432 superFieldCount = superclass.instanceFields.size(); 433 } 434 435 //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets 436 int totalFieldCount = superFieldCount + fields.length; 437 SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount); 438 439 int fieldOffset; 440 441 if (superclass != null && superFieldCount > 0) { 442 for (int i=0; i<superFieldCount; i++) { 443 instanceFields.append(superclass.instanceFields.keyAt(i), superclass.instanceFields.valueAt(i)); 444 } 445 446 fieldOffset = instanceFields.keyAt(superFieldCount-1); 447 448 FieldReference lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1); 449 char fieldType = lastSuperField.getType().charAt(0); 450 if (fieldType == 'J' || fieldType == 'D') { 451 fieldOffset += 8; 452 } else { 453 fieldOffset += 4; 454 } 455 } else { 456 //the field values start at 8 bytes into the DataObject dalvik structure 457 fieldOffset = 8; 458 } 459 460 boolean gotDouble = false; 461 for (int i=0; i<fields.length; i++) { 462 FieldReference field = fields[i]; 463 464 //add padding to align the wide fields, if needed 465 if (fieldTypes[i] == WIDE && !gotDouble) { 466 if (!gotDouble) { 467 if (fieldOffset % 8 != 0) { 468 assert fieldOffset % 8 == 4; 469 fieldOffset += 4; 470 } 471 gotDouble = true; 472 } 473 } 474 475 instanceFields.append(fieldOffset, field); 476 if (fieldTypes[i] == WIDE) { 477 fieldOffset += 8; 478 } else { 479 fieldOffset += 4; 480 } 481 } 482 483 return instanceFields; 484 } 485 486 @Nonnull 487 private ArrayList<Field> getInstanceFields(@Nonnull ClassDef classDef) { 488 ArrayList<Field> instanceFields = Lists.newArrayList(); 489 for (Field field: classDef.getFields()) { 490 if (!FieldUtil.isStatic(field)) { 491 instanceFields.add(field); 492 } 493 } 494 return instanceFields; 495 } 496 497 private byte getFieldType(String fieldType) { 498 switch (fieldType.charAt(0)) { 499 case '[': 500 case 'L': 501 return 0; //REFERENCE 502 case 'J': 503 case 'D': 504 return 1; //WIDE 505 default: 506 return 2; //OTHER 507 } 508 } 509 510 private void swap(byte[] fieldTypes, FieldReference[] fields, int position1, int position2) { 511 byte tempType = fieldTypes[position1]; 512 fieldTypes[position1] = fieldTypes[position2]; 513 fieldTypes[position2] = tempType; 514 515 FieldReference tempField = fields[position1]; 516 fields[position1] = fields[position2]; 517 fields[position2] = tempField; 518 } 519 520 private int getNextFieldOffset() { 521 SparseArray<FieldReference> instanceFields = getInstanceFields(); 522 if (instanceFields.size() == 0) { 523 return 8; 524 } 525 526 int lastItemIndex = instanceFields.size()-1; 527 int fieldOffset = instanceFields.keyAt(lastItemIndex); 528 FieldReference lastField = instanceFields.valueAt(lastItemIndex); 529 530 switch (lastField.getType().charAt(0)) { 531 case 'J': 532 case 'D': 533 return fieldOffset + 8; 534 default: 535 return fieldOffset + 4; 536 } 537 } 538 539 //TODO: check the case when we have a package private method that overrides an interface method 540 @Nonnull 541 private Method[] loadVtable() { 542 //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 543 List<Method> virtualMethodList = Lists.newLinkedList(); 544 545 //copy the virtual methods from the superclass 546 String superclassType = getSuperclass(); 547 if (superclassType != null) { 548 ClassProto superclass = (ClassProto) classPath.getClass(superclassType); 549 for (int i=0; i<superclass.getVtable().length; i++) { 550 virtualMethodList.add(superclass.getVtable()[i]); 551 } 552 } 553 554 //iterate over the virtual methods in the current class, and only add them when we don't already have the 555 //method (i.e. if it was implemented by the superclass) 556 if (!isInterface()) { 557 addToVtable(getClassDef().getVirtualMethods(), virtualMethodList); 558 559 for (ClassDef interfaceDef: getDirectInterfaces()) { 560 addToVtable(interfaceDef.getVirtualMethods(), virtualMethodList); 561 } 562 } 563 564 Method[] vtable = new Method[virtualMethodList.size()]; 565 for (int i=0; i<virtualMethodList.size(); i++) { 566 vtable[i] = virtualMethodList.get(i); 567 } 568 569 return vtable; 570 } 571 572 private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, @Nonnull List<Method> vtable) { 573 List<? extends Method> methods = Lists.newArrayList(localMethods); 574 Collections.sort(methods); 575 576 for (Method virtualMethod: methods) { 577 boolean found = false; 578 for (int i=0; i<vtable.size(); i++) { 579 Method superMethod = vtable.get(i); 580 if (methodSignaturesMatch(superMethod, virtualMethod)) { 581 if (classPath.getApi() < 17 || canAccess(superMethod)) { 582 found = true; 583 vtable.set(i, virtualMethod); 584 break; 585 } 586 } 587 } 588 if (!found) { 589 vtable.add(virtualMethod); 590 } 591 } 592 } 593 594 private boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) { 595 return (a.getName().equals(b.getName()) 596 && a.getReturnType().equals(b.getReturnType()) 597 && a.getParameters().equals(b.getParameters())); 598 } 599 600 private boolean canAccess(@Nonnull Method virtualMethod) { 601 if (!methodIsPackagePrivate(virtualMethod.getAccessFlags())) { 602 return true; 603 } 604 605 String otherPackage = getPackage(virtualMethod.getDefiningClass()); 606 String ourPackage = getPackage(getClassDef().getType()); 607 return otherPackage.equals(ourPackage); 608 } 609 610 @Nonnull 611 private String getPackage(@Nonnull String classType) { 612 int lastSlash = classType.lastIndexOf('/'); 613 if (lastSlash < 0) { 614 return ""; 615 } 616 return classType.substring(1, lastSlash); 617 } 618 619 private static boolean methodIsPackagePrivate(int accessFlags) { 620 return (accessFlags & (AccessFlags.PRIVATE.getValue() | 621 AccessFlags.PROTECTED.getValue() | 622 AccessFlags.PUBLIC.getValue())) == 0; 623 } 624} 625