Home | History | Annotate | Download | only in analysis
      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 
     32 package org.jf.dexlib2.analysis;
     33 
     34 import com.google.common.base.Predicates;
     35 import com.google.common.base.Supplier;
     36 import com.google.common.base.Suppliers;
     37 import com.google.common.collect.Iterables;
     38 import com.google.common.collect.Lists;
     39 import com.google.common.collect.Maps;
     40 import org.jf.dexlib2.AccessFlags;
     41 import org.jf.dexlib2.analysis.util.TypeProtoUtils;
     42 import org.jf.dexlib2.iface.ClassDef;
     43 import org.jf.dexlib2.iface.Field;
     44 import org.jf.dexlib2.iface.Method;
     45 import org.jf.dexlib2.iface.reference.FieldReference;
     46 import org.jf.dexlib2.iface.reference.MethodReference;
     47 import org.jf.dexlib2.immutable.ImmutableMethod;
     48 import org.jf.util.ExceptionWithContext;
     49 import org.jf.util.SparseArray;
     50 
     51 import javax.annotation.Nonnull;
     52 import javax.annotation.Nullable;
     53 import java.util.ArrayList;
     54 import java.util.Collections;
     55 import java.util.LinkedHashMap;
     56 import java.util.List;
     57 
     58 /**
     59  * A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields
     60  * and their offsets.
     61  */
     62 public class ClassProto implements TypeProto {
     63     @Nonnull protected final ClassPath classPath;
     64     @Nonnull protected final String type;
     65 
     66     protected boolean vtableFullyResolved = true;
     67     protected boolean interfacesFullyResolved = true;
     68 
     69     public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) {
     70         if (type.charAt(0) != 'L') {
     71             throw new ExceptionWithContext("Cannot construct ClassProto for non reference type: %s", type);
     72         }
     73         this.classPath = classPath;
     74         this.type = type;
     75     }
     76 
     77     @Override public String toString() { return type; }
     78     @Nonnull @Override public ClassPath getClassPath() { return classPath; }
     79     @Nonnull @Override public String getType() { return type; }
     80 
     81     @Nonnull
     82     public ClassDef getClassDef() {
     83         return classDefSupplier.get();
     84     }
     85 
     86 
     87     @Nonnull private final Supplier<ClassDef> classDefSupplier = Suppliers.memoize(new Supplier<ClassDef>() {
     88         @Override public ClassDef get() {
     89             return classPath.getClassDef(type);
     90         }
     91     });
     92 
     93     /**
     94      * Returns true if this class is an interface.
     95      *
     96      * If this class is not defined, then this will throw an UnresolvedClassException
     97      *
     98      * @return True if this class is an interface
     99      */
    100     public boolean isInterface() {
    101         ClassDef classDef = getClassDef();
    102         return (classDef.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
    103     }
    104 
    105     /**
    106      * Returns the set of interfaces that this class implements as a Map<String, ClassDef>.
    107      *
    108      * The ClassDef value will be present only for the interfaces that this class directly implements (including any
    109      * interfaces transitively implemented), but not for any interfaces that are only implemented by a superclass of
    110      * this class
    111      *
    112      * For any interfaces that are only implemented by a superclass (or the class itself, if the class is an interface),
    113      * the value will be null.
    114      *
    115      * If any interface couldn't be resolved, then the interfacesFullyResolved field will be set to false upon return.
    116      *
    117      * @return the set of interfaces that this class implements as a Map<String, ClassDef>.
    118      */
    119     @Nonnull
    120     protected LinkedHashMap<String, ClassDef> getInterfaces() {
    121         return interfacesSupplier.get();
    122     }
    123 
    124     @Nonnull
    125     private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier =
    126             Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
    127                 @Override public LinkedHashMap<String, ClassDef> get() {
    128                     LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
    129 
    130                     try {
    131                         for (String interfaceType: getClassDef().getInterfaces()) {
    132                             if (!interfaces.containsKey(interfaceType)) {
    133                                 ClassDef interfaceDef;
    134                                 try {
    135                                     interfaceDef = classPath.getClassDef(interfaceType);
    136                                     interfaces.put(interfaceType, interfaceDef);
    137                                 } catch (UnresolvedClassException ex) {
    138                                     interfaces.put(interfaceType, null);
    139                                     interfacesFullyResolved = false;
    140                                 }
    141 
    142                                 ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
    143                                 for (String superInterface: interfaceProto.getInterfaces().keySet()) {
    144                                     if (!interfaces.containsKey(superInterface)) {
    145                                         interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
    146                                     }
    147                                 }
    148                                 if (!interfaceProto.interfacesFullyResolved) {
    149                                     interfacesFullyResolved = false;
    150                                 }
    151                             }
    152                         }
    153                     } catch (UnresolvedClassException ex) {
    154                         interfacesFullyResolved = false;
    155                     }
    156 
    157                     // now add self and super class interfaces, required for common super class lookup
    158                     // we don't really need ClassDef's for that, so let's just use null
    159 
    160                     if (isInterface() && !interfaces.containsKey(getType())) {
    161                         interfaces.put(getType(), null);
    162                     }
    163 
    164                     try {
    165                         String superclass = getSuperclass();
    166                         if (superclass != null) {
    167                             ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
    168                             for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
    169                                 if (!interfaces.containsKey(superclassInterface)) {
    170                                     interfaces.put(superclassInterface, null);
    171                                 }
    172                             }
    173                             if (!superclassProto.interfacesFullyResolved) {
    174                                 interfacesFullyResolved = false;
    175                             }
    176                         }
    177                     } catch (UnresolvedClassException ex) {
    178                         interfacesFullyResolved = false;
    179                     }
    180 
    181                     return interfaces;
    182                 }
    183             });
    184 
    185     /**
    186      * Gets the interfaces directly implemented by this class, or the interfaces they transitively implement.
    187      *
    188      * This does not include any interfaces that are only implemented by a superclass
    189      *
    190      * @return An iterables of ClassDefs representing the directly or transitively implemented interfaces
    191      * @throws UnresolvedClassException if interfaces could not be fully resolved
    192      */
    193     @Nonnull
    194     protected Iterable<ClassDef> getDirectInterfaces() {
    195         Iterable<ClassDef> directInterfaces =
    196                 Iterables.filter(getInterfaces().values(), Predicates.notNull());
    197 
    198         if (!interfacesFullyResolved) {
    199             throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
    200         }
    201 
    202         return directInterfaces;
    203     }
    204 
    205     /**
    206      * Checks if this class implements the given interface.
    207      *
    208      * If the interfaces of this class cannot be fully resolved then this
    209      * method will either return true or throw an UnresolvedClassException
    210      *
    211      * @param iface The interface to check for
    212      * @return true if this class implements the given interface, otherwise false
    213      * @throws UnresolvedClassException if the interfaces for this class could not be fully resolved, and the interface
    214      * is not one of the interfaces that were successfully resolved
    215      */
    216     @Override
    217     public boolean implementsInterface(@Nonnull String iface) {
    218         if (getInterfaces().containsKey(iface)) {
    219             return true;
    220         }
    221         if (!interfacesFullyResolved) {
    222             throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
    223         }
    224         return false;
    225     }
    226 
    227     @Nullable @Override
    228     public String getSuperclass() {
    229         return getClassDef().getSuperclass();
    230     }
    231 
    232     /**
    233      * This is a helper method for getCommonSuperclass
    234      *
    235      * It checks if this class is an interface, and if so, if other implements it.
    236      *
    237      * If this class is undefined, we go ahead and check if it is listed in other's interfaces. If not, we throw an
    238      * UndefinedClassException
    239      *
    240      * If the interfaces of other cannot be fully resolved, we check the interfaces that can be resolved. If not found,
    241      * we throw an UndefinedClassException
    242      *
    243      * @param other The class to check the interfaces of
    244      * @return true if this class is an interface (or is undefined) other implements this class
    245      *
    246      */
    247     private boolean checkInterface(@Nonnull ClassProto other) {
    248         boolean isResolved = true;
    249         boolean isInterface = true;
    250         try {
    251             isInterface = isInterface();
    252         } catch (UnresolvedClassException ex) {
    253             isResolved = false;
    254             // if we don't know if this class is an interface or not,
    255             // we can still try to call other.implementsInterface(this)
    256         }
    257         if (isInterface) {
    258             try {
    259                 if (other.implementsInterface(getType())) {
    260                     return true;
    261                 }
    262             } catch (UnresolvedClassException ex) {
    263                 // There are 2 possibilities here, depending on whether we were able to resolve this class.
    264                 // 1. If this class is resolved, then we know it is an interface class. The other class either
    265                 //    isn't defined, or its interfaces couldn't be fully resolved.
    266                 //    In this case, we throw an UnresolvedClassException
    267                 // 2. If this class is not resolved, we had tried to call implementsInterface anyway. We don't
    268                 //    know for sure if this class is an interface or not. We return false, and let processing
    269                 //    continue in getCommonSuperclass
    270                 if (isResolved) {
    271                     throw ex;
    272                 }
    273             }
    274         }
    275         return false;
    276     }
    277 
    278     @Override @Nonnull
    279     public TypeProto getCommonSuperclass(@Nonnull TypeProto other) {
    280         // use the other type's more specific implementation
    281         if (!(other instanceof ClassProto)) {
    282             return other.getCommonSuperclass(this);
    283         }
    284 
    285         if (this == other || getType().equals(other.getType())) {
    286             return this;
    287         }
    288 
    289         if (this.getType().equals("Ljava/lang/Object;")) {
    290             return this;
    291         }
    292 
    293         if (other.getType().equals("Ljava/lang/Object;")) {
    294             return other;
    295         }
    296 
    297         boolean gotException = false;
    298         try {
    299             if (checkInterface((ClassProto)other)) {
    300                 return this;
    301             }
    302         } catch (UnresolvedClassException ex) {
    303             gotException = true;
    304         }
    305 
    306         try {
    307             if (((ClassProto)other).checkInterface(this)) {
    308                 return other;
    309             }
    310         } catch (UnresolvedClassException ex) {
    311             gotException = true;
    312         }
    313         if (gotException) {
    314             return classPath.getUnknownClass();
    315         }
    316 
    317         List<TypeProto> thisChain = Lists.<TypeProto>newArrayList(this);
    318         Iterables.addAll(thisChain, TypeProtoUtils.getSuperclassChain(this));
    319 
    320         List<TypeProto> otherChain = Lists.newArrayList(other);
    321         Iterables.addAll(otherChain, TypeProtoUtils.getSuperclassChain(other));
    322 
    323         // reverse them, so that the first entry is either Ljava/lang/Object; or Ujava/lang/Object;
    324         thisChain = Lists.reverse(thisChain);
    325         otherChain = Lists.reverse(otherChain);
    326 
    327         for (int i=Math.min(thisChain.size(), otherChain.size())-1; i>=0; i--) {
    328             TypeProto typeProto = thisChain.get(i);
    329             if (typeProto.getType().equals(otherChain.get(i).getType())) {
    330                 return typeProto;
    331             }
    332         }
    333 
    334         return classPath.getUnknownClass();
    335     }
    336 
    337     @Override
    338     @Nullable
    339     public FieldReference getFieldByOffset(int fieldOffset) {
    340         if (getInstanceFields().size() == 0) {
    341             return null;
    342         }
    343         return getInstanceFields().get(fieldOffset);
    344     }
    345 
    346     @Override
    347     @Nullable
    348     public MethodReference getMethodByVtableIndex(int vtableIndex) {
    349         List<Method> vtable = getVtable();
    350         if (vtableIndex < 0 || vtableIndex >= vtable.size()) {
    351             return null;
    352         }
    353 
    354         return vtable.get(vtableIndex);
    355     }
    356 
    357     @Nonnull SparseArray<FieldReference> getInstanceFields() {
    358         return instanceFieldsSupplier.get();
    359     }
    360 
    361     @Nonnull private final Supplier<SparseArray<FieldReference>> instanceFieldsSupplier =
    362             Suppliers.memoize(new Supplier<SparseArray<FieldReference>>() {
    363                 @Override public SparseArray<FieldReference> get() {
    364                     //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
    365                     //arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
    366                     //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
    367 
    368                     final byte REFERENCE = 0;
    369                     final byte WIDE = 1;
    370                     final byte OTHER = 2;
    371 
    372                     ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
    373                     final int fieldCount = fields.size();
    374                     //the "type" for each field in fields. 0=reference,1=wide,2=other
    375                     byte[] fieldTypes = new byte[fields.size()];
    376                     for (int i=0; i<fieldCount; i++) {
    377                         fieldTypes[i] = getFieldType(fields.get(i));
    378                     }
    379 
    380                     //The first operation is to move all of the reference fields to the front. To do this, find the first
    381                     //non-reference field, then find the last reference field, swap them and repeat
    382                     int back = fields.size() - 1;
    383                     int front;
    384                     for (front = 0; front<fieldCount; front++) {
    385                         if (fieldTypes[front] != REFERENCE) {
    386                             while (back > front) {
    387                                 if (fieldTypes[back] == REFERENCE) {
    388                                     swap(fieldTypes, fields, front, back--);
    389                                     break;
    390                                 }
    391                                 back--;
    392                             }
    393                         }
    394 
    395                         if (fieldTypes[front] != REFERENCE) {
    396                             break;
    397                         }
    398                     }
    399 
    400                     int startFieldOffset = 8;
    401                     String superclassType = getSuperclass();
    402                     ClassProto superclass = null;
    403                     if (superclassType != null) {
    404                         superclass = (ClassProto) classPath.getClass(superclassType);
    405                         if (superclass != null) {
    406                             startFieldOffset = superclass.getNextFieldOffset();
    407                         }
    408                     }
    409 
    410                     int fieldIndexMod;
    411                     if ((startFieldOffset % 8) == 0) {
    412                         fieldIndexMod = 0;
    413                     } else {
    414                         fieldIndexMod = 1;
    415                     }
    416 
    417                     //next, we need to group all the wide fields after the reference fields. But the wide fields have to be
    418                     //8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
    419                     //is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
    420                     //If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
    421                     if (front < fieldCount && (front % 2) != fieldIndexMod) {
    422                         if (fieldTypes[front] == WIDE) {
    423                             //we need to swap in a 32-bit field, so the wide fields will be correctly aligned
    424                             back = fieldCount - 1;
    425                             while (back > front) {
    426                                 if (fieldTypes[back] == OTHER) {
    427                                     swap(fieldTypes, fields, front++, back);
    428                                     break;
    429                                 }
    430                                 back--;
    431                             }
    432                         } else {
    433                             //there's already a 32-bit field here that we can use
    434                             front++;
    435                         }
    436                     }
    437 
    438                     //do the swap thing for wide fields
    439                     back = fieldCount - 1;
    440                     for (; front<fieldCount; front++) {
    441                         if (fieldTypes[front] != WIDE) {
    442                             while (back > front) {
    443                                 if (fieldTypes[back] == WIDE) {
    444                                     swap(fieldTypes, fields, front, back--);
    445                                     break;
    446                                 }
    447                                 back--;
    448                             }
    449                         }
    450 
    451                         if (fieldTypes[front] != WIDE) {
    452                             break;
    453                         }
    454                     }
    455 
    456                     SparseArray<FieldReference> superFields;
    457                     if (superclass != null) {
    458                         superFields = superclass.getInstanceFields();
    459                     } else {
    460                         superFields = new SparseArray<FieldReference>();
    461                     }
    462                     int superFieldCount = superFields.size();
    463 
    464                     //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
    465                     int totalFieldCount = superFieldCount + fieldCount;
    466                     SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
    467 
    468                     int fieldOffset;
    469 
    470                     if (superclass != null && superFieldCount > 0) {
    471                         for (int i=0; i<superFieldCount; i++) {
    472                             instanceFields.append(superFields.keyAt(i), superFields.valueAt(i));
    473                         }
    474 
    475                         fieldOffset = instanceFields.keyAt(superFieldCount-1);
    476 
    477                         FieldReference lastSuperField = superFields.valueAt(superFieldCount-1);
    478                         char fieldType = lastSuperField.getType().charAt(0);
    479                         if (fieldType == 'J' || fieldType == 'D') {
    480                             fieldOffset += 8;
    481                         } else {
    482                             fieldOffset += 4;
    483                         }
    484                     } else {
    485                         //the field values start at 8 bytes into the DataObject dalvik structure
    486                         fieldOffset = 8;
    487                     }
    488 
    489                     boolean gotDouble = false;
    490                     for (int i=0; i<fieldCount; i++) {
    491                         FieldReference field = fields.get(i);
    492 
    493                         //add padding to align the wide fields, if needed
    494                         if (fieldTypes[i] == WIDE && !gotDouble) {
    495                             if (!gotDouble) {
    496                                 if (fieldOffset % 8 != 0) {
    497                                     assert fieldOffset % 8 == 4;
    498                                     fieldOffset += 4;
    499                                 }
    500                                 gotDouble = true;
    501                             }
    502                         }
    503 
    504                         instanceFields.append(fieldOffset, field);
    505                         if (fieldTypes[i] == WIDE) {
    506                             fieldOffset += 8;
    507                         } else {
    508                             fieldOffset += 4;
    509                         }
    510                     }
    511 
    512                     return instanceFields;
    513                 }
    514 
    515                 @Nonnull
    516                 private ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
    517                     ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
    518                     Collections.sort(fields);
    519                     return fields;
    520                 }
    521 
    522                 private byte getFieldType(@Nonnull FieldReference field) {
    523                     switch (field.getType().charAt(0)) {
    524                         case '[':
    525                         case 'L':
    526                             return 0; //REFERENCE
    527                         case 'J':
    528                         case 'D':
    529                             return 1; //WIDE
    530                         default:
    531                             return 2; //OTHER
    532                     }
    533                 }
    534 
    535                 private void swap(byte[] fieldTypes, List<Field> fields, int position1, int position2) {
    536                     byte tempType = fieldTypes[position1];
    537                     fieldTypes[position1] = fieldTypes[position2];
    538                     fieldTypes[position2] = tempType;
    539 
    540                     Field tempField = fields.set(position1, fields.get(position2));
    541                     fields.set(position2, tempField);
    542                 }
    543             });
    544 
    545     private int getNextFieldOffset() {
    546         SparseArray<FieldReference> instanceFields = getInstanceFields();
    547         if (instanceFields.size() == 0) {
    548             return 8;
    549         }
    550 
    551         int lastItemIndex = instanceFields.size()-1;
    552         int fieldOffset = instanceFields.keyAt(lastItemIndex);
    553         FieldReference lastField = instanceFields.valueAt(lastItemIndex);
    554 
    555         switch (lastField.getType().charAt(0)) {
    556             case 'J':
    557             case 'D':
    558                 return fieldOffset + 8;
    559             default:
    560                 return fieldOffset + 4;
    561         }
    562     }
    563 
    564     @Nonnull List<Method> getVtable() {
    565         return vtableSupplier.get();
    566     }
    567 
    568     //TODO: check the case when we have a package private method that overrides an interface method
    569     @Nonnull private final Supplier<List<Method>> vtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
    570         @Override public List<Method> get() {
    571             List<Method> vtable = Lists.newArrayList();
    572 
    573             //copy the virtual methods from the superclass
    574             String superclassType;
    575             try {
    576                 superclassType = getSuperclass();
    577             } catch (UnresolvedClassException ex) {
    578                 vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
    579                 vtableFullyResolved = false;
    580                 return vtable;
    581             }
    582 
    583             if (superclassType != null) {
    584                 ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
    585                 vtable.addAll(superclass.getVtable());
    586 
    587                 // if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this
    588                 // class should start, so we just propagate what we can from the parent and hope for the best.
    589                 if (!superclass.vtableFullyResolved) {
    590                     vtableFullyResolved = false;
    591                     return vtable;
    592                 }
    593             }
    594 
    595             //iterate over the virtual methods in the current class, and only add them when we don't already have the
    596             //method (i.e. if it was implemented by the superclass)
    597             if (!isInterface()) {
    598                 addToVtable(getClassDef().getVirtualMethods(), vtable, true);
    599 
    600                 // assume that interface method is implemented in the current class, when adding it to vtable
    601                 // otherwise it looks like that method is invoked on an interface, which fails Dalvik's optimization checks
    602                 for (ClassDef interfaceDef: getDirectInterfaces()) {
    603                     List<Method> interfaceMethods = Lists.newArrayList();
    604                     for (Method interfaceMethod: interfaceDef.getVirtualMethods()) {
    605                         ImmutableMethod method = new ImmutableMethod(
    606                                 type,
    607                                 interfaceMethod.getName(),
    608                                 interfaceMethod.getParameters(),
    609                                 interfaceMethod.getReturnType(),
    610                                 interfaceMethod.getAccessFlags(),
    611                                 interfaceMethod.getAnnotations(),
    612                                 interfaceMethod.getImplementation());
    613                         interfaceMethods.add(method);
    614                     }
    615                     addToVtable(interfaceMethods, vtable, false);
    616                 }
    617             }
    618             return vtable;
    619         }
    620 
    621         private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
    622                                  @Nonnull List<Method> vtable, boolean replaceExisting) {
    623             List<? extends Method> methods = Lists.newArrayList(localMethods);
    624             Collections.sort(methods);
    625 
    626             outer: for (Method virtualMethod: methods) {
    627                 for (int i=0; i<vtable.size(); i++) {
    628                     Method superMethod = vtable.get(i);
    629                     if (methodSignaturesMatch(superMethod, virtualMethod)) {
    630                         if (classPath.getApi() < 17 || canAccess(superMethod)) {
    631                             if (replaceExisting) {
    632                                 vtable.set(i, virtualMethod);
    633                             }
    634                             continue outer;
    635                         }
    636                     }
    637                 }
    638                 // we didn't find an equivalent method, so add it as a new entry
    639                 vtable.add(virtualMethod);
    640             }
    641         }
    642 
    643         private boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) {
    644             return (a.getName().equals(b.getName()) &&
    645                     a.getReturnType().equals(b.getReturnType()) &&
    646                     a.getParameters().equals(b.getParameters()));
    647         }
    648 
    649         private boolean canAccess(@Nonnull Method virtualMethod) {
    650             if (!methodIsPackagePrivate(virtualMethod.getAccessFlags())) {
    651                 return true;
    652             }
    653 
    654             String otherPackage = getPackage(virtualMethod.getDefiningClass());
    655             String ourPackage = getPackage(getClassDef().getType());
    656             return otherPackage.equals(ourPackage);
    657         }
    658 
    659         @Nonnull
    660         private String getPackage(@Nonnull String classType) {
    661             int lastSlash = classType.lastIndexOf('/');
    662             if (lastSlash < 0) {
    663                 return "";
    664             }
    665             return classType.substring(1, lastSlash);
    666         }
    667 
    668         private boolean methodIsPackagePrivate(int accessFlags) {
    669             return (accessFlags & (AccessFlags.PRIVATE.getValue() |
    670                     AccessFlags.PROTECTED.getValue() |
    671                     AccessFlags.PUBLIC.getValue())) == 0;
    672         }
    673     });
    674 }
    675