blob: cc8a53c97fde92f8ca47dfd5efcf930f196a0fd8 [file] [log] [blame]
/*
* Copyright 2013, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jf.dexlib2.analysis;
import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.*;
import com.google.common.primitives.Ints;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
import org.jf.dexlib2.base.reference.BaseMethodReference;
import org.jf.dexlib2.iface.*;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.util.MethodUtil;
import org.jf.util.AlignmentUtils;
import org.jf.util.ExceptionWithContext;
import org.jf.util.SparseArray;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.Map.Entry;
/**
* A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields
* and their offsets.
*/
public class ClassProto implements TypeProto {
private static final byte REFERENCE = 0;
private static final byte WIDE = 1;
private static final byte OTHER = 2;
@Nonnull protected final ClassPath classPath;
@Nonnull protected final String type;
protected boolean vtableFullyResolved = true;
protected boolean interfacesFullyResolved = true;
protected Set<String> unresolvedInterfaces = null;
public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) {
if (type.charAt(0) != 'L') {
throw new ExceptionWithContext("Cannot construct ClassProto for non reference type: %s", type);
}
this.classPath = classPath;
this.type = type;
}
@Override public String toString() { return type; }
@Nonnull @Override public ClassPath getClassPath() { return classPath; }
@Nonnull @Override public String getType() { return type; }
@Nonnull
public ClassDef getClassDef() {
return classDefSupplier.get();
}
@Nonnull private final Supplier<ClassDef> classDefSupplier = Suppliers.memoize(new Supplier<ClassDef>() {
@Override public ClassDef get() {
return classPath.getClassDef(type);
}
});
/**
* Returns true if this class is an interface.
*
* If this class is not defined, then this will throw an UnresolvedClassException
*
* @return True if this class is an interface
*/
public boolean isInterface() {
ClassDef classDef = getClassDef();
return (classDef.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
}
/**
* Returns the set of interfaces that this class implements as a Map<String, ClassDef>.
*
* The ClassDef value will be present only for the interfaces that this class directly implements (including any
* interfaces transitively implemented), but not for any interfaces that are only implemented by a superclass of
* this class
*
* For any interfaces that are only implemented by a superclass (or the class itself, if the class is an interface),
* the value will be null.
*
* If any interface couldn't be resolved, then the interfacesFullyResolved field will be set to false upon return.
*
* @return the set of interfaces that this class implements as a Map<String, ClassDef>.
*/
@Nonnull
protected LinkedHashMap<String, ClassDef> getInterfaces() {
if (!classPath.isArt() || classPath.oatVersion < 72) {
return preDefaultMethodInterfaceSupplier.get();
} else {
return postDefaultMethodInterfaceSupplier.get();
}
}
/**
* This calculates the interfaces in the order required for vtable generation for dalvik and pre-default method ART
*/
@Nonnull
private final Supplier<LinkedHashMap<String, ClassDef>> preDefaultMethodInterfaceSupplier =
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
@Override public LinkedHashMap<String, ClassDef> get() {
Set<String> unresolvedInterfaces = new HashSet<String>(0);
LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
try {
for (String interfaceType: getClassDef().getInterfaces()) {
if (!interfaces.containsKey(interfaceType)) {
ClassDef interfaceDef;
try {
interfaceDef = classPath.getClassDef(interfaceType);
interfaces.put(interfaceType, interfaceDef);
} catch (UnresolvedClassException ex) {
interfaces.put(interfaceType, null);
unresolvedInterfaces.add(interfaceType);
interfacesFullyResolved = false;
}
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
for (String superInterface: interfaceProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superInterface)) {
interfaces.put(superInterface,
interfaceProto.getInterfaces().get(superInterface));
}
}
if (!interfaceProto.interfacesFullyResolved) {
unresolvedInterfaces.addAll(interfaceProto.getUnresolvedInterfaces());
interfacesFullyResolved = false;
}
}
}
} catch (UnresolvedClassException ex) {
interfaces.put(type, null);
unresolvedInterfaces.add(type);
interfacesFullyResolved = false;
}
// now add self and super class interfaces, required for common super class lookup
// we don't really need ClassDef's for that, so let's just use null
if (isInterface() && !interfaces.containsKey(getType())) {
interfaces.put(getType(), null);
}
String superclass = getSuperclass();
try {
if (superclass != null) {
ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superclassInterface)) {
interfaces.put(superclassInterface, null);
}
}
if (!superclassProto.interfacesFullyResolved) {
unresolvedInterfaces.addAll(superclassProto.getUnresolvedInterfaces());
interfacesFullyResolved = false;
}
}
} catch (UnresolvedClassException ex) {
unresolvedInterfaces.add(superclass);
interfacesFullyResolved = false;
}
if (unresolvedInterfaces.size() > 0) {
ClassProto.this.unresolvedInterfaces = unresolvedInterfaces;
}
return interfaces;
}
});
/**
* This calculates the interfaces in the order required for vtable generation for post-default method ART
*/
@Nonnull
private final Supplier<LinkedHashMap<String, ClassDef>> postDefaultMethodInterfaceSupplier =
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
@Override public LinkedHashMap<String, ClassDef> get() {
Set<String> unresolvedInterfaces = new HashSet<String>(0);
LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
String superclass = getSuperclass();
if (superclass != null) {
ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
interfaces.put(superclassInterface, null);
}
if (!superclassProto.interfacesFullyResolved) {
unresolvedInterfaces.addAll(superclassProto.getUnresolvedInterfaces());
interfacesFullyResolved = false;
}
}
try {
for (String interfaceType: getClassDef().getInterfaces()) {
if (!interfaces.containsKey(interfaceType)) {
ClassProto interfaceProto = (ClassProto)classPath.getClass(interfaceType);
try {
for (Entry<String, ClassDef> entry: interfaceProto.getInterfaces().entrySet()) {
if (!interfaces.containsKey(entry.getKey())) {
interfaces.put(entry.getKey(), entry.getValue());
}
}
} catch (UnresolvedClassException ex) {
interfaces.put(interfaceType, null);
unresolvedInterfaces.add(interfaceType);
interfacesFullyResolved = false;
}
if (!interfaceProto.interfacesFullyResolved) {
unresolvedInterfaces.addAll(interfaceProto.getUnresolvedInterfaces());
interfacesFullyResolved = false;
}
try {
ClassDef interfaceDef = classPath.getClassDef(interfaceType);
interfaces.put(interfaceType, interfaceDef);
} catch (UnresolvedClassException ex) {
interfaces.put(interfaceType, null);
unresolvedInterfaces.add(interfaceType);
interfacesFullyResolved = false;
}
}
}
} catch (UnresolvedClassException ex) {
interfaces.put(type, null);
unresolvedInterfaces.add(type);
interfacesFullyResolved = false;
}
if (unresolvedInterfaces.size() > 0) {
ClassProto.this.unresolvedInterfaces = unresolvedInterfaces;
}
return interfaces;
}
});
@Nonnull
protected Set<String> getUnresolvedInterfaces() {
if (unresolvedInterfaces == null) {
return ImmutableSet.of();
}
return unresolvedInterfaces;
}
/**
* Gets the interfaces directly implemented by this class, or the interfaces they transitively implement.
*
* This does not include any interfaces that are only implemented by a superclass
*
* @return An iterables of ClassDefs representing the directly or transitively implemented interfaces
* @throws UnresolvedClassException if interfaces could not be fully resolved
*/
@Nonnull
protected Iterable<ClassDef> getDirectInterfaces() {
Iterable<ClassDef> directInterfaces =
FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull());
if (!interfacesFullyResolved) {
throw new UnresolvedClassException("Interfaces for class %s not fully resolved: %s", getType(),
Joiner.on(',').join(getUnresolvedInterfaces()));
}
return directInterfaces;
}
/**
* Checks if this class implements the given interface.
*
* If the interfaces of this class cannot be fully resolved then this
* method will either return true or throw an UnresolvedClassException
*
* @param iface The interface to check for
* @return true if this class implements the given interface, otherwise false
* @throws UnresolvedClassException if the interfaces for this class could not be fully resolved, and the interface
* is not one of the interfaces that were successfully resolved
*/
@Override
public boolean implementsInterface(@Nonnull String iface) {
if (getInterfaces().containsKey(iface)) {
return true;
}
if (!interfacesFullyResolved) {
throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
}
return false;
}
@Nullable @Override
public String getSuperclass() {
return getClassDef().getSuperclass();
}
/**
* This is a helper method for getCommonSuperclass
*
* It checks if this class is an interface, and if so, if other implements it.
*
* If this class is undefined, we go ahead and check if it is listed in other's interfaces. If not, we throw an
* UndefinedClassException
*
* If the interfaces of other cannot be fully resolved, we check the interfaces that can be resolved. If not found,
* we throw an UndefinedClassException
*
* @param other The class to check the interfaces of
* @return true if this class is an interface (or is undefined) other implements this class
*
*/
private boolean checkInterface(@Nonnull ClassProto other) {
boolean isResolved = true;
boolean isInterface = true;
try {
isInterface = isInterface();
} catch (UnresolvedClassException ex) {
isResolved = false;
// if we don't know if this class is an interface or not,
// we can still try to call other.implementsInterface(this)
}
if (isInterface) {
try {
if (other.implementsInterface(getType())) {
return true;
}
} catch (UnresolvedClassException ex) {
// There are 2 possibilities here, depending on whether we were able to resolve this class.
// 1. If this class is resolved, then we know it is an interface class. The other class either
// isn't defined, or its interfaces couldn't be fully resolved.
// In this case, we throw an UnresolvedClassException
// 2. If this class is not resolved, we had tried to call implementsInterface anyway. We don't
// know for sure if this class is an interface or not. We return false, and let processing
// continue in getCommonSuperclass
if (isResolved) {
throw ex;
}
}
}
return false;
}
@Override @Nonnull
public TypeProto getCommonSuperclass(@Nonnull TypeProto other) {
// use the other type's more specific implementation
if (!(other instanceof ClassProto)) {
return other.getCommonSuperclass(this);
}
if (this == other || getType().equals(other.getType())) {
return this;
}
if (this.getType().equals("Ljava/lang/Object;")) {
return this;
}
if (other.getType().equals("Ljava/lang/Object;")) {
return other;
}
boolean gotException = false;
try {
if (checkInterface((ClassProto)other)) {
return this;
}
} catch (UnresolvedClassException ex) {
gotException = true;
}
try {
if (((ClassProto)other).checkInterface(this)) {
return other;
}
} catch (UnresolvedClassException ex) {
gotException = true;
}
if (gotException) {
return classPath.getUnknownClass();
}
List<TypeProto> thisChain = Lists.<TypeProto>newArrayList(this);
Iterables.addAll(thisChain, TypeProtoUtils.getSuperclassChain(this));
List<TypeProto> otherChain = Lists.newArrayList(other);
Iterables.addAll(otherChain, TypeProtoUtils.getSuperclassChain(other));
// reverse them, so that the first entry is either Ljava/lang/Object; or Ujava/lang/Object;
thisChain = Lists.reverse(thisChain);
otherChain = Lists.reverse(otherChain);
for (int i=Math.min(thisChain.size(), otherChain.size())-1; i>=0; i--) {
TypeProto typeProto = thisChain.get(i);
if (typeProto.getType().equals(otherChain.get(i).getType())) {
return typeProto;
}
}
return classPath.getUnknownClass();
}
@Override
@Nullable
public FieldReference getFieldByOffset(int fieldOffset) {
if (getInstanceFields().size() == 0) {
return null;
}
return getInstanceFields().get(fieldOffset);
}
@Override
@Nullable
public Method getMethodByVtableIndex(int vtableIndex) {
List<Method> vtable = getVtable();
if (vtableIndex < 0 || vtableIndex >= vtable.size()) {
return null;
}
return vtable.get(vtableIndex);
}
public int findMethodIndexInVtable(@Nonnull MethodReference method) {
return findMethodIndexInVtable(getVtable(), method);
}
private int findMethodIndexInVtable(@Nonnull List<Method> vtable, MethodReference method) {
for (int i=0; i<vtable.size(); i++) {
Method candidate = vtable.get(i);
if (MethodUtil.methodSignaturesMatch(candidate, method)) {
if (!classPath.shouldCheckPackagePrivateAccess() ||
AnalyzedMethodUtil.canAccess(this, candidate, true, false, false)) {
return i;
}
}
}
return -1;
}
private int findMethodIndexInVtableReverse(@Nonnull List<Method> vtable, MethodReference method) {
for (int i=vtable.size() - 1; i>=0; i--) {
Method candidate = vtable.get(i);
if (MethodUtil.methodSignaturesMatch(candidate, method)) {
if (!classPath.shouldCheckPackagePrivateAccess() ||
AnalyzedMethodUtil.canAccess(this, candidate, true, false, false)) {
return i;
}
}
}
return -1;
}
@Nonnull public SparseArray<FieldReference> getInstanceFields() {
if (classPath.isArt()) {
return artInstanceFieldsSupplier.get();
} else {
return dalvikInstanceFieldsSupplier.get();
}
}
@Nonnull private final Supplier<SparseArray<FieldReference>> dalvikInstanceFieldsSupplier =
Suppliers.memoize(new Supplier<SparseArray<FieldReference>>() {
@Override public SparseArray<FieldReference> get() {
//This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
//arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
//See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
final int fieldCount = fields.size();
//the "type" for each field in fields. 0=reference,1=wide,2=other
byte[] fieldTypes = new byte[fields.size()];
for (int i=0; i<fieldCount; i++) {
fieldTypes[i] = getFieldType(fields.get(i));
}
//The first operation is to move all of the reference fields to the front. To do this, find the first
//non-reference field, then find the last reference field, swap them and repeat
int back = fields.size() - 1;
int front;
for (front = 0; front<fieldCount; front++) {
if (fieldTypes[front] != REFERENCE) {
while (back > front) {
if (fieldTypes[back] == REFERENCE) {
swap(fieldTypes, fields, front, back--);
break;
}
back--;
}
}
if (fieldTypes[front] != REFERENCE) {
break;
}
}
int startFieldOffset = 8;
String superclassType = getSuperclass();
ClassProto superclass = null;
if (superclassType != null) {
superclass = (ClassProto) classPath.getClass(superclassType);
startFieldOffset = superclass.getNextFieldOffset();
}
int fieldIndexMod;
if ((startFieldOffset % 8) == 0) {
fieldIndexMod = 0;
} else {
fieldIndexMod = 1;
}
//next, we need to group all the wide fields after the reference fields. But the wide fields have to be
//8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
//is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
//If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
if (front < fieldCount && (front % 2) != fieldIndexMod) {
if (fieldTypes[front] == WIDE) {
//we need to swap in a 32-bit field, so the wide fields will be correctly aligned
back = fieldCount - 1;
while (back > front) {
if (fieldTypes[back] == OTHER) {
swap(fieldTypes, fields, front++, back);
break;
}
back--;
}
} else {
//there's already a 32-bit field here that we can use
front++;
}
}
//do the swap thing for wide fields
back = fieldCount - 1;
for (; front<fieldCount; front++) {
if (fieldTypes[front] != WIDE) {
while (back > front) {
if (fieldTypes[back] == WIDE) {
swap(fieldTypes, fields, front, back--);
break;
}
back--;
}
}
if (fieldTypes[front] != WIDE) {
break;
}
}
SparseArray<FieldReference> superFields;
if (superclass != null) {
superFields = superclass.getInstanceFields();
} else {
superFields = new SparseArray<FieldReference>();
}
int superFieldCount = superFields.size();
//now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
int totalFieldCount = superFieldCount + fieldCount;
SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
int fieldOffset;
if (superclass != null && superFieldCount > 0) {
for (int i=0; i<superFieldCount; i++) {
instanceFields.append(superFields.keyAt(i), superFields.valueAt(i));
}
fieldOffset = instanceFields.keyAt(superFieldCount-1);
FieldReference lastSuperField = superFields.valueAt(superFieldCount-1);
char fieldType = lastSuperField.getType().charAt(0);
if (fieldType == 'J' || fieldType == 'D') {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
} else {
//the field values start at 8 bytes into the DataObject dalvik structure
fieldOffset = 8;
}
boolean gotDouble = false;
for (int i=0; i<fieldCount; i++) {
FieldReference field = fields.get(i);
//add padding to align the wide fields, if needed
if (fieldTypes[i] == WIDE && !gotDouble) {
if (fieldOffset % 8 != 0) {
assert fieldOffset % 8 == 4;
fieldOffset += 4;
}
gotDouble = true;
}
instanceFields.append(fieldOffset, field);
if (fieldTypes[i] == WIDE) {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
}
return instanceFields;
}
@Nonnull
private ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
Collections.sort(fields);
return fields;
}
private void swap(byte[] fieldTypes, List<Field> fields, int position1, int position2) {
byte tempType = fieldTypes[position1];
fieldTypes[position1] = fieldTypes[position2];
fieldTypes[position2] = tempType;
Field tempField = fields.set(position1, fields.get(position2));
fields.set(position2, tempField);
}
});
private static abstract class FieldGap implements Comparable<FieldGap> {
public final int offset;
public final int size;
public static FieldGap newFieldGap(int offset, int size, int oatVersion) {
if (oatVersion >= 67) {
return new FieldGap(offset, size) {
@Override public int compareTo(@Nonnull FieldGap o) {
int result = Ints.compare(o.size, size);
if (result != 0) {
return result;
}
return Ints.compare(offset, o.offset);
}
};
} else {
return new FieldGap(offset, size) {
@Override public int compareTo(@Nonnull FieldGap o) {
int result = Ints.compare(size, o.size);
if (result != 0) {
return result;
}
return Ints.compare(o.offset, offset);
}
};
}
}
private FieldGap(int offset, int size) {
this.offset = offset;
this.size = size;
}
}
@Nonnull private final Supplier<SparseArray<FieldReference>> artInstanceFieldsSupplier =
Suppliers.memoize(new Supplier<SparseArray<FieldReference>>() {
@Override public SparseArray<FieldReference> get() {
// We need to follow the same algorithm that art uses to arrange fields, so that we end up with the
// same field offsets, which is needed for deodexing.
// See LinkFields() in art/runtime/class_linker.cc
PriorityQueue<FieldGap> gaps = new PriorityQueue<FieldGap>();
SparseArray<FieldReference> linkedFields = new SparseArray<FieldReference>();
ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
int fieldOffset = 0;
String superclassType = getSuperclass();
if (superclassType != null) {
// TODO: what to do if superclass doesn't exist?
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
SparseArray<FieldReference> superFields = superclass.getInstanceFields();
FieldReference field = null;
int lastOffset = 0;
for (int i=0; i<superFields.size(); i++) {
int offset = superFields.keyAt(i);
field = superFields.valueAt(i);
linkedFields.put(offset, field);
lastOffset = offset;
}
if (field != null) {
fieldOffset = lastOffset + getFieldSize(field);
}
}
for (Field field: fields) {
int fieldSize = getFieldSize(field);
if (!AlignmentUtils.isAligned(fieldOffset, fieldSize)) {
int oldOffset = fieldOffset;
fieldOffset = AlignmentUtils.alignOffset(fieldOffset, fieldSize);
addFieldGap(oldOffset, fieldOffset, gaps);
}
FieldGap gap = gaps.peek();
if (gap != null && gap.size >= fieldSize) {
gaps.poll();
linkedFields.put(gap.offset, field);
if (gap.size > fieldSize) {
addFieldGap(gap.offset + fieldSize, gap.offset + gap.size, gaps);
}
} else {
linkedFields.append(fieldOffset, field);
fieldOffset += fieldSize;
}
}
return linkedFields;
}
private void addFieldGap(int gapStart, int gapEnd, @Nonnull PriorityQueue<FieldGap> gaps) {
int offset = gapStart;
while (offset < gapEnd) {
int remaining = gapEnd - offset;
if ((remaining >= 4) && (offset % 4 == 0)) {
gaps.add(FieldGap.newFieldGap(offset, 4, classPath.oatVersion));
offset += 4;
} else if (remaining >= 2 && (offset % 2 == 0)) {
gaps.add(FieldGap.newFieldGap(offset, 2, classPath.oatVersion));
offset += 2;
} else {
gaps.add(FieldGap.newFieldGap(offset, 1, classPath.oatVersion));
offset += 1;
}
}
}
@Nonnull
private ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
Collections.sort(fields, new Comparator<Field>() {
@Override public int compare(Field field1, Field field2) {
int result = Ints.compare(getFieldSortOrder(field1), getFieldSortOrder(field2));
if (result != 0) {
return result;
}
result = field1.getName().compareTo(field2.getName());
if (result != 0) {
return result;
}
return field1.getType().compareTo(field2.getType());
}
});
return fields;
}
private int getFieldSortOrder(@Nonnull FieldReference field) {
// The sort order is based on type size (except references are first), and then based on the
// enum value of the primitive type for types of equal size. See: Primitive::Type enum
// in art/runtime/primitive.h
switch (field.getType().charAt(0)) {
/* reference */
case '[':
case 'L':
return 0;
/* 64 bit */
case 'J':
return 1;
case 'D':
return 2;
/* 32 bit */
case 'I':
return 3;
case 'F':
return 4;
/* 16 bit */
case 'C':
return 5;
case 'S':
return 6;
/* 8 bit */
case 'Z':
return 7;
case 'B':
return 8;
}
throw new ExceptionWithContext("Invalid field type: %s", field.getType());
}
private int getFieldSize(@Nonnull FieldReference field) {
return getTypeSize(field.getType().charAt(0));
}
});
private int getNextFieldOffset() {
SparseArray<FieldReference> instanceFields = getInstanceFields();
if (instanceFields.size() == 0) {
return classPath.isArt() ? 0 : 8;
}
int lastItemIndex = instanceFields.size()-1;
int fieldOffset = instanceFields.keyAt(lastItemIndex);
FieldReference lastField = instanceFields.valueAt(lastItemIndex);
if (classPath.isArt()) {
return fieldOffset + getTypeSize(lastField.getType().charAt(0));
} else {
switch (lastField.getType().charAt(0)) {
case 'J':
case 'D':
return fieldOffset + 8;
default:
return fieldOffset + 4;
}
}
}
private static int getTypeSize(char type) {
switch (type) {
case 'J':
case 'D':
return 8;
case '[':
case 'L':
case 'I':
case 'F':
return 4;
case 'C':
case 'S':
return 2;
case 'B':
case 'Z':
return 1;
}
throw new ExceptionWithContext("Invalid type: %s", type);
}
@Nonnull public List<Method> getVtable() {
if (!classPath.isArt() || classPath.oatVersion < 72) {
return preDefaultMethodVtableSupplier.get();
} else if (classPath.oatVersion < 87) {
return buggyPostDefaultMethodVtableSupplier.get();
} else {
return postDefaultMethodVtableSupplier.get();
}
}
//TODO: check the case when we have a package private method that overrides an interface method
@Nonnull private final Supplier<List<Method>> preDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
@Override public List<Method> get() {
List<Method> vtable = Lists.newArrayList();
//copy the virtual methods from the superclass
String superclassType;
try {
superclassType = getSuperclass();
} catch (UnresolvedClassException ex) {
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
vtableFullyResolved = false;
return vtable;
}
if (superclassType != null) {
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
vtable.addAll(superclass.getVtable());
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this
// class should start, so we just propagate what we can from the parent and hope for the best.
if (!superclass.vtableFullyResolved) {
vtableFullyResolved = false;
return vtable;
}
}
//iterate over the virtual methods in the current class, and only add them when we don't already have the
//method (i.e. if it was implemented by the superclass)
if (!isInterface()) {
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
// We use the current class for any vtable method references that we add, rather than the interface, so
// we don't end up trying to call invoke-virtual using an interface, which will fail verification
Iterable<ClassDef> interfaces = getDirectInterfaces();
for (ClassDef interfaceDef: interfaces) {
List<Method> interfaceMethods = Lists.newArrayList();
for (Method interfaceMethod: interfaceDef.getVirtualMethods()) {
interfaceMethods.add(new ReparentedMethod(interfaceMethod, type));
}
addToVtable(interfaceMethods, vtable, false, true);
}
}
return vtable;
}
});
/**
* This is the vtable supplier for a version of art that had buggy vtable calculation logic. In some cases it can
* produce multiple vtable entries for a given virtual method. This supplier duplicates this buggy logic in order to
* generate an identical vtable
*/
@Nonnull private final Supplier<List<Method>> buggyPostDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
@Override public List<Method> get() {
List<Method> vtable = Lists.newArrayList();
//copy the virtual methods from the superclass
String superclassType;
try {
superclassType = getSuperclass();
} catch (UnresolvedClassException ex) {
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
vtableFullyResolved = false;
return vtable;
}
if (superclassType != null) {
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
vtable.addAll(superclass.getVtable());
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by
// this class should start, so we just propagate what we can from the parent and hope for the best.
if (!superclass.vtableFullyResolved) {
vtableFullyResolved = false;
return vtable;
}
}
//iterate over the virtual methods in the current class, and only add them when we don't already have the
//method (i.e. if it was implemented by the superclass)
if (!isInterface()) {
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
List<String> interfaces = Lists.newArrayList(getInterfaces().keySet());
List<Method> defaultMethods = Lists.newArrayList();
List<Method> defaultConflictMethods = Lists.newArrayList();
List<Method> mirandaMethods = Lists.newArrayList();
final HashMap<MethodReference, Integer> methodOrder = Maps.newHashMap();
for (int i=interfaces.size()-1; i>=0; i--) {
String interfaceType = interfaces.get(i);
ClassDef interfaceDef = classPath.getClassDef(interfaceType);
for (Method interfaceMethod : interfaceDef.getVirtualMethods()) {
int vtableIndex = findMethodIndexInVtableReverse(vtable, interfaceMethod);
Method oldVtableMethod = null;
if (vtableIndex >= 0) {
oldVtableMethod = vtable.get(vtableIndex);
}
for (int j=0; j<vtable.size(); j++) {
Method candidate = vtable.get(j);
if (MethodUtil.methodSignaturesMatch(candidate, interfaceMethod)) {
if (!classPath.shouldCheckPackagePrivateAccess() ||
AnalyzedMethodUtil.canAccess(ClassProto.this, candidate, true, false, false)) {
if (interfaceMethodOverrides(interfaceMethod, candidate)) {
vtable.set(j, interfaceMethod);
}
}
}
}
if (vtableIndex >= 0) {
if (!isOverridableByDefaultMethod(vtable.get(vtableIndex))) {
continue;
}
}
int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod);
if (defaultMethodIndex >= 0) {
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
ClassProto existingInterface = (ClassProto)classPath.getClass(
defaultMethods.get(defaultMethodIndex).getDefiningClass());
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
Method removedMethod = defaultMethods.remove(defaultMethodIndex);
defaultConflictMethods.add(removedMethod);
}
}
continue;
}
int defaultConflictMethodIndex = findMethodIndexInVtable(
defaultConflictMethods, interfaceMethod);
if (defaultConflictMethodIndex >= 0) {
// There's already a matching method in the conflict list, we don't need to do
// anything else
continue;
}
int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod);
if (mirandaMethodIndex >= 0) {
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
ClassProto existingInterface = (ClassProto)classPath.getClass(
mirandaMethods.get(mirandaMethodIndex).getDefiningClass());
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
Method oldMethod = mirandaMethods.remove(mirandaMethodIndex);
int methodOrderValue = methodOrder.get(oldMethod);
methodOrder.put(interfaceMethod, methodOrderValue);
defaultMethods.add(interfaceMethod);
}
}
continue;
}
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
if (oldVtableMethod != null) {
if (!interfaceMethodOverrides(interfaceMethod, oldVtableMethod)) {
continue;
}
}
defaultMethods.add(interfaceMethod);
methodOrder.put(interfaceMethod, methodOrder.size());
} else {
// TODO: do we need to check interfaceMethodOverrides here?
if (oldVtableMethod == null) {
mirandaMethods.add(interfaceMethod);
methodOrder.put(interfaceMethod, methodOrder.size());
}
}
}
}
Comparator<MethodReference> comparator = new Comparator<MethodReference>() {
@Override public int compare(MethodReference o1, MethodReference o2) {
return Ints.compare(methodOrder.get(o1), methodOrder.get(o2));
}
};
// The methods should be in the same order within each list as they were iterated over.
// They can be misordered if, e.g. a method was originally added to the default list, but then moved
// to the conflict list.
Collections.sort(mirandaMethods, comparator);
Collections.sort(defaultMethods, comparator);
Collections.sort(defaultConflictMethods, comparator);
vtable.addAll(mirandaMethods);
vtable.addAll(defaultMethods);
vtable.addAll(defaultConflictMethods);
}
return vtable;
}
});
@Nonnull private final Supplier<List<Method>> postDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
@Override public List<Method> get() {
List<Method> vtable = Lists.newArrayList();
//copy the virtual methods from the superclass
String superclassType;
try {
superclassType = getSuperclass();
} catch (UnresolvedClassException ex) {
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
vtableFullyResolved = false;
return vtable;
}
if (superclassType != null) {
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
vtable.addAll(superclass.getVtable());
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by
// this class should start, so we just propagate what we can from the parent and hope for the best.
if (!superclass.vtableFullyResolved) {
vtableFullyResolved = false;
return vtable;
}
}
//iterate over the virtual methods in the current class, and only add them when we don't already have the
//method (i.e. if it was implemented by the superclass)
if (!isInterface()) {
addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
Iterable<ClassDef> interfaces = Lists.reverse(Lists.newArrayList(getDirectInterfaces()));
List<Method> defaultMethods = Lists.newArrayList();
List<Method> defaultConflictMethods = Lists.newArrayList();
List<Method> mirandaMethods = Lists.newArrayList();
final HashMap<MethodReference, Integer> methodOrder = Maps.newHashMap();
for (ClassDef interfaceDef: interfaces) {
for (Method interfaceMethod : interfaceDef.getVirtualMethods()) {
int vtableIndex = findMethodIndexInVtable(vtable, interfaceMethod);
if (vtableIndex >= 0) {
if (interfaceMethodOverrides(interfaceMethod, vtable.get(vtableIndex))) {
vtable.set(vtableIndex, interfaceMethod);
}
} else {
int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod);
if (defaultMethodIndex >= 0) {
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
ClassProto existingInterface = (ClassProto)classPath.getClass(
defaultMethods.get(defaultMethodIndex).getDefiningClass());
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
Method removedMethod = defaultMethods.remove(defaultMethodIndex);
defaultConflictMethods.add(removedMethod);
}
}
continue;
}
int defaultConflictMethodIndex = findMethodIndexInVtable(
defaultConflictMethods, interfaceMethod);
if (defaultConflictMethodIndex >= 0) {
// There's already a matching method in the conflict list, we don't need to do
// anything else
continue;
}
int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod);
if (mirandaMethodIndex >= 0) {
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
ClassProto existingInterface = (ClassProto)classPath.getClass(
mirandaMethods.get(mirandaMethodIndex).getDefiningClass());
if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
Method oldMethod = mirandaMethods.remove(mirandaMethodIndex);
int methodOrderValue = methodOrder.get(oldMethod);
methodOrder.put(interfaceMethod, methodOrderValue);
defaultMethods.add(interfaceMethod);
}
}
continue;
}
if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
defaultMethods.add(interfaceMethod);
methodOrder.put(interfaceMethod, methodOrder.size());
} else {
mirandaMethods.add(interfaceMethod);
methodOrder.put(interfaceMethod, methodOrder.size());
}
}
}
}
Comparator<MethodReference> comparator = new Comparator<MethodReference>() {
@Override public int compare(MethodReference o1, MethodReference o2) {
return Ints.compare(methodOrder.get(o1), methodOrder.get(o2));
}
};
// The methods should be in the same order within each list as they were iterated over.
// They can be misordered if, e.g. a method was originally added to the default list, but then moved
// to the conflict list.
Collections.sort(defaultMethods, comparator);
Collections.sort(defaultConflictMethods, comparator);
Collections.sort(mirandaMethods, comparator);
addToVtable(defaultMethods, vtable, false, false);
addToVtable(defaultConflictMethods, vtable, false, false);
addToVtable(mirandaMethods, vtable, false, false);
}
return vtable;
}
});
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, @Nonnull List<Method> vtable,
boolean replaceExisting, boolean sort) {
if (sort) {
ArrayList<Method> methods = Lists.newArrayList(localMethods);
Collections.sort(methods);
localMethods = methods;
}
for (Method virtualMethod: localMethods) {
int vtableIndex = findMethodIndexInVtable(vtable, virtualMethod);
if (vtableIndex >= 0) {
if (replaceExisting) {
vtable.set(vtableIndex, virtualMethod);
}
} else {
// we didn't find an equivalent method, so add it as a new entry
vtable.add(virtualMethod);
}
}
}
private static byte getFieldType(@Nonnull FieldReference field) {
switch (field.getType().charAt(0)) {
case '[':
case 'L':
return 0; //REFERENCE
case 'J':
case 'D':
return 1; //WIDE
default:
return 2; //OTHER
}
}
private boolean isOverridableByDefaultMethod(@Nonnull Method method) {
ClassProto classProto = (ClassProto)classPath.getClass(method.getDefiningClass());
return classProto.isInterface();
}
/**
* Checks if the interface method overrides the virtual or interface method2
* @param method A Method from an interface
* @param method2 A Method from an interface or a class
* @return true if the interface method overrides the virtual or interface method2
*/
private boolean interfaceMethodOverrides(@Nonnull Method method, @Nonnull Method method2) {
ClassProto classProto = (ClassProto)classPath.getClass(method2.getDefiningClass());
if (classProto.isInterface()) {
ClassProto targetClassProto = (ClassProto)classPath.getClass(method.getDefiningClass());
return targetClassProto.implementsInterface(method2.getDefiningClass());
} else {
return false;
}
}
static class ReparentedMethod extends BaseMethodReference implements Method {
private final Method method;
private final String definingClass;
public ReparentedMethod(Method method, String definingClass) {
this.method = method;
this.definingClass = definingClass;
}
@Nonnull @Override public String getDefiningClass() {
return definingClass;
}
@Nonnull @Override public String getName() {
return method.getName();
}
@Nonnull @Override public List<? extends CharSequence> getParameterTypes() {
return method.getParameterTypes();
}
@Nonnull @Override public String getReturnType() {
return method.getReturnType();
}
@Nonnull @Override public List<? extends MethodParameter> getParameters() {
return method.getParameters();
}
@Override public int getAccessFlags() {
return method.getAccessFlags();
}
@Nonnull @Override public Set<? extends Annotation> getAnnotations() {
return method.getAnnotations();
}
@Nullable @Override public MethodImplementation getImplementation() {
return method.getImplementation();
}
}
}