/* * * * 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()); } } }