/*
*
*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
*
* 1. Definitions.
*
* "License" shall mean the terms and conditions for use, reproduction,
* and distribution as defined by Sections 1 through 9 of this document.
*
* "Licensor" shall mean the copyright owner or entity authorized by
* the copyright owner that is granting the License.
*
* "Legal Entity" shall mean the union of the acting entity and all
* other entities that control, are controlled by, or are under common
* control with that entity. For the purposes of this definition,
* "control" means (i) the power, direct or indirect, to cause the
* direction or management of such entity, whether by contract or
* otherwise, or (ii) ownership of fifty percent (50%) or more of the
* outstanding shares, or (iii) beneficial ownership of such entity.
*
* "You" (or "Your") shall mean an individual or Legal Entity
* exercising permissions granted by this License.
*
* "Source" form shall mean the preferred form for making modifications,
* including but not limited to software source code, documentation
* source, and configuration files.
*
* "Object" form shall mean any form resulting from mechanical
* transformation or translation of a Source form, including but
* not limited to compiled object code, generated documentation,
* and conversions to other media types.
*
* "Work" shall mean the work of authorship, whether in Source or
* Object form, made available under the License, as indicated by a
* copyright notice that is included in or attached to the work
* (an example is provided in the Appendix below).
*
* "Derivative Works" shall mean any work, whether in Source or Object
* form, that is based on (or derived from) the Work and for which the
* editorial revisions, annotations, elaborations, or other modifications
* represent, as a whole, an original work of authorship. For the purposes
* of this License, Derivative Works shall not include works that remain
* separable from, or merely link (or bind by name) to the interfaces of,
* the Work and Derivative Works thereof.
*
* "Contribution" shall mean any work of authorship, including
* the original version of the Work and any modifications or additions
* to that Work or Derivative Works thereof, that is intentionally
* submitted to Licensor for inclusion in the Work by the copyright owner
* or by an individual or Legal Entity authorized to submit on behalf of
* the copyright owner. For the purposes of this definition, "submitted"
* means any form of electronic, verbal, or written communication sent
* to the Licensor or its representatives, including but not limited to
* communication on electronic mailing lists, source code control systems,
* and issue tracking systems that are managed by, or on behalf of, the
* Licensor for the purpose of discussing and improving the Work, but
* excluding communication that is conspicuously marked or otherwise
* designated in writing by the copyright owner as "Not a Contribution."
*
* "Contributor" shall mean Licensor and any individual or Legal Entity
* on behalf of whom a Contribution has been received by Licensor and
* subsequently incorporated within the Work.
*
* 2. Grant of Copyright License. Subject to the terms and conditions of
* this License, each Contributor hereby grants to You a perpetual,
* worldwide, non-exclusive, no-charge, royalty-free, irrevocable
* copyright license to reproduce, prepare Derivative Works of,
* publicly display, publicly perform, sublicense, and distribute the
* Work and such Derivative Works in Source or Object form.
*
* 3. Grant of Patent License. Subject to the terms and conditions of
* this License, each Contributor hereby grants to You a perpetual,
* worldwide, non-exclusive, no-charge, royalty-free, irrevocable
* (except as stated in this section) patent license to make, have made,
* use, offer to sell, sell, import, and otherwise transfer the Work,
* where such license applies only to those patent claims licensable
* by such Contributor that are necessarily infringed by their
* Contribution(s) alone or by combination of their Contribution(s)
* with the Work to which such Contribution(s) was submitted. If You
* institute patent litigation against any entity (including a
* cross-claim or counterclaim in a lawsuit) alleging that the Work
* or a Contribution incorporated within the Work constitutes direct
* or contributory patent infringement, then any patent licenses
* granted to You under this License for that Work shall terminate
* as of the date such litigation is filed.
*
* 4. Redistribution. You may reproduce and distribute copies of the
* Work or Derivative Works thereof in any medium, with or without
* modifications, and in Source or Object form, provided that You
* meet the following conditions:
*
* (a) You must give any other recipients of the Work or
* Derivative Works a copy of this License; and
*
* (b) You must cause any modified files to carry prominent notices
* stating that You changed the files; and
*
* (c) You must retain, in the Source form of any Derivative Works
* that You distribute, all copyright, patent, trademark, and
* attribution notices from the Source form of the Work,
* excluding those notices that do not pertain to any part of
* the Derivative Works; and
*
* (d) If the Work includes a "NOTICE" text file as part of its
* distribution, then any Derivative Works that You distribute must
* include a readable copy of the attribution notices contained
* within such NOTICE file, excluding those notices that do not
* pertain to any part of the Derivative Works, in at least one
* of the following places: within a NOTICE text file distributed
* as part of the Derivative Works; within the Source form or
* documentation, if provided along with the Derivative Works; or,
* within a display generated by the Derivative Works, if and
* wherever such third-party notices normally appear. The contents
* of the NOTICE file are for informational purposes only and
* do not modify the License. You may add Your own attribution
* notices within Derivative Works that You distribute, alongside
* or as an addendum to the NOTICE text from the Work, provided
* that such additional attribution notices cannot be construed
* as modifying the License.
*
* You may add Your own copyright statement to Your modifications and
* may provide additional or different license terms and conditions
* for use, reproduction, or distribution of Your modifications, or
* for any such Derivative Works as a whole, provided Your use,
* reproduction, and distribution of the Work otherwise complies with
* the conditions stated in this License.
*
* 5. Submission of Contributions. Unless You explicitly state otherwise,
* any Contribution intentionally submitted for inclusion in the Work
* by You to the Licensor shall be under the terms and conditions of
* this License, without any additional terms or conditions.
* Notwithstanding the above, nothing herein shall supersede or modify
* the terms of any separate license agreement you may have executed
* with Licensor regarding such Contributions.
*
* 6. Trademarks. This License does not grant permission to use the trade
* names, trademarks, service marks, or product names of the Licensor,
* except as required for reasonable and customary use in describing the
* origin of the Work and reproducing the content of the NOTICE file.
*
* 7. Disclaimer of Warranty. Unless required by applicable law or
* agreed to in writing, Licensor provides the Work (and each
* Contributor provides its Contributions) on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied, including, without limitation, any warranties or conditions
* of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
* PARTICULAR PURPOSE. You are solely responsible for determining the
* appropriateness of using or redistributing the Work and assume any
* risks associated with Your exercise of permissions under this License.
*
* 8. Limitation of Liability. In no event and under no legal theory,
* whether in tort (including negligence), contract, or otherwise,
* unless required by applicable law (such as deliberate and grossly
* negligent acts) or agreed to in writing, shall any Contributor be
* liable to You for damages, including any direct, indirect, special,
* incidental, or consequential damages of any character arising as a
* result of this License or out of the use or inability to use the
* Work (including but not limited to damages for loss of goodwill,
* work stoppage, computer failure or malfunction, or any and all
* other commercial damages or losses), even if such Contributor
* has been advised of the possibility of such damages.
*
* 9. Accepting Warranty or Additional Liability. While redistributing
* the Work or Derivative Works thereof, You may choose to offer,
* and charge a fee for, acceptance of support, warranty, indemnity,
* or other liability obligations and/or rights consistent with this
* License. However, in accepting such obligations, You may act only
* on Your own behalf and on Your sole responsibility, not on behalf
* of any other Contributor, and only if You agree to indemnify,
* defend, and hold each Contributor harmless for any liability
* incurred by, or claims asserted against, such Contributor by reason
* of your accepting any such warranty or additional liability.
*
* END OF TERMS AND CONDITIONS
*
* APPENDIX: How to apply the Apache License to your work.
*
* To apply the Apache License to your work, attach the following
* boilerplate notice, with the fields enclosed by brackets "[]"
* replaced with your own identifying information. (Don't include
* the brackets!) The text should be enclosed in the appropriate
* comment syntax for the file format. We also recommend that a
* file or class name and description of purpose be included on the
* same "printed page" as the copyright notice for easier
* identification within third-party archives.
*
* Copyright 2016 Alibaba Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package com.taobao.common.dexpatcher.algorithms.diff.utils;
import com.taobao.common.dexpatcher.DexPatcherLogger;
import com.taobao.dex.*;
import com.taobao.dex.io.DexDataBuffer;
import com.taobao.dx.instruction.InstructionComparator;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.regex.Pattern;
public final class DexClassesComparator {
public static final int COMPARE_MODE_NORMAL = 0;
public static final int COMPARE_MODE_CAUSE_REF_CHANGE_ONLY = 1;
private static final String TAG = "DexClassesComparator";
private static final int DBG_FIRST_SPECIAL = 0x0A; // the smallest special opcode
private static final int DBG_LINE_BASE = -4; // the smallest line number increment
private static final int DBG_LINE_RANGE = 15; // the number of line increments represented
private final List<DexClassInfo> addedClassInfoList = new ArrayList<DexClassInfo>();
private final List<DexClassInfo> deletedClassInfoList = new ArrayList<DexClassInfo>();
// classDesc => [oldClassInfo, newClassInfo]
private final Map<String, DexClassInfo[]> changedClassDescToClassInfosMap = new HashMap<String, DexClassInfo[]>();
private final Set<Pattern> patternsOfClassDescToCheck = new HashSet<Pattern>();
private final Set<Pattern> patternsOfIgnoredRemovedClassDesc = new HashSet<Pattern>();
private final Set<String> oldDescriptorOfClassesToCheck = new HashSet<String>();
private final Set<String> newDescriptorOfClassesToCheck = new HashSet<String>();
private final Map<String, DexClassInfo> oldClassDescriptorToClassInfoMap = new HashMap<String, DexClassInfo>();
private final Map<String, DexClassInfo> newClassDescriptorToClassInfoMap = new HashMap<String, DexClassInfo>();
// Record class descriptors whose references key (index or offset) of methods and fields
// are changed.
private final Set<String> refAffectedClassDescs = new HashSet<String>();
private final DexPatcherLogger logger = new DexPatcherLogger();
private int compareMode = COMPARE_MODE_NORMAL;
public DexClassesComparator(String patternStringOfClassDescToCheck) {
patternsOfClassDescToCheck.add(
Pattern.compile(
PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStringOfClassDescToCheck)
)
);
}
public DexClassesComparator(String... patternStringsOfClassDescToCheck) {
for (String patternStr : patternStringsOfClassDescToCheck) {
patternsOfClassDescToCheck.add(
Pattern.compile(
PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr)
)
);
}
}
public DexClassesComparator(Collection<String> patternStringsOfClassDescToCheck) {
for (String patternStr : patternStringsOfClassDescToCheck) {
patternsOfClassDescToCheck.add(
Pattern.compile(
PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr)
)
);
}
}
public void setIgnoredRemovedClassDescPattern(String... patternStringsOfLoaderClassDesc) {
patternsOfIgnoredRemovedClassDesc.clear();
for (String patternStr : patternStringsOfLoaderClassDesc) {
patternsOfIgnoredRemovedClassDesc.add(
Pattern.compile(
PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr)
)
);
}
}
public void setIgnoredRemovedClassDescPattern(Collection<String> patternStringsOfLoaderClassDesc) {
patternsOfIgnoredRemovedClassDesc.clear();
for (String patternStr : patternStringsOfLoaderClassDesc) {
patternsOfIgnoredRemovedClassDesc.add(
Pattern.compile(
PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr)
)
);
}
}
public void setCompareMode(int mode) {
if (mode == COMPARE_MODE_NORMAL || mode == COMPARE_MODE_CAUSE_REF_CHANGE_ONLY) {
this.compareMode = mode;
} else {
throw new IllegalArgumentException("bad compare mode: " + mode);
}
}
public void setLogger(DexPatcherLogger.IDexPatcherLogger logger) {
this.logger.setLoggerImpl(logger);
}
public List<DexClassInfo> getAddedClassInfos() {
return Collections.unmodifiableList(addedClassInfoList);
}
public List<DexClassInfo> getDeletedClassInfos() {
return Collections.unmodifiableList(deletedClassInfoList);
}
public Map<String, DexClassInfo[]> getChangedClassDescToInfosMap() {
return Collections.unmodifiableMap(changedClassDescToClassInfosMap);
}
public void startCheck(File oldDexFile, File newDexFile) throws IOException {
startCheck(new Dex(oldDexFile), new Dex(newDexFile));
}
public void startCheck(Dex oldDex, Dex newDex) {
startCheck(DexGroup.wrap(oldDex), DexGroup.wrap(newDex));
}
public void startCheck(DexGroup oldDexGroup, DexGroup newDexGroup) {
// Init assist structures.
addedClassInfoList.clear();
deletedClassInfoList.clear();
changedClassDescToClassInfosMap.clear();
oldDescriptorOfClassesToCheck.clear();
newDescriptorOfClassesToCheck.clear();
oldClassDescriptorToClassInfoMap.clear();
newClassDescriptorToClassInfoMap.clear();
refAffectedClassDescs.clear();
// Map classDesc and typeIndex to classInfo
// and collect typeIndex of classes to check in oldDexes.
for (Dex oldDex : oldDexGroup.dexes) {
int classDefIndex = 0;
for (ClassDef oldClassDef : oldDex.classDefs()) {
String desc = oldDex.typeNames().get(oldClassDef.typeIndex);
if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) {
if (!oldDescriptorOfClassesToCheck.add(desc)) {
throw new IllegalStateException(
String.format(
"duplicate class descriptor [%s] in different old dexes.",
desc
)
);
}
}
DexClassInfo classInfo = new DexClassInfo(desc, classDefIndex, oldClassDef, oldDex);
++classDefIndex;
oldClassDescriptorToClassInfoMap.put(desc, classInfo);
}
}
// Map classDesc and typeIndex to classInfo
// and collect typeIndex of classes to check in newDexes.
for (Dex newDex : newDexGroup.dexes) {
int classDefIndex = 0;
for (ClassDef newClassDef : newDex.classDefs()) {
String desc = newDex.typeNames().get(newClassDef.typeIndex);
if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) {
if (!newDescriptorOfClassesToCheck.add(desc)) {
throw new IllegalStateException(
String.format(
"duplicate class descriptor [%s] in different new dexes.",
desc
)
);
}
}
DexClassInfo classInfo = new DexClassInfo(desc, classDefIndex, newClassDef, newDex);
++classDefIndex;
newClassDescriptorToClassInfoMap.put(desc, classInfo);
}
}
Set<String> deletedClassDescs = new HashSet<String>(oldDescriptorOfClassesToCheck);
deletedClassDescs.removeAll(newDescriptorOfClassesToCheck);
for (String desc : deletedClassDescs) {
// These classes are deleted as we expect to, so we remove them
// from result.
if (Utils.isStringMatchesPatterns(desc, patternsOfIgnoredRemovedClassDesc)) {
logger.i(TAG, "Ignored deleted class: %s", desc);
continue;
} else {
logger.i(TAG, "Deleted class: %s", desc);
}
deletedClassInfoList.add(oldClassDescriptorToClassInfoMap.get(desc));
}
Set<String> addedClassDescs = new HashSet<String>(newDescriptorOfClassesToCheck);
addedClassDescs.removeAll(oldDescriptorOfClassesToCheck);
for (String desc : addedClassDescs) {
logger.i(TAG, "Added class: %s", desc);
addedClassInfoList.add(newClassDescriptorToClassInfoMap.get(desc));
}
Set<String> mayBeChangedClassDescs = new HashSet<String>(oldDescriptorOfClassesToCheck);
mayBeChangedClassDescs.retainAll(newDescriptorOfClassesToCheck);
for (String desc : mayBeChangedClassDescs) {
DexClassInfo oldClassInfo = oldClassDescriptorToClassInfoMap.get(desc);
DexClassInfo newClassInfo = newClassDescriptorToClassInfoMap.get(desc);
switch (compareMode) {
case COMPARE_MODE_NORMAL: {
if (!isSameClass(
oldClassInfo.owner,
newClassInfo.owner,
oldClassInfo.classDef,
newClassInfo.classDef
)) {
logger.i(TAG, "Changed class: %s", desc);
changedClassDescToClassInfosMap.put(
desc, new DexClassInfo[]{oldClassInfo, newClassInfo}
);
}
break;
}
case COMPARE_MODE_CAUSE_REF_CHANGE_ONLY: {
if (isClassChangeAffectedToRef(
oldClassInfo.owner,
newClassInfo.owner,
oldClassInfo.classDef,
newClassInfo.classDef
)) {
logger.i(TAG, "Ref-changed class: %s", desc);
changedClassDescToClassInfosMap.put(
desc, new DexClassInfo[]{oldClassInfo, newClassInfo}
);
}
break;
}
}
}
}
private boolean isClassChangeAffectedToRef(
Dex oldDex,
Dex newDex,
ClassDef oldClassDef,
ClassDef newClassDef
) {
boolean result = false;
String classDesc = oldDex.typeNames().get(oldClassDef.typeIndex);
do {
if (refAffectedClassDescs.contains(classDesc)) {
result = true;
return result;
}
// Any changes on superclass could affect refs of members in current class.
if (isTypeChangeAffectedToRef(
oldDex, newDex, oldClassDef.supertypeIndex, newClassDef.supertypeIndex
)) {
result = true;
break;
}
// Any changes on current class's interface list could affect refs
// of members in current class.
short[] oldInterfaceTypeIds = oldDex.interfaceTypeIndicesFromClassDef(oldClassDef);
short[] newInterfaceTypeIds = newDex.interfaceTypeIndicesFromClassDef(newClassDef);
if (isTypeIdsChangeAffectedToRef(
oldDex, newDex, oldInterfaceTypeIds, newInterfaceTypeIds, false
)) {
result = true;
break;
}
// Any changes on current class's member lists could affect refs
// of members in current class.
ClassData oldClassData =
(oldClassDef.classDataOffset != 0 ? oldDex.readClassData(oldClassDef) : null);
ClassData newClassData =
(newClassDef.classDataOffset != 0 ? newDex.readClassData(newClassDef) : null);
if (isClassDataChangeAffectedToRef(
oldDex, newDex, oldClassData, newClassData
)) {
result = true;
break;
}
} while (false);
if (result) {
refAffectedClassDescs.add(classDesc);
}
return result;
}
private boolean isTypeChangeAffectedToRef(
Dex oldDex, Dex newDex, int oldTypeId, int newTypeId
) {
if (oldTypeId != ClassDef.NO_INDEX && newTypeId != ClassDef.NO_INDEX) {
String oldClassDesc = oldDex.typeNames().get(oldTypeId);
String newClassDesc = newDex.typeNames().get(newTypeId);
if (!oldClassDesc.equals(newClassDesc)) {
return true;
}
final DexClassInfo oldClassInfo = oldClassDescriptorToClassInfoMap.get(oldClassDesc);
final DexClassInfo newClassInfo = newClassDescriptorToClassInfoMap.get(newClassDesc);
ClassDef oldClassDef = (oldClassInfo != null ? oldClassInfo.classDef : null);
ClassDef newClassDef = (newClassInfo != null ? newClassInfo.classDef : null);
if (oldClassDef != null && newClassDef != null) {
return isClassChangeAffectedToRef(oldClassInfo.owner, newClassInfo.owner, oldClassDef, newClassDef);
} else
if (oldClassDef == null && newClassDef == null) {
return false;
} else {
// If current comparing class is ignored, since it must be removed
// in patched dexes as we expected, here we ignore this kind of changes.
return !Utils.isStringMatchesPatterns(oldClassDesc, patternsOfIgnoredRemovedClassDesc);
}
} else {
if (!(oldTypeId == ClassDef.NO_INDEX && newTypeId == ClassDef.NO_INDEX)) {
return true;
}
}
return false;
}
private boolean isTypeIdsChangeAffectedToRef(
Dex oldDex,
Dex newDex,
short[] oldTypeIds,
short[] newTypeIds,
boolean compareNameOnly
) {
if (oldTypeIds.length != newTypeIds.length) {
return true;
}
int typeIdCount = oldTypeIds.length;
for (int i = 0; i < typeIdCount; ++i) {
if (compareNameOnly) {
String oldTypeName = oldDex.typeNames().get(oldTypeIds[i]);
String newTypeName = newDex.typeNames().get(newTypeIds[i]);
if (!oldTypeName.equals(newTypeName)) {
return true;
}
} else {
if (isTypeChangeAffectedToRef(oldDex, newDex, oldTypeIds[i], newTypeIds[i])) {
return true;
}
}
}
return false;
}
private boolean isClassDataChangeAffectedToRef(
Dex oldDex,
Dex newDex,
ClassData oldClassData,
ClassData newClassData
) {
if (oldClassData != null && newClassData != null) {
if (isFieldsChangeAffectedToRef(
oldDex, newDex, oldClassData.instanceFields, newClassData.instanceFields
)) {
return true;
}
if (isFieldsChangeAffectedToRef(
oldDex, newDex, oldClassData.staticFields, newClassData.staticFields
)) {
return true;
}
if (isMethodsChangeAffectedToRef(
oldDex, newDex, oldClassData.directMethods, newClassData.directMethods
)) {
return true;
}
if (isMethodsChangeAffectedToRef(
oldDex, newDex, oldClassData.virtualMethods, newClassData.virtualMethods
)) {
return true;
}
} else {
if (!(oldClassData == null && newClassData == null)) {
return true;
}
}
return false;
}
private boolean isFieldsChangeAffectedToRef(
Dex oldDex,
Dex newDex,
ClassData.Field[] oldFields,
ClassData.Field[] newFields
) {
if (oldFields.length != newFields.length) {
return true;
}
int fieldCount = oldFields.length;
for (int i = 0; i < fieldCount; ++i) {
ClassData.Field oldField = oldFields[i];
ClassData.Field newField = newFields[i];
if (oldField.accessFlags != newField.accessFlags) {
return true;
}
FieldId oldFieldId = oldDex.fieldIds().get(oldField.fieldIndex);
FieldId newFieldId = newDex.fieldIds().get(newField.fieldIndex);
String oldFieldName = oldDex.strings().get(oldFieldId.nameIndex);
String newFieldName = newDex.strings().get(newFieldId.nameIndex);
if (!oldFieldName.equals(newFieldName)) {
return true;
}
String oldFieldTypeName = oldDex.typeNames().get(oldFieldId.typeIndex);
String newFieldTypeName = newDex.typeNames().get(newFieldId.typeIndex);
if (!oldFieldTypeName.equals(newFieldTypeName)) {
return true;
}
}
return false;
}
private boolean isMethodsChangeAffectedToRef(
Dex oldDex,
Dex newDex,
ClassData.Method[] oldMethods,
ClassData.Method[] newMethods
) {
if (oldMethods.length != newMethods.length) {
return true;
}
int methodCount = oldMethods.length;
for (int i = 0; i < methodCount; ++i) {
ClassData.Method oldMethod = oldMethods[i];
ClassData.Method newMethod = newMethods[i];
if (oldMethod.accessFlags != newMethod.accessFlags) {
return true;
}
MethodId oldMethodId = oldDex.methodIds().get(oldMethod.methodIndex);
MethodId newMethodId = newDex.methodIds().get(newMethod.methodIndex);
String oldMethodName = oldDex.strings().get(oldMethodId.nameIndex);
String newMethodName = newDex.strings().get(newMethodId.nameIndex);
if (!oldMethodName.equals(newMethodName)) {
return true;
}
ProtoId oldProtoId = oldDex.protoIds().get(oldMethodId.protoIndex);
ProtoId newProtoId = newDex.protoIds().get(newMethodId.protoIndex);
String oldMethodShorty = oldDex.strings().get(oldProtoId.shortyIndex);
String newMethodShorty = newDex.strings().get(newProtoId.shortyIndex);
if (!oldMethodShorty.equals(newMethodShorty)) {
return true;
}
String oldMethodReturnTypeName = oldDex.typeNames().get(oldProtoId.returnTypeIndex);
String newMethodReturnTypeName = newDex.typeNames().get(newProtoId.returnTypeIndex);
if (!oldMethodReturnTypeName.equals(newMethodReturnTypeName)) {
return true;
}
short[] oldParameterIds = oldDex.parameterTypeIndicesFromMethodId(oldMethodId);
short[] newParameterIds = newDex.parameterTypeIndicesFromMethodId(newMethodId);
if (isTypeIdsChangeAffectedToRef(
oldDex, newDex, oldParameterIds, newParameterIds, true
)) {
return true;
}
}
return false;
}
private boolean isSameClass(
Dex oldDex,
Dex newDex,
ClassDef oldClassDef,
ClassDef newClassDef
) {
if (oldClassDef.accessFlags != newClassDef.accessFlags) {
return false;
}
if (!isSameClassDesc(
oldDex, newDex, oldClassDef.supertypeIndex, newClassDef.supertypeIndex
)) {
return false;
}
short[] oldInterfaceIndices = oldDex.interfaceTypeIndicesFromClassDef(oldClassDef);
short[] newInterfaceIndices = newDex.interfaceTypeIndicesFromClassDef(newClassDef);
if (oldInterfaceIndices.length != newInterfaceIndices.length) {
return false;
} else {
for (int i = 0; i < oldInterfaceIndices.length; ++i) {
if (!isSameClassDesc(oldDex, newDex, oldInterfaceIndices[i], newInterfaceIndices[i])) {
return false;
}
}
}
if (!isSameName(oldDex, newDex, oldClassDef.sourceFileIndex, newClassDef.sourceFileIndex)) {
return false;
}
if (!isSameAnnotationDirectory(
oldDex,
newDex,
oldClassDef.annotationsOffset,
newClassDef.annotationsOffset
)) {
return false;
}
if (!isSameClassData(
oldDex,
newDex,
oldClassDef.classDataOffset,
newClassDef.classDataOffset
)) {
return false;
}
return isSameStaticValue(
oldDex,
newDex,
oldClassDef.staticValuesOffset,
newClassDef.staticValuesOffset
);
}
private boolean isSameStaticValue(
Dex oldDex,
Dex newDex,
int oldStaticValueOffset,
int newStaticValueOffset
) {
if (oldStaticValueOffset == 0 && newStaticValueOffset == 0) {
return true;
}
if (oldStaticValueOffset == 0 || newStaticValueOffset == 0) {
return false;
}
EncodedValue oldStaticValue =
oldDex.openSection(oldStaticValueOffset).readEncodedArray();
EncodedValue newStaticValue =
newDex.openSection(newStaticValueOffset).readEncodedArray();
EncodedValueReader oldReader =
new EncodedValueReader(oldStaticValue, EncodedValueReader.ENCODED_ARRAY);
EncodedValueReader newReader =
new EncodedValueReader(newStaticValue, EncodedValueReader.ENCODED_ARRAY);
return isSameEncodedValue(oldDex, newDex, oldReader, newReader);
}
private boolean isSameClassDesc(Dex oldDex, Dex newDex, int oldTypeId, int newTypeId) {
String oldClassDesc = oldDex.typeNames().get(oldTypeId);
String newClassDesc = newDex.typeNames().get(newTypeId);
return oldClassDesc.equals(newClassDesc);
}
private boolean isSameName(Dex oldDex, Dex newDex, int oldStringId, int newStringId) {
if (oldStringId == TableOfContents.Section.UNDEF_INDEX
&& newStringId == TableOfContents.Section.UNDEF_INDEX) {
return true;
}
if (oldStringId == TableOfContents.Section.UNDEF_INDEX
|| newStringId == TableOfContents.Section.UNDEF_INDEX) {
return false;
}
return oldDex.strings().get(oldStringId).equals(newDex.strings().get(newStringId));
}
private boolean isSameAnnotationDirectory(
Dex oldDex,
Dex newDex,
int oldAnnotationDirectoryOffset,
int newAnnotationDirectoryOffset
) {
if (oldAnnotationDirectoryOffset == 0 && newAnnotationDirectoryOffset == 0) {
return true;
}
if (oldAnnotationDirectoryOffset == 0 || newAnnotationDirectoryOffset == 0) {
return false;
}
AnnotationsDirectory oldAnnotationsDirectory =
oldDex.openSection(oldAnnotationDirectoryOffset).readAnnotationsDirectory();
AnnotationsDirectory newAnnotationsDirectory =
newDex.openSection(newAnnotationDirectoryOffset).readAnnotationsDirectory();
if (!isSameAnnotationSet(
oldDex,
newDex,
oldAnnotationsDirectory.classAnnotationsOffset,
newAnnotationsDirectory.classAnnotationsOffset
)) {
return false;
}
int[][] oldFieldAnnotations = oldAnnotationsDirectory.fieldAnnotations;
int[][] newFieldAnnotations = newAnnotationsDirectory.fieldAnnotations;
if (oldFieldAnnotations.length != newFieldAnnotations.length) {
return false;
}
for (int i = 0; i < oldFieldAnnotations.length; ++i) {
if (!isSameFieldId(
oldDex, newDex, oldFieldAnnotations[i][0], newFieldAnnotations[i][0]
)) {
return false;
}
if (!isSameAnnotationSet(
oldDex, newDex, oldFieldAnnotations[i][1], newFieldAnnotations[i][1]
)) {
return false;
}
}
int[][] oldMethodAnnotations = oldAnnotationsDirectory.methodAnnotations;
int[][] newMethodAnnotations = newAnnotationsDirectory.methodAnnotations;
if (oldMethodAnnotations.length != newMethodAnnotations.length) {
return false;
}
for (int i = 0; i < oldMethodAnnotations.length; ++i) {
if (!isSameMethodId(
oldDex, newDex, oldMethodAnnotations[i][0], newMethodAnnotations[i][0]
)) {
return false;
}
if (!isSameAnnotationSet(
oldDex, newDex, oldMethodAnnotations[i][1], newMethodAnnotations[i][1]
)) {
return false;
}
}
int[][] oldParameterAnnotations = oldAnnotationsDirectory.parameterAnnotations;
int[][] newParameterAnnotations = newAnnotationsDirectory.parameterAnnotations;
if (oldParameterAnnotations.length != newParameterAnnotations.length) {
return false;
}
for (int i = 0; i < oldParameterAnnotations.length; ++i) {
if (!isSameMethodId(
oldDex, newDex, oldParameterAnnotations[i][0], newParameterAnnotations[i][0]
)) {
return false;
}
if (!isSameAnnotationSetRefList(
oldDex, newDex, oldParameterAnnotations[i][1], newParameterAnnotations[i][1]
)) {
return false;
}
}
return true;
}
private boolean isSameFieldId(Dex oldDex, Dex newDex, int oldFieldIdIdx, int newFieldIdIdx) {
FieldId oldFieldId = oldDex.fieldIds().get(oldFieldIdIdx);
FieldId newFieldId = newDex.fieldIds().get(newFieldIdIdx);
if (!isSameClassDesc(
oldDex, newDex, oldFieldId.declaringClassIndex, newFieldId.declaringClassIndex
)) {
return false;
}
if (!isSameClassDesc(
oldDex, newDex, oldFieldId.typeIndex, newFieldId.typeIndex
)) {
return false;
}
String oldName = oldDex.strings().get(oldFieldId.nameIndex);
String newName = newDex.strings().get(newFieldId.nameIndex);
return oldName.equals(newName);
}
private boolean isSameMethodId(Dex oldDex, Dex newDex, int oldMethodIdIdx, int newMethodIdIdx) {
MethodId oldMethodId = oldDex.methodIds().get(oldMethodIdIdx);
MethodId newMethodId = newDex.methodIds().get(newMethodIdIdx);
if (!isSameClassDesc(
oldDex, newDex, oldMethodId.declaringClassIndex, newMethodId.declaringClassIndex
)) {
return false;
}
if (!isSameProtoId(oldDex, newDex, oldMethodId.protoIndex, newMethodId.protoIndex)) {
return false;
}
String oldName = oldDex.strings().get(oldMethodId.nameIndex);
String newName = newDex.strings().get(newMethodId.nameIndex);
return oldName.equals(newName);
}
private boolean isSameProtoId(Dex oldDex, Dex newDex, int oldProtoIdIdx, int newProtoIdIdx) {
ProtoId oldProtoId = oldDex.protoIds().get(oldProtoIdIdx);
ProtoId newProtoId = newDex.protoIds().get(newProtoIdIdx);
String oldShorty = oldDex.strings().get(oldProtoId.shortyIndex);
String newShorty = newDex.strings().get(newProtoId.shortyIndex);
if (!oldShorty.equals(newShorty)) {
return false;
}
if (!isSameClassDesc(
oldDex, newDex, oldProtoId.returnTypeIndex, newProtoId.returnTypeIndex
)) {
return false;
}
return isSameParameters(
oldDex, newDex, oldProtoId.parametersOffset, newProtoId.parametersOffset
);
}
private boolean isSameParameters(
Dex oldDex, Dex newDex, int oldParametersOffset, int newParametersOffset
) {
if (oldParametersOffset == 0 && newParametersOffset == 0) {
return true;
}
if (oldParametersOffset == 0 || newParametersOffset == 0) {
return false;
}
TypeList oldParameters = oldDex.openSection(oldParametersOffset).readTypeList();
TypeList newParameters = newDex.openSection(newParametersOffset).readTypeList();
if (oldParameters.types.length != newParameters.types.length) {
return false;
}
for (int i = 0; i < oldParameters.types.length; ++i) {
if (!isSameClassDesc(
oldDex, newDex, oldParameters.types[i], newParameters.types[i]
)) {
return false;
}
}
return true;
}
private boolean isSameAnnotationSetRefList(
Dex oldDex,
Dex newDex,
int oldAnnotationSetRefListOffset,
int newAnnotationSetRefListOffset
) {
if (oldAnnotationSetRefListOffset == 0 && newAnnotationSetRefListOffset == 0) {
return true;
}
if (oldAnnotationSetRefListOffset == 0 || newAnnotationSetRefListOffset == 0) {
return false;
}
AnnotationSetRefList oldAnnotationSetRefList = oldDex.openSection(
oldAnnotationSetRefListOffset
).readAnnotationSetRefList();
AnnotationSetRefList newAnnotationSetRefList = newDex.openSection(
newAnnotationSetRefListOffset
).readAnnotationSetRefList();
int oldAnnotationSetRefListCount = oldAnnotationSetRefList.annotationSetRefItems.length;
int newAnnotationSetRefListCount = newAnnotationSetRefList.annotationSetRefItems.length;
if (oldAnnotationSetRefListCount != newAnnotationSetRefListCount) {
return false;
}
for (int i = 0; i < oldAnnotationSetRefListCount; ++i) {
if (!isSameAnnotationSet(
oldDex,
newDex,
oldAnnotationSetRefList.annotationSetRefItems[i],
newAnnotationSetRefList.annotationSetRefItems[i]
)) {
return false;
}
}
return true;
}
private boolean isSameAnnotationSet(
Dex oldDex, Dex newDex, int oldAnnotationSetOffset, int newAnnotationSetOffset
) {
if (oldAnnotationSetOffset == 0 && newAnnotationSetOffset == 0) {
return true;
}
if (oldAnnotationSetOffset == 0 || newAnnotationSetOffset == 0) {
return false;
}
AnnotationSet oldClassAnnotationSet =
oldDex.openSection(oldAnnotationSetOffset).readAnnotationSet();
AnnotationSet newClassAnnotationSet =
newDex.openSection(newAnnotationSetOffset).readAnnotationSet();
int oldAnnotationOffsetCount = oldClassAnnotationSet.annotationOffsets.length;
int newAnnotationOffsetCount = newClassAnnotationSet.annotationOffsets.length;
if (oldAnnotationOffsetCount != newAnnotationOffsetCount) {
return false;
}
for (int i = 0; i < oldAnnotationOffsetCount; ++i) {
if (!isSameAnnotation(
oldDex,
newDex,
oldClassAnnotationSet.annotationOffsets[i],
newClassAnnotationSet.annotationOffsets[i]
)) {
return false;
}
}
return true;
}
private boolean isSameAnnotation(
Dex oldDex, Dex newDex, int oldAnnotationOffset, int newAnnotationOffset
) {
Annotation oldAnnotation = oldDex.openSection(oldAnnotationOffset).readAnnotation();
Annotation newAnnotation = newDex.openSection(newAnnotationOffset).readAnnotation();
if (oldAnnotation.visibility != newAnnotation.visibility) {
return false;
}
EncodedValueReader oldAnnoReader = oldAnnotation.getReader();
EncodedValueReader newAnnoReader = newAnnotation.getReader();
return isSameAnnotationByReader(oldDex, newDex, oldAnnoReader, newAnnoReader);
}
private boolean isSameAnnotationByReader(
Dex oldDex,
Dex newDex,
EncodedValueReader oldAnnoReader,
EncodedValueReader newAnnoReader
) {
int oldFieldCount = oldAnnoReader.readAnnotation();
int newFieldCount = newAnnoReader.readAnnotation();
if (oldFieldCount != newFieldCount) {
return false;
}
int oldAnnoType = oldAnnoReader.getAnnotationType();
int newAnnoType = newAnnoReader.getAnnotationType();
if (!isSameClassDesc(oldDex, newDex, oldAnnoType, newAnnoType)) {
return false;
}
for (int i = 0; i < oldFieldCount; ++i) {
int oldAnnoNameIdx = oldAnnoReader.readAnnotationName();
int newAnnoNameIdx = newAnnoReader.readAnnotationName();
if (!isSameName(oldDex, newDex, oldAnnoNameIdx, newAnnoNameIdx)) {
return false;
}
if (!isSameEncodedValue(oldDex, newDex, oldAnnoReader, newAnnoReader)) {
return false;
}
}
return true;
}
private boolean isSameEncodedValue(
Dex oldDex,
Dex newDex,
EncodedValueReader oldAnnoReader,
EncodedValueReader newAnnoReader
) {
int oldAnnoItemType = oldAnnoReader.peek();
int newAnnoItemType = newAnnoReader.peek();
if (oldAnnoItemType != newAnnoItemType) {
return false;
}
switch (oldAnnoItemType) {
case EncodedValueReader.ENCODED_BYTE: {
byte oldByte = oldAnnoReader.readByte();
byte newByte = newAnnoReader.readByte();
return oldByte == newByte;
}
case EncodedValueReader.ENCODED_SHORT: {
short oldShort = oldAnnoReader.readShort();
short newShort = newAnnoReader.readShort();
return oldShort == newShort;
}
case EncodedValueReader.ENCODED_INT: {
int oldInt = oldAnnoReader.readInt();
int newInt = newAnnoReader.readInt();
return oldInt == newInt;
}
case EncodedValueReader.ENCODED_LONG: {
long oldLong = oldAnnoReader.readLong();
long newLong = newAnnoReader.readLong();
return oldLong == newLong;
}
case EncodedValueReader.ENCODED_CHAR: {
char oldChar = oldAnnoReader.readChar();
char newChar = newAnnoReader.readChar();
return oldChar == newChar;
}
case EncodedValueReader.ENCODED_FLOAT: {
float oldFloat = oldAnnoReader.readFloat();
float newFloat = newAnnoReader.readFloat();
return Float.compare(oldFloat, newFloat) == 0;
}
case EncodedValueReader.ENCODED_DOUBLE: {
double oldDouble = oldAnnoReader.readDouble();
double newDouble = newAnnoReader.readDouble();
return Double.compare(oldDouble, newDouble) == 0;
}
case EncodedValueReader.ENCODED_STRING: {
int oldStringIdx = oldAnnoReader.readString();
int newStringIdx = newAnnoReader.readString();
return isSameName(oldDex, newDex, oldStringIdx, newStringIdx);
}
case EncodedValueReader.ENCODED_TYPE: {
int oldTypeId = oldAnnoReader.readType();
int newTypeId = newAnnoReader.readType();
return isSameClassDesc(oldDex, newDex, oldTypeId, newTypeId);
}
case EncodedValueReader.ENCODED_FIELD: {
int oldFieldId = oldAnnoReader.readField();
int newFieldId = newAnnoReader.readField();
return isSameFieldId(oldDex, newDex, oldFieldId, newFieldId);
}
case EncodedValueReader.ENCODED_ENUM: {
int oldFieldId = oldAnnoReader.readEnum();
int newFieldId = newAnnoReader.readEnum();
return isSameFieldId(oldDex, newDex, oldFieldId, newFieldId);
}
case EncodedValueReader.ENCODED_METHOD: {
int oldMethodId = oldAnnoReader.readMethod();
int newMethodId = newAnnoReader.readMethod();
return isSameMethodId(oldDex, newDex, oldMethodId, newMethodId);
}
case EncodedValueReader.ENCODED_ARRAY: {
int oldArrSize = oldAnnoReader.readArray();
int newArrSize = newAnnoReader.readArray();
if (oldArrSize != newArrSize) {
return false;
}
for (int i = 0; i < oldArrSize; ++i) {
if (!isSameEncodedValue(oldDex, newDex, oldAnnoReader, newAnnoReader)) {
return false;
}
}
return true;
}
case EncodedValueReader.ENCODED_ANNOTATION: {
return isSameAnnotationByReader(oldDex, newDex, oldAnnoReader, newAnnoReader);
}
case EncodedValueReader.ENCODED_NULL: {
oldAnnoReader.readNull();
newAnnoReader.readNull();
return true;
}
case EncodedValueReader.ENCODED_BOOLEAN: {
boolean oldBool = oldAnnoReader.readBoolean();
boolean newBool = newAnnoReader.readBoolean();
return oldBool == newBool;
}
default: {
throw new IllegalStateException(
"Unexpected annotation value type: " + Integer.toHexString(oldAnnoItemType)
);
}
}
}
private boolean isSameClassData(
Dex oldDex, Dex newDex, int oldClassDataOffset, int newClassDataOffset
) {
if (oldClassDataOffset == 0 && newClassDataOffset == 0) {
return true;
}
if (oldClassDataOffset == 0 || newClassDataOffset == 0) {
return false;
}
ClassData oldClassData = oldDex.openSection(oldClassDataOffset).readClassData();
ClassData newClassData = newDex.openSection(newClassDataOffset).readClassData();
ClassData.Field[] oldInstanceFields = oldClassData.instanceFields;
ClassData.Field[] newInstanceFields = newClassData.instanceFields;
if (oldInstanceFields.length != newInstanceFields.length) {
return false;
}
for (int i = 0; i < oldInstanceFields.length; ++i) {
if (!isSameField(oldDex, newDex, oldInstanceFields[i], newInstanceFields[i])) {
return false;
}
}
ClassData.Field[] oldStaticFields = oldClassData.staticFields;
ClassData.Field[] newStaticFields = newClassData.staticFields;
if (oldStaticFields.length != newStaticFields.length) {
return false;
}
for (int i = 0; i < oldStaticFields.length; ++i) {
if (!isSameField(oldDex, newDex, oldStaticFields[i], newStaticFields[i])) {
return false;
}
}
ClassData.Method[] oldDirectMethods = oldClassData.directMethods;
ClassData.Method[] newDirectMethods = newClassData.directMethods;
if (oldDirectMethods.length != newDirectMethods.length) {
return false;
}
for (int i = 0; i < oldDirectMethods.length; ++i) {
if (!isSameMethod(oldDex, newDex, oldDirectMethods[i], newDirectMethods[i])) {
return false;
}
}
ClassData.Method[] oldVirtualMethods = oldClassData.virtualMethods;
ClassData.Method[] newVirtualMethods = newClassData.virtualMethods;
if (oldVirtualMethods.length != newVirtualMethods.length) {
return false;
}
for (int i = 0; i < oldVirtualMethods.length; ++i) {
if (!isSameMethod(oldDex, newDex, oldVirtualMethods[i], newVirtualMethods[i])) {
return false;
}
}
return true;
}
private boolean isSameField(
Dex oldDex, Dex newDex, ClassData.Field oldField, ClassData.Field newField
) {
if (oldField.accessFlags != newField.accessFlags) {
return false;
}
return isSameFieldId(oldDex, newDex, oldField.fieldIndex, newField.fieldIndex);
}
private boolean isSameMethod(
Dex oldDex, Dex newDex, ClassData.Method oldMethod, ClassData.Method newMethod
) {
if (oldMethod.accessFlags != newMethod.accessFlags) {
return false;
}
if (!isSameMethodId(oldDex, newDex, oldMethod.methodIndex, newMethod.methodIndex)) {
return false;
}
return isSameCode(oldDex, newDex, oldMethod.codeOffset, newMethod.codeOffset);
}
private boolean isSameCode(
final Dex oldDex, final Dex newDex, int oldCodeOffset, int newCodeOffset
) {
if (oldCodeOffset == 0 && newCodeOffset == 0) {
return true;
}
if (oldCodeOffset == 0 || newCodeOffset == 0) {
return false;
}
Code oldCode = oldDex.openSection(oldCodeOffset).readCode();
Code newCode = newDex.openSection(newCodeOffset).readCode();
if (oldCode.registersSize != newCode.registersSize) {
return false;
}
if (oldCode.insSize != newCode.insSize) {
return false;
}
final InstructionComparator insnComparator = new InstructionComparator(
oldCode.instructions,
newCode.instructions
) {
@Override
protected boolean compareString(int stringIndex1, int stringIndex2) {
return isSameName(oldDex, newDex, stringIndex1, stringIndex2);
}
@Override
protected boolean compareType(int typeIndex1, int typeIndex2) {
return isSameClassDesc(oldDex, newDex, typeIndex1, typeIndex2);
}
@Override
protected boolean compareField(int fieldIndex1, int fieldIndex2) {
return isSameFieldId(oldDex, newDex, fieldIndex1, fieldIndex2);
}
@Override
protected boolean compareMethod(int methodIndex1, int methodIndex2) {
return isSameMethodId(oldDex, newDex, methodIndex1, methodIndex2);
}
};
if (!insnComparator.compare()) {
return false;
}
if (!isSameDebugInfo(
oldDex, newDex, oldCode.debugInfoOffset, newCode.debugInfoOffset, insnComparator
)) {
return false;
}
if (!isSameTries(oldDex, newDex, oldCode.tries, newCode.tries, insnComparator)) {
return false;
}
return isSameCatchHandlers(
oldDex, newDex, oldCode.catchHandlers, newCode.catchHandlers, insnComparator
);
}
private boolean isSameDebugInfo(
Dex oldDex,
Dex newDex,
int oldDebugInfoOffset,
int newDebugInfoOffset,
InstructionComparator insnComparator
) {
if (oldDebugInfoOffset == 0 && newDebugInfoOffset == 0) {
return true;
}
if (oldDebugInfoOffset == 0 || newDebugInfoOffset == 0) {
return false;
}
DebugInfoItem oldDebugInfoItem =
oldDex.openSection(oldDebugInfoOffset).readDebugInfoItem();
DebugInfoItem newDebugInfoItem =
newDex.openSection(newDebugInfoOffset).readDebugInfoItem();
if (oldDebugInfoItem.lineStart != newDebugInfoItem.lineStart) {
return false;
}
if (oldDebugInfoItem.parameterNames.length != newDebugInfoItem.parameterNames.length) {
return false;
}
for (int i = 0; i < oldDebugInfoItem.parameterNames.length; ++i) {
int oldNameIdx = oldDebugInfoItem.parameterNames[i];
int newNameIdx = newDebugInfoItem.parameterNames[i];
if (!isSameName(oldDex, newDex, oldNameIdx, newNameIdx)) {
return false;
}
}
DexDataBuffer oldDbgInfoBuffer =
new DexDataBuffer(ByteBuffer.wrap(oldDebugInfoItem.infoSTM));
DexDataBuffer newDbgInfoBuffer =
new DexDataBuffer(ByteBuffer.wrap(newDebugInfoItem.infoSTM));
int oldLine = oldDebugInfoItem.lineStart;
int oldAddress = 0;
int newLine = newDebugInfoItem.lineStart;
int newAddress = 0;
while (oldDbgInfoBuffer.available() > 0 && newDbgInfoBuffer.available() > 0) {
int oldOpCode = oldDbgInfoBuffer.readUnsignedByte();
int newOpCode = newDbgInfoBuffer.readUnsignedByte();
if (oldOpCode != newOpCode) {
if (oldOpCode < DBG_FIRST_SPECIAL || newOpCode < DBG_FIRST_SPECIAL) {
return false;
}
}
int currOpCode = oldOpCode;
switch (currOpCode) {
case DebugInfoItem.DBG_END_SEQUENCE: {
break;
}
case DebugInfoItem.DBG_ADVANCE_PC: {
int oldAddrDiff = oldDbgInfoBuffer.readUleb128();
int newAddrDiff = newDbgInfoBuffer.readUleb128();
oldAddress += oldAddrDiff;
newAddress += newAddrDiff;
if (!insnComparator.isSameInstruction(oldAddress, newAddress)) {
return false;
}
break;
}
case DebugInfoItem.DBG_ADVANCE_LINE: {
int oldLineDiff = oldDbgInfoBuffer.readSleb128();
int newLineDiff = newDbgInfoBuffer.readSleb128();
oldLine += oldLineDiff;
newLine += newLineDiff;
if (oldLine != newLine) {
return false;
}
break;
}
case DebugInfoItem.DBG_START_LOCAL:
case DebugInfoItem.DBG_START_LOCAL_EXTENDED: {
int oldRegisterNum = oldDbgInfoBuffer.readUleb128();
int newRegisterNum = newDbgInfoBuffer.readUleb128();
if (oldRegisterNum != newRegisterNum) {
return false;
}
int oldNameIndex = oldDbgInfoBuffer.readUleb128p1();
int newNameIndex = newDbgInfoBuffer.readUleb128p1();
if (!isSameName(oldDex, newDex, oldNameIndex, newNameIndex)) {
return false;
}
int oldTypeIndex = oldDbgInfoBuffer.readUleb128p1();
int newTypeIndex = newDbgInfoBuffer.readUleb128p1();
if (!isSameClassDesc(oldDex, newDex, oldTypeIndex, newTypeIndex)) {
return false;
}
if (currOpCode == DebugInfoItem.DBG_START_LOCAL_EXTENDED) {
int oldSigIndex = oldDbgInfoBuffer.readUleb128p1();
int newSigIndex = newDbgInfoBuffer.readUleb128p1();
if (!isSameName(oldDex, newDex, oldSigIndex, newSigIndex)) {
return false;
}
}
break;
}
case DebugInfoItem.DBG_END_LOCAL:
case DebugInfoItem.DBG_RESTART_LOCAL: {
int oldRegisterNum = oldDbgInfoBuffer.readUleb128();
int newRegisterNum = newDbgInfoBuffer.readUleb128();
if (oldRegisterNum != newRegisterNum) {
return false;
}
break;
}
case DebugInfoItem.DBG_SET_FILE: {
int oldNameIndex = oldDbgInfoBuffer.readUleb128p1();
int newNameIndex = newDbgInfoBuffer.readUleb128p1();
if (!isSameName(oldDex, newDex, oldNameIndex, newNameIndex)) {
return false;
}
break;
}
case DebugInfoItem.DBG_SET_PROLOGUE_END:
case DebugInfoItem.DBG_SET_EPILOGUE_BEGIN: {
break;
}
default: {
int oldAdjustedOpcode = oldOpCode - DBG_FIRST_SPECIAL;
oldLine += DBG_LINE_BASE + (oldAdjustedOpcode % DBG_LINE_RANGE);
oldAddress += (oldAdjustedOpcode / DBG_LINE_RANGE);
int newAdjustedOpcode = newOpCode - DBG_FIRST_SPECIAL;
newLine += DBG_LINE_BASE + (newAdjustedOpcode % DBG_LINE_RANGE);
newAddress += (newAdjustedOpcode / DBG_LINE_RANGE);
if (oldLine != newLine) {
return false;
}
if (!insnComparator.isSameInstruction(oldAddress, newAddress)) {
return false;
}
break;
}
}
}
if (oldDbgInfoBuffer.available() > 0 || newDbgInfoBuffer.available() > 0) {
return false;
}
return true;
}
private boolean isSameTries(
Dex oldDex,
Dex newDex,
Code.Try[] oldTries,
Code.Try[] newTries,
InstructionComparator insnComparator
) {
if (oldTries.length != newTries.length) {
return false;
}
for (int i = 0; i < oldTries.length; ++i) {
Code.Try oldTry = oldTries[i];
Code.Try newTry = newTries[i];
if (oldTry.instructionCount != newTry.instructionCount) {
return false;
}
if (oldTry.catchHandlerIndex != newTry.catchHandlerIndex) {
return false;
}
if (!insnComparator.isSameInstruction(oldTry.startAddress, newTry.startAddress)) {
return false;
}
}
return true;
}
private boolean isSameCatchHandlers(
Dex oldDex,
Dex newDex,
Code.CatchHandler[] oldCatchHandlers,
Code.CatchHandler[] newCatchHandlers,
InstructionComparator insnComparator
) {
if (oldCatchHandlers.length != newCatchHandlers.length) {
return false;
}
for (int i = 0; i < oldCatchHandlers.length; ++i) {
Code.CatchHandler oldCatchHandler = oldCatchHandlers[i];
Code.CatchHandler newCatchHandler = newCatchHandlers[i];
int oldTypeAddrPairCount = oldCatchHandler.typeIndexes.length;
int newTypeAddrPairCount = newCatchHandler.typeIndexes.length;
if (oldTypeAddrPairCount != newTypeAddrPairCount) {
return false;
}
if (oldCatchHandler.catchAllAddress != -1 && newCatchHandler.catchAllAddress != -1) {
return insnComparator.isSameInstruction(
oldCatchHandler.catchAllAddress, newCatchHandler.catchAllAddress
);
} else {
if (!(oldCatchHandler.catchAllAddress == -1 && newCatchHandler.catchAllAddress == -1)) {
return false;
}
}
for (int j = 0; j < oldTypeAddrPairCount; ++j) {
if (!isSameClassDesc(
oldDex,
newDex,
oldCatchHandler.typeIndexes[j],
newCatchHandler.typeIndexes[j]
)) {
return false;
}
if (!insnComparator.isSameInstruction(
oldCatchHandler.addresses[j], newCatchHandler.addresses[j]
)) {
return false;
}
}
}
return true;
}
public static final class DexClassInfo {
public String classDesc = null;
public int classDefIndex = ClassDef.NO_INDEX;
public ClassDef classDef = null;
public Dex owner = null;
private DexClassInfo(String classDesc, int classDefIndex, ClassDef classDef, Dex owner) {
this.classDesc = classDesc;
this.classDef = classDef;
this.classDefIndex = classDefIndex;
this.owner = owner;
}
private DexClassInfo() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return classDesc;
}
@Override
public boolean equals(Object obj) {
DexClassInfo other = (DexClassInfo) obj;
if (!classDesc.equals(other.classDesc)) {
return false;
}
return owner.computeSignature(false).equals(other.owner.computeSignature(false));
}
}
public static final class DexGroup {
public final Dex[] dexes;
private DexGroup(Dex... dexes) {
if (dexes == null || dexes.length == 0) {
throw new IllegalArgumentException("dexes is null or empty.");
}
this.dexes = new Dex[dexes.length];
System.arraycopy(dexes, 0, this.dexes, 0, dexes.length);
}
private DexGroup(File... dexFiles) throws IOException {
if (dexFiles == null || dexFiles.length == 0) {
throw new IllegalArgumentException("dexFiles is null or empty.");
}
this.dexes = new Dex[dexFiles.length];
for (int i = 0; i < dexFiles.length; ++i) {
this.dexes[i] = new Dex(dexFiles[i]);
}
}
private DexGroup(List<File> dexFileList) throws IOException {
if (dexFileList == null || dexFileList.isEmpty()) {
throw new IllegalArgumentException("dexFileList is null or empty.");
}
this.dexes = new Dex[dexFileList.size()];
for (int i = 0; i < this.dexes.length; ++i) {
this.dexes[i] = new Dex(dexFileList.get(i));
}
}
private DexGroup() {
throw new UnsupportedOperationException();
}
public static DexGroup wrap(Dex... dexes) {
return new DexGroup(dexes);
}
public static DexGroup wrap(File... dexFiles) throws IOException {
return new DexGroup(dexFiles);
}
public static DexGroup wrap(List<File> dexFileList) throws IOException {
return new DexGroup(dexFileList);
}
public Set<DexClassInfo> getClassInfosInDexesWithDuplicateCheck() {
Map<String, DexClassInfo> classDescToInfoMap = new HashMap<String, DexClassInfo>();
for (Dex dex : dexes) {
int classDefIndex = 0;
for (ClassDef classDef : dex.classDefs()) {
String classDesc = dex.typeNames().get(classDef.typeIndex);
if (!classDescToInfoMap.containsKey(classDesc)) {
classDescToInfoMap.put(classDesc, new DexClassInfo(classDesc, classDefIndex, classDef, dex));
++classDefIndex;
} else {
throw new IllegalStateException(
String.format(
"duplicate class descriptor [%s] in different dexes.", classDesc
)
);
}
}
}
return new HashSet<DexClassInfo>(classDescToInfoMap.values());
}
}
}