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 com.google.common.primitives.Ints;
     41 import org.jf.dexlib2.AccessFlags;
     42 import org.jf.dexlib2.analysis.util.TypeProtoUtils;
     43 import org.jf.dexlib2.iface.ClassDef;
     44 import org.jf.dexlib2.iface.Field;
     45 import org.jf.dexlib2.iface.Method;
     46 import org.jf.dexlib2.iface.reference.FieldReference;
     47 import org.jf.dexlib2.iface.reference.MethodReference;
     48 import org.jf.dexlib2.immutable.ImmutableMethod;
     49 import org.jf.dexlib2.util.MethodUtil;
     50 import org.jf.util.AlignmentUtils;
     51 import org.jf.util.ExceptionWithContext;
     52 import org.jf.util.SparseArray;
     53 
     54 import javax.annotation.Nonnull;
     55 import javax.annotation.Nullable;
     56 import java.util.*;
     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     private static final byte REFERENCE = 0;
     64     private static final byte WIDE = 1;
     65     private static final byte OTHER = 2;
     66 
     67     @Nonnull protected final ClassPath classPath;
     68     @Nonnull protected final String type;
     69 
     70     protected boolean vtableFullyResolved = true;
     71     protected boolean interfacesFullyResolved = true;
     72 
     73     public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) {
     74         if (type.charAt(0) != 'L') {
     75             throw new ExceptionWithContext("Cannot construct ClassProto for non reference type: %s", type);
     76         }
     77         this.classPath = classPath;
     78         this.type = type;
     79     }
     80 
     81     @Override public String toString() { return type; }
     82     @Nonnull @Override public ClassPath getClassPath() { return classPath; }
     83     @Nonnull @Override public String getType() { return type; }
     84 
     85     @Nonnull
     86     public ClassDef getClassDef() {
     87         return classDefSupplier.get();
     88     }
     89 
     90 
     91     @Nonnull private final Supplier<ClassDef> classDefSupplier = Suppliers.memoize(new Supplier<ClassDef>() {
     92         @Override public ClassDef get() {
     93             return classPath.getClassDef(type);
     94         }
     95     });
     96 
     97     /**
     98      * Returns true if this class is an interface.
     99      *
    100      * If this class is not defined, then this will throw an UnresolvedClassException
    101      *
    102      * @return True if this class is an interface
    103      */
    104     public boolean isInterface() {
    105         ClassDef classDef = getClassDef();
    106         return (classDef.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
    107     }
    108 
    109     /**
    110      * Returns the set of interfaces that this class implements as a Map<String, ClassDef>.
    111      *
    112      * The ClassDef value will be present only for the interfaces that this class directly implements (including any
    113      * interfaces transitively implemented), but not for any interfaces that are only implemented by a superclass of
    114      * this class
    115      *
    116      * For any interfaces that are only implemented by a superclass (or the class itself, if the class is an interface),
    117      * the value will be null.
    118      *
    119      * If any interface couldn't be resolved, then the interfacesFullyResolved field will be set to false upon return.
    120      *
    121      * @return the set of interfaces that this class implements as a Map<String, ClassDef>.
    122      */
    123     @Nonnull
    124     protected LinkedHashMap<String, ClassDef> getInterfaces() {
    125         return interfacesSupplier.get();
    126     }
    127 
    128     @Nonnull
    129     private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier =
    130             Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
    131                 @Override public LinkedHashMap<String, ClassDef> get() {
    132                     LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
    133 
    134                     try {
    135                         for (String interfaceType: getClassDef().getInterfaces()) {
    136                             if (!interfaces.containsKey(interfaceType)) {
    137                                 ClassDef interfaceDef;
    138                                 try {
    139                                     interfaceDef = classPath.getClassDef(interfaceType);
    140                                     interfaces.put(interfaceType, interfaceDef);
    141                                 } catch (UnresolvedClassException ex) {
    142                                     interfaces.put(interfaceType, null);
    143                                     interfacesFullyResolved = false;
    144                                 }
    145 
    146                                 ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
    147                                 for (String superInterface: interfaceProto.getInterfaces().keySet()) {
    148                                     if (!interfaces.containsKey(superInterface)) {
    149                                         interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
    150                                     }
    151                                 }
    152                                 if (!interfaceProto.interfacesFullyResolved) {
    153                                     interfacesFullyResolved = false;
    154                                 }
    155                             }
    156                         }
    157                     } catch (UnresolvedClassException ex) {
    158                         interfacesFullyResolved = false;
    159                     }
    160 
    161                     // now add self and super class interfaces, required for common super class lookup
    162                     // we don't really need ClassDef's for that, so let's just use null
    163 
    164                     if (isInterface() && !interfaces.containsKey(getType())) {
    165                         interfaces.put(getType(), null);
    166                     }
    167 
    168                     try {
    169                         String superclass = getSuperclass();
    170                         if (superclass != null) {
    171                             ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
    172                             for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
    173                                 if (!interfaces.containsKey(superclassInterface)) {
    174                                     interfaces.put(superclassInterface, null);
    175                                 }
    176                             }
    177                             if (!superclassProto.interfacesFullyResolved) {
    178                                 interfacesFullyResolved = false;
    179                             }
    180                         }
    181                     } catch (UnresolvedClassException ex) {
    182                         interfacesFullyResolved = false;
    183                     }
    184 
    185                     return interfaces;
    186                 }
    187             });
    188 
    189     /**
    190      * Gets the interfaces directly implemented by this class, or the interfaces they transitively implement.
    191      *
    192      * This does not include any interfaces that are only implemented by a superclass
    193      *
    194      * @return An iterables of ClassDefs representing the directly or transitively implemented interfaces
    195      * @throws UnresolvedClassException if interfaces could not be fully resolved
    196      */
    197     @Nonnull
    198     protected Iterable<ClassDef> getDirectInterfaces() {
    199         Iterable<ClassDef> directInterfaces =
    200                 Iterables.filter(getInterfaces().values(), Predicates.notNull());
    201 
    202         if (!interfacesFullyResolved) {
    203             throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
    204         }
    205 
    206         return directInterfaces;
    207     }
    208 
    209     /**
    210      * Checks if this class implements the given interface.
    211      *
    212      * If the interfaces of this class cannot be fully resolved then this
    213      * method will either return true or throw an UnresolvedClassException
    214      *
    215      * @param iface The interface to check for
    216      * @return true if this class implements the given interface, otherwise false
    217      * @throws UnresolvedClassException if the interfaces for this class could not be fully resolved, and the interface
    218      * is not one of the interfaces that were successfully resolved
    219      */
    220     @Override
    221     public boolean implementsInterface(@Nonnull String iface) {
    222         if (getInterfaces().containsKey(iface)) {
    223             return true;
    224         }
    225         if (!interfacesFullyResolved) {
    226             throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
    227         }
    228         return false;
    229     }
    230 
    231     @Nullable @Override
    232     public String getSuperclass() {
    233         return getClassDef().getSuperclass();
    234     }
    235 
    236     /**
    237      * This is a helper method for getCommonSuperclass
    238      *
    239      * It checks if this class is an interface, and if so, if other implements it.
    240      *
    241      * If this class is undefined, we go ahead and check if it is listed in other's interfaces. If not, we throw an
    242      * UndefinedClassException
    243      *
    244      * If the interfaces of other cannot be fully resolved, we check the interfaces that can be resolved. If not found,
    245      * we throw an UndefinedClassException
    246      *
    247      * @param other The class to check the interfaces of
    248      * @return true if this class is an interface (or is undefined) other implements this class
    249      *
    250      */
    251     private boolean checkInterface(@Nonnull ClassProto other) {
    252         boolean isResolved = true;
    253         boolean isInterface = true;
    254         try {
    255             isInterface = isInterface();
    256         } catch (UnresolvedClassException ex) {
    257             isResolved = false;
    258             // if we don't know if this class is an interface or not,
    259             // we can still try to call other.implementsInterface(this)
    260         }
    261         if (isInterface) {
    262             try {
    263                 if (other.implementsInterface(getType())) {
    264                     return true;
    265                 }
    266             } catch (UnresolvedClassException ex) {
    267                 // There are 2 possibilities here, depending on whether we were able to resolve this class.
    268                 // 1. If this class is resolved, then we know it is an interface class. The other class either
    269                 //    isn't defined, or its interfaces couldn't be fully resolved.
    270                 //    In this case, we throw an UnresolvedClassException
    271                 // 2. If this class is not resolved, we had tried to call implementsInterface anyway. We don't
    272                 //    know for sure if this class is an interface or not. We return false, and let processing
    273                 //    continue in getCommonSuperclass
    274                 if (isResolved) {
    275                     throw ex;
    276                 }
    277             }
    278         }
    279         return false;
    280     }
    281 
    282     @Override @Nonnull
    283     public TypeProto getCommonSuperclass(@Nonnull TypeProto other) {
    284         // use the other type's more specific implementation
    285         if (!(other instanceof ClassProto)) {
    286             return other.getCommonSuperclass(this);
    287         }
    288 
    289         if (this == other || getType().equals(other.getType())) {
    290             return this;
    291         }
    292 
    293         if (this.getType().equals("Ljava/lang/Object;")) {
    294             return this;
    295         }
    296 
    297         if (other.getType().equals("Ljava/lang/Object;")) {
    298             return other;
    299         }
    300 
    301         boolean gotException = false;
    302         try {
    303             if (checkInterface((ClassProto)other)) {
    304                 return this;
    305             }
    306         } catch (UnresolvedClassException ex) {
    307             gotException = true;
    308         }
    309 
    310         try {
    311             if (((ClassProto)other).checkInterface(this)) {
    312                 return other;
    313             }
    314         } catch (UnresolvedClassException ex) {
    315             gotException = true;
    316         }
    317         if (gotException) {
    318             return classPath.getUnknownClass();
    319         }
    320 
    321         List<TypeProto> thisChain = Lists.<TypeProto>newArrayList(this);
    322         Iterables.addAll(thisChain, TypeProtoUtils.getSuperclassChain(this));
    323 
    324         List<TypeProto> otherChain = Lists.newArrayList(other);
    325         Iterables.addAll(otherChain, TypeProtoUtils.getSuperclassChain(other));
    326 
    327         // reverse them, so that the first entry is either Ljava/lang/Object; or Ujava/lang/Object;
    328         thisChain = Lists.reverse(thisChain);
    329         otherChain = Lists.reverse(otherChain);
    330 
    331         for (int i=Math.min(thisChain.size(), otherChain.size())-1; i>=0; i--) {
    332             TypeProto typeProto = thisChain.get(i);
    333             if (typeProto.getType().equals(otherChain.get(i).getType())) {
    334                 return typeProto;
    335             }
    336         }
    337 
    338         return classPath.getUnknownClass();
    339     }
    340 
    341     @Override
    342     @Nullable
    343     public FieldReference getFieldByOffset(int fieldOffset) {
    344         if (getInstanceFields().size() == 0) {
    345             return null;
    346         }
    347         return getInstanceFields().get(fieldOffset);
    348     }
    349 
    350     @Override
    351     @Nullable
    352     public Method getMethodByVtableIndex(int vtableIndex) {
    353         List<Method> vtable = getVtable();
    354         if (vtableIndex < 0 || vtableIndex >= vtable.size()) {
    355             return null;
    356         }
    357 
    358         return vtable.get(vtableIndex);
    359     }
    360 
    361     public int findMethodIndexInVtable(@Nonnull MethodReference method) {
    362         List<Method> vtable = getVtable();
    363         for (int i=0; i<vtable.size(); i++) {
    364             Method candidate = vtable.get(i);
    365             if (MethodUtil.methodSignaturesMatch(candidate, method)) {
    366                 if (!classPath.shouldCheckPackagePrivateAccess() ||
    367                         AnalyzedMethodUtil.canAccess(this, candidate, true, false, false)) {
    368                     return i;
    369                 }
    370             }
    371         }
    372         return -1;
    373     }
    374 
    375     @Nonnull SparseArray<FieldReference> getInstanceFields() {
    376         if (classPath.isArt()) {
    377             return artInstanceFieldsSupplier.get();
    378         } else {
    379             return dalvikInstanceFieldsSupplier.get();
    380         }
    381     }
    382 
    383     @Nonnull private final Supplier<SparseArray<FieldReference>> dalvikInstanceFieldsSupplier =
    384             Suppliers.memoize(new Supplier<SparseArray<FieldReference>>() {
    385                 @Override public SparseArray<FieldReference> get() {
    386                     //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
    387                     //arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
    388                     //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
    389 
    390                     ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
    391                     final int fieldCount = fields.size();
    392                     //the "type" for each field in fields. 0=reference,1=wide,2=other
    393                     byte[] fieldTypes = new byte[fields.size()];
    394                     for (int i=0; i<fieldCount; i++) {
    395                         fieldTypes[i] = getFieldType(fields.get(i));
    396                     }
    397 
    398                     //The first operation is to move all of the reference fields to the front. To do this, find the first
    399                     //non-reference field, then find the last reference field, swap them and repeat
    400                     int back = fields.size() - 1;
    401                     int front;
    402                     for (front = 0; front<fieldCount; front++) {
    403                         if (fieldTypes[front] != REFERENCE) {
    404                             while (back > front) {
    405                                 if (fieldTypes[back] == REFERENCE) {
    406                                     swap(fieldTypes, fields, front, back--);
    407                                     break;
    408                                 }
    409                                 back--;
    410                             }
    411                         }
    412 
    413                         if (fieldTypes[front] != REFERENCE) {
    414                             break;
    415                         }
    416                     }
    417 
    418                     int startFieldOffset = 8;
    419                     String superclassType = getSuperclass();
    420                     ClassProto superclass = null;
    421                     if (superclassType != null) {
    422                         superclass = (ClassProto) classPath.getClass(superclassType);
    423                         if (superclass != null) {
    424                             startFieldOffset = superclass.getNextFieldOffset();
    425                         }
    426                     }
    427 
    428                     int fieldIndexMod;
    429                     if ((startFieldOffset % 8) == 0) {
    430                         fieldIndexMod = 0;
    431                     } else {
    432                         fieldIndexMod = 1;
    433                     }
    434 
    435                     //next, we need to group all the wide fields after the reference fields. But the wide fields have to be
    436                     //8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
    437                     //is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
    438                     //If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
    439                     if (front < fieldCount && (front % 2) != fieldIndexMod) {
    440                         if (fieldTypes[front] == WIDE) {
    441                             //we need to swap in a 32-bit field, so the wide fields will be correctly aligned
    442                             back = fieldCount - 1;
    443                             while (back > front) {
    444                                 if (fieldTypes[back] == OTHER) {
    445                                     swap(fieldTypes, fields, front++, back);
    446                                     break;
    447                                 }
    448                                 back--;
    449                             }
    450                         } else {
    451                             //there's already a 32-bit field here that we can use
    452                             front++;
    453                         }
    454                     }
    455 
    456                     //do the swap thing for wide fields
    457                     back = fieldCount - 1;
    458                     for (; front<fieldCount; front++) {
    459                         if (fieldTypes[front] != WIDE) {
    460                             while (back > front) {
    461                                 if (fieldTypes[back] == WIDE) {
    462                                     swap(fieldTypes, fields, front, back--);
    463                                     break;
    464                                 }
    465                                 back--;
    466                             }
    467                         }
    468 
    469                         if (fieldTypes[front] != WIDE) {
    470                             break;
    471                         }
    472                     }
    473 
    474                     SparseArray<FieldReference> superFields;
    475                     if (superclass != null) {
    476                         superFields = superclass.getInstanceFields();
    477                     } else {
    478                         superFields = new SparseArray<FieldReference>();
    479                     }
    480                     int superFieldCount = superFields.size();
    481 
    482                     //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
    483                     int totalFieldCount = superFieldCount + fieldCount;
    484                     SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
    485 
    486                     int fieldOffset;
    487 
    488                     if (superclass != null && superFieldCount > 0) {
    489                         for (int i=0; i<superFieldCount; i++) {
    490                             instanceFields.append(superFields.keyAt(i), superFields.valueAt(i));
    491                         }
    492 
    493                         fieldOffset = instanceFields.keyAt(superFieldCount-1);
    494 
    495                         FieldReference lastSuperField = superFields.valueAt(superFieldCount-1);
    496                         char fieldType = lastSuperField.getType().charAt(0);
    497                         if (fieldType == 'J' || fieldType == 'D') {
    498                             fieldOffset += 8;
    499                         } else {
    500                             fieldOffset += 4;
    501                         }
    502                     } else {
    503                         //the field values start at 8 bytes into the DataObject dalvik structure
    504                         fieldOffset = 8;
    505                     }
    506 
    507                     boolean gotDouble = false;
    508                     for (int i=0; i<fieldCount; i++) {
    509                         FieldReference field = fields.get(i);
    510 
    511                         //add padding to align the wide fields, if needed
    512                         if (fieldTypes[i] == WIDE && !gotDouble) {
    513                             if (!gotDouble) {
    514                                 if (fieldOffset % 8 != 0) {
    515                                     assert fieldOffset % 8 == 4;
    516                                     fieldOffset += 4;
    517                                 }
    518                                 gotDouble = true;
    519                             }
    520                         }
    521 
    522                         instanceFields.append(fieldOffset, field);
    523                         if (fieldTypes[i] == WIDE) {
    524                             fieldOffset += 8;
    525                         } else {
    526                             fieldOffset += 4;
    527                         }
    528                     }
    529 
    530                     return instanceFields;
    531                 }
    532 
    533                 @Nonnull
    534                 private ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
    535                     ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
    536                     Collections.sort(fields);
    537                     return fields;
    538                 }
    539 
    540                 private void swap(byte[] fieldTypes, List<Field> fields, int position1, int position2) {
    541                     byte tempType = fieldTypes[position1];
    542                     fieldTypes[position1] = fieldTypes[position2];
    543                     fieldTypes[position2] = tempType;
    544 
    545                     Field tempField = fields.set(position1, fields.get(position2));
    546                     fields.set(position2, tempField);
    547                 }
    548             });
    549 
    550     private static abstract class FieldGap implements Comparable<FieldGap> {
    551         public final int offset;
    552         public final int size;
    553 
    554         public static FieldGap newFieldGap(int offset, int size, int oatVersion) {
    555             if (oatVersion >= 67) {
    556                 return new FieldGap(offset, size) {
    557                     @Override public int compareTo(FieldGap o) {
    558                         int result = Ints.compare(o.size, size);
    559                         if (result != 0) {
    560                             return result;
    561                         }
    562                         return Ints.compare(offset, o.offset);
    563                     }
    564                 };
    565             } else {
    566                 return new FieldGap(offset, size) {
    567                     @Override public int compareTo(FieldGap o) {
    568                         int result = Ints.compare(size, o.size);
    569                         if (result != 0) {
    570                             return result;
    571                         }
    572                         return Ints.compare(o.offset, offset);
    573                     }
    574                 };
    575             }
    576         }
    577 
    578         private FieldGap(int offset, int size) {
    579             this.offset = offset;
    580             this.size = size;
    581         }
    582     }
    583 
    584     @Nonnull private final Supplier<SparseArray<FieldReference>> artInstanceFieldsSupplier =
    585             Suppliers.memoize(new Supplier<SparseArray<FieldReference>>() {
    586 
    587                 @Override public SparseArray<FieldReference> get() {
    588                     // We need to follow the same algorithm that art uses to arrange fields, so that we end up with the
    589                     // same field offsets, which is needed for deodexing.
    590                     // See LinkFields() in art/runtime/class_linker.cc
    591 
    592                     PriorityQueue<FieldGap> gaps = new PriorityQueue<FieldGap>();
    593 
    594                     SparseArray<FieldReference> linkedFields = new SparseArray<FieldReference>();
    595                     ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
    596 
    597                     int fieldOffset = 0;
    598                     String superclassType = getSuperclass();
    599                     if (superclassType != null) {
    600                         // TODO: what to do if superclass doesn't exist?
    601                         ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
    602                         SparseArray<FieldReference> superFields = superclass.getInstanceFields();
    603                         FieldReference field = null;
    604                         int lastOffset = 0;
    605                         for (int i=0; i<superFields.size(); i++) {
    606                             int offset = superFields.keyAt(i);
    607                             field = superFields.valueAt(i);
    608                             linkedFields.put(offset, field);
    609                             lastOffset = offset;
    610                         }
    611                         if (field != null) {
    612                             fieldOffset = lastOffset + getFieldSize(field);
    613                         }
    614                     }
    615 
    616                     for (Field field: fields) {
    617                         int fieldSize = getFieldSize(field);
    618 
    619                         if (!AlignmentUtils.isAligned(fieldOffset, fieldSize)) {
    620                             int oldOffset = fieldOffset;
    621                             fieldOffset = AlignmentUtils.alignOffset(fieldOffset, fieldSize);
    622                             addFieldGap(oldOffset, fieldOffset, gaps);
    623                         }
    624 
    625                         FieldGap gap = gaps.peek();
    626                         if (gap != null && gap.size >= fieldSize) {
    627                             gaps.poll();
    628                             linkedFields.put(gap.offset, field);
    629                             if (gap.size > fieldSize) {
    630                                 addFieldGap(gap.offset + fieldSize, gap.offset + gap.size, gaps);
    631                             }
    632                         } else {
    633                             linkedFields.append(fieldOffset, field);
    634                             fieldOffset += fieldSize;
    635                         }
    636                     }
    637 
    638                     return linkedFields;
    639                 }
    640 
    641                 private void addFieldGap(int gapStart, int gapEnd, @Nonnull PriorityQueue<FieldGap> gaps) {
    642                     int offset = gapStart;
    643 
    644                     while (offset < gapEnd) {
    645                         int remaining = gapEnd - offset;
    646 
    647                         if ((remaining >= 4) && (offset % 4 == 0)) {
    648                             gaps.add(FieldGap.newFieldGap(offset, 4, classPath.oatVersion));
    649                             offset += 4;
    650                         } else if (remaining >= 2 && (offset % 2 == 0)) {
    651                             gaps.add(FieldGap.newFieldGap(offset, 2, classPath.oatVersion));
    652                             offset += 2;
    653                         } else {
    654                             gaps.add(FieldGap.newFieldGap(offset, 1, classPath.oatVersion));
    655                             offset += 1;
    656                         }
    657                     }
    658                 }
    659 
    660                 @Nonnull
    661                 private ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
    662                     ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
    663                     Collections.sort(fields, new Comparator<Field>() {
    664                         @Override public int compare(Field field1, Field field2) {
    665                             int result = Ints.compare(getFieldSortOrder(field1), getFieldSortOrder(field2));
    666                             if (result != 0) {
    667                                 return result;
    668                             }
    669 
    670                             result = field1.getName().compareTo(field2.getName());
    671                             if (result != 0) {
    672                                 return result;
    673                             }
    674                             return field1.getType().compareTo(field2.getType());
    675                         }
    676                     });
    677                     return fields;
    678                 }
    679 
    680                 private int getFieldSortOrder(@Nonnull FieldReference field) {
    681                     // The sort order is based on type size (except references are first), and then based on the
    682                     // enum value of the primitive type for types of equal size. See: Primitive::Type enum
    683                     // in art/runtime/primitive.h
    684                     switch (field.getType().charAt(0)) {
    685                         /* reference */
    686                         case '[':
    687                         case 'L':
    688                             return 0;
    689                         /* 64 bit */
    690                         case 'J':
    691                             return 1;
    692                         case 'D':
    693                             return 2;
    694                         /* 32 bit */
    695                         case 'I':
    696                             return 3;
    697                         case 'F':
    698                             return 4;
    699                         /* 16 bit */
    700                         case 'C':
    701                             return 5;
    702                         case 'S':
    703                             return 6;
    704                         /* 8 bit */
    705                         case 'Z':
    706                             return 7;
    707                         case 'B':
    708                             return 8;
    709                     }
    710                     throw new ExceptionWithContext("Invalid field type: %s", field.getType());
    711                 }
    712 
    713                 private int getFieldSize(@Nonnull FieldReference field) {
    714                     return getTypeSize(field.getType().charAt(0));
    715                 }
    716             });
    717 
    718     private int getNextFieldOffset() {
    719         SparseArray<FieldReference> instanceFields = getInstanceFields();
    720         if (instanceFields.size() == 0) {
    721             return classPath.isArt() ? 0 : 8;
    722         }
    723 
    724         int lastItemIndex = instanceFields.size()-1;
    725         int fieldOffset = instanceFields.keyAt(lastItemIndex);
    726         FieldReference lastField = instanceFields.valueAt(lastItemIndex);
    727 
    728         if (classPath.isArt()) {
    729             return fieldOffset + getTypeSize(lastField.getType().charAt(0));
    730         } else {
    731             switch (lastField.getType().charAt(0)) {
    732                 case 'J':
    733                 case 'D':
    734                     return fieldOffset + 8;
    735                 default:
    736                     return fieldOffset + 4;
    737             }
    738         }
    739     }
    740 
    741     private static int getTypeSize(char type) {
    742         switch (type) {
    743             case 'J':
    744             case 'D':
    745                 return 8;
    746             case '[':
    747             case 'L':
    748             case 'I':
    749             case 'F':
    750                 return 4;
    751             case 'C':
    752             case 'S':
    753                 return 2;
    754             case 'B':
    755             case 'Z':
    756                 return 1;
    757         }
    758         throw new ExceptionWithContext("Invalid type: %s", type);
    759     }
    760 
    761     @Nonnull List<Method> getVtable() {
    762         return vtableSupplier.get();
    763     }
    764 
    765     //TODO: check the case when we have a package private method that overrides an interface method
    766     @Nonnull private final Supplier<List<Method>> vtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
    767         @Override public List<Method> get() {
    768             List<Method> vtable = Lists.newArrayList();
    769 
    770             //copy the virtual methods from the superclass
    771             String superclassType;
    772             try {
    773                 superclassType = getSuperclass();
    774             } catch (UnresolvedClassException ex) {
    775                 vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
    776                 vtableFullyResolved = false;
    777                 return vtable;
    778             }
    779 
    780             if (superclassType != null) {
    781                 ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
    782                 vtable.addAll(superclass.getVtable());
    783 
    784                 // if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this
    785                 // class should start, so we just propagate what we can from the parent and hope for the best.
    786                 if (!superclass.vtableFullyResolved) {
    787                     vtableFullyResolved = false;
    788                     return vtable;
    789                 }
    790             }
    791 
    792             //iterate over the virtual methods in the current class, and only add them when we don't already have the
    793             //method (i.e. if it was implemented by the superclass)
    794             if (!isInterface()) {
    795                 addToVtable(getClassDef().getVirtualMethods(), vtable, true);
    796 
    797                 // assume that interface method is implemented in the current class, when adding it to vtable
    798                 // otherwise it looks like that method is invoked on an interface, which fails Dalvik's optimization checks
    799                 for (ClassDef interfaceDef: getDirectInterfaces()) {
    800                     List<Method> interfaceMethods = Lists.newArrayList();
    801                     for (Method interfaceMethod: interfaceDef.getVirtualMethods()) {
    802                         ImmutableMethod method = new ImmutableMethod(
    803                                 type,
    804                                 interfaceMethod.getName(),
    805                                 interfaceMethod.getParameters(),
    806                                 interfaceMethod.getReturnType(),
    807                                 interfaceMethod.getAccessFlags(),
    808                                 interfaceMethod.getAnnotations(),
    809                                 interfaceMethod.getImplementation());
    810                         interfaceMethods.add(method);
    811                     }
    812                     addToVtable(interfaceMethods, vtable, false);
    813                 }
    814             }
    815             return vtable;
    816         }
    817 
    818         private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
    819                                  @Nonnull List<Method> vtable, boolean replaceExisting) {
    820             List<? extends Method> methods = Lists.newArrayList(localMethods);
    821             Collections.sort(methods);
    822 
    823             outer: for (Method virtualMethod: methods) {
    824                 for (int i=0; i<vtable.size(); i++) {
    825                     Method superMethod = vtable.get(i);
    826                     if (MethodUtil.methodSignaturesMatch(superMethod, virtualMethod)) {
    827                         if (!classPath.shouldCheckPackagePrivateAccess() ||
    828                                 AnalyzedMethodUtil.canAccess(ClassProto.this, superMethod, true, false, false)) {
    829                             if (replaceExisting) {
    830                                 vtable.set(i, virtualMethod);
    831                             }
    832                             continue outer;
    833                         }
    834                     }
    835                 }
    836                 // we didn't find an equivalent method, so add it as a new entry
    837                 vtable.add(virtualMethod);
    838             }
    839         }
    840     });
    841 
    842     private static byte getFieldType(@Nonnull FieldReference field) {
    843         switch (field.getType().charAt(0)) {
    844             case '[':
    845             case 'L':
    846                 return 0; //REFERENCE
    847             case 'J':
    848             case 'D':
    849                 return 1; //WIDE
    850             default:
    851                 return 2; //OTHER
    852         }
    853     }
    854 }
    855