use of com.tencent.tinker.android.dex.Dex in project tinker by Tencent.
the class DexDiffDecoder method collectAddedOrDeletedClasses.
/**
* Before starting real diff works, we collect added class descriptor
* and deleted class descriptor for further analysing in {@code checkCrossDexMovingClasses}.
*/
private void collectAddedOrDeletedClasses(File oldFile, File newFile) throws IOException {
Dex oldDex = new Dex(oldFile);
Dex newDex = new Dex(newFile);
Set<String> oldClassDescs = new HashSet<>();
for (ClassDef oldClassDef : oldDex.classDefs()) {
oldClassDescs.add(oldDex.typeNames().get(oldClassDef.typeIndex));
}
Set<String> newClassDescs = new HashSet<>();
for (ClassDef newClassDef : newDex.classDefs()) {
newClassDescs.add(newDex.typeNames().get(newClassDef.typeIndex));
}
Set<String> addedClassDescs = new HashSet<>(newClassDescs);
addedClassDescs.removeAll(oldClassDescs);
Set<String> deletedClassDescs = new HashSet<>(oldClassDescs);
deletedClassDescs.removeAll(newClassDescs);
for (String addedClassDesc : addedClassDescs) {
if (addedClassDescToDexNameMap.containsKey(addedClassDesc)) {
throw new TinkerPatchException(String.format("Class Duplicate. Class [%s] is added in both new dex: [%s] and [%s]. Please check your newly apk.", addedClassDesc, addedClassDescToDexNameMap.get(addedClassDesc), newFile.toString()));
} else {
addedClassDescToDexNameMap.put(addedClassDesc, newFile.toString());
}
}
for (String deletedClassDesc : deletedClassDescs) {
if (deletedClassDescToDexNameMap.containsKey(deletedClassDesc)) {
throw new TinkerPatchException(String.format("Class Duplicate. Class [%s] is deleted in both old dex: [%s] and [%s]. Please check your base apk.", deletedClassDesc, addedClassDescToDexNameMap.get(deletedClassDesc), oldFile.toString()));
} else {
deletedClassDescToDexNameMap.put(deletedClassDesc, newFile.toString());
}
}
}
use of com.tencent.tinker.android.dex.Dex in project tinker by Tencent.
the class ExcludedClassModifiedChecker method checkIfExcludedClassWasModifiedInNewDex.
public void checkIfExcludedClassWasModifiedInNewDex(File oldFile, File newFile) throws IOException, TinkerPatchException {
if (oldFile == null && newFile == null) {
throw new TinkerPatchException("both oldFile and newFile are null.");
}
oldDex = (oldFile != null ? new Dex(oldFile) : null);
newDex = (newFile != null ? new Dex(newFile) : null);
int stmCode = STMCODE_START;
while (stmCode != STMCODE_END) {
switch(stmCode) {
/**
* Check rule:
* Loader classes must only appear in primary dex and each of them in primary old dex should keep
* completely consistent in new primary dex.
*
* An error is announced when any of these conditions below is fit:
* 1. Primary old dex is missing.
* 2. Primary new dex is missing.
* 3. There are not any loader classes in primary old dex.
* 4. There are some new loader classes added in new primary dex.
* 5. Loader classes in old primary dex are modified in new primary dex.
* 6. Loader classes are found in secondary old dexes.
* 7. Loader classes are found in secondary new dexes.
*/
case STMCODE_START:
{
boolean isPrimaryDex = isPrimaryDex((oldFile == null ? newFile : oldFile));
if (isPrimaryDex) {
if (oldFile == null) {
stmCode = STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING;
} else if (newFile == null) {
stmCode = STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING;
} else {
dexCmptor.startCheck(oldDex, newDex);
deletedClassInfos = dexCmptor.getDeletedClassInfos();
addedClassInfos = dexCmptor.getAddedClassInfos();
changedClassInfosMap = new HashMap<>(dexCmptor.getChangedClassDescToInfosMap());
// All loader classes are in new dex, while none of them in old one.
if (deletedClassInfos.isEmpty() && changedClassInfosMap.isEmpty() && !addedClassInfos.isEmpty()) {
stmCode = STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX;
} else {
if (addedClassInfos.isEmpty()) {
// class descriptor is completely matches, see if any contents changes.
ArrayList<String> removeClasses = new ArrayList<>();
for (String classname : changedClassInfosMap.keySet()) {
if (Utils.checkFileInPattern(ignoreChangeWarning, classname)) {
Logger.e("loader class pattern: " + classname + " has changed, but it match ignore change pattern, just ignore!");
removeClasses.add(classname);
}
}
changedClassInfosMap.keySet().removeAll(removeClasses);
if (changedClassInfosMap.isEmpty()) {
stmCode = STMCODE_END;
} else {
stmCode = STMCODE_ERROR_LOADER_CLASS_CHANGED;
}
} else {
stmCode = STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH;
}
}
}
} else {
Set<Pattern> patternsOfClassDescToCheck = new HashSet<>();
for (String patternStr : config.mDexLoaderPattern) {
patternsOfClassDescToCheck.add(Pattern.compile(PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr)));
}
if (oldDex != null) {
oldClassesDescToCheck.clear();
for (ClassDef classDef : oldDex.classDefs()) {
String desc = oldDex.typeNames().get(classDef.typeIndex);
if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) {
oldClassesDescToCheck.add(desc);
}
}
if (!oldClassesDescToCheck.isEmpty()) {
stmCode = STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX;
break;
}
}
if (newDex != null) {
newClassesDescToCheck.clear();
for (ClassDef classDef : newDex.classDefs()) {
String desc = newDex.typeNames().get(classDef.typeIndex);
if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) {
newClassesDescToCheck.add(desc);
}
}
if (!newClassesDescToCheck.isEmpty()) {
stmCode = STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX;
break;
}
}
stmCode = STMCODE_END;
}
break;
}
case STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING:
{
throw new TinkerPatchException("old primary dex is missing.");
}
case STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING:
{
throw new TinkerPatchException("new primary dex is missing.");
}
case STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX:
{
throw new TinkerPatchException("all loader classes don't appear in old primary dex.");
}
case STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH:
{
throw new TinkerPatchException("there's loader classes added in new primary dex, such these changes will not take effect.\n" + "added classes: " + Utils.collectionToString(addedClassInfos));
}
case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX:
{
final String msg = "loader classes are found in old secondary dex. Found classes: " + Utils.collectionToString(oldClassesDescToCheck);
if (config.mAllowLoaderInAnyDex) {
Logger.d(msg);
return;
} else {
throw new TinkerPatchException(msg);
}
}
case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX:
{
final String msg = "loader classes are found in new secondary dex. Found classes: " + Utils.collectionToString(newClassesDescToCheck);
if (config.mAllowLoaderInAnyDex) {
Logger.d(msg);
return;
} else {
throw new TinkerPatchException(msg);
}
}
case STMCODE_ERROR_LOADER_CLASS_CHANGED:
{
String msg = "some loader class has been changed in new primary dex." + " Such these changes will not take effect!!" + " related classes: " + Utils.collectionToString(changedClassInfosMap.keySet());
throw new TinkerPatchException(msg);
}
default:
{
Logger.e("internal-error: unexpected stmCode.");
stmCode = STMCODE_END;
break;
}
}
}
}
use of com.tencent.tinker.android.dex.Dex in project tinker by Tencent.
the class DexDiffDecoder method generateChangedClassesDexFile.
@SuppressWarnings("NewApi")
private void generateChangedClassesDexFile() throws IOException {
final String dexMode = config.mDexRaw ? "raw" : "jar";
List<File> oldDexList = new ArrayList<>();
List<File> newDexList = new ArrayList<>();
for (AbstractMap.SimpleEntry<File, File> oldAndNewDexFilePair : oldAndNewDexFilePairList) {
File oldDexFile = oldAndNewDexFilePair.getKey();
File newDexFile = oldAndNewDexFilePair.getValue();
if (oldDexFile != null) {
oldDexList.add(oldDexFile);
}
if (newDexFile != null) {
newDexList.add(newDexFile);
}
}
DexGroup oldDexGroup = DexGroup.wrap(oldDexList);
DexGroup newDexGroup = DexGroup.wrap(newDexList);
ChangedClassesDexClassInfoCollector collector = new ChangedClassesDexClassInfoCollector();
collector.setExcludedClassPatterns(config.mDexLoaderPattern);
collector.setLogger(dexPatcherLoggerBridge);
collector.setIncludeRefererToRefererAffectedClasses(true);
Set<DexClassInfo> classInfosInChangedClassesDex = collector.doCollect(oldDexGroup, newDexGroup);
Set<Dex> owners = new HashSet<>();
Map<Dex, Set<String>> ownerToDescOfChangedClassesMap = new HashMap<>();
for (DexClassInfo classInfo : classInfosInChangedClassesDex) {
owners.add(classInfo.owner);
Set<String> descOfChangedClasses = ownerToDescOfChangedClassesMap.get(classInfo.owner);
if (descOfChangedClasses == null) {
descOfChangedClasses = new HashSet<>();
ownerToDescOfChangedClassesMap.put(classInfo.owner, descOfChangedClasses);
}
descOfChangedClasses.add(classInfo.classDesc);
}
StringBuilder metaBuilder = new StringBuilder();
int changedDexId = 1;
for (Dex dex : owners) {
Set<String> descOfChangedClassesInCurrDex = ownerToDescOfChangedClassesMap.get(dex);
DexFile dexFile = new DexBackedDexFile(org.jf.dexlib2.Opcodes.forApi(20), dex.getBytes());
boolean isCurrentDexHasChangedClass = false;
for (org.jf.dexlib2.iface.ClassDef classDef : dexFile.getClasses()) {
if (descOfChangedClassesInCurrDex.contains(classDef.getType())) {
isCurrentDexHasChangedClass = true;
break;
}
}
if (!isCurrentDexHasChangedClass) {
continue;
}
DexBuilder dexBuilder = new DexBuilder(Opcodes.forApi(23));
for (org.jf.dexlib2.iface.ClassDef classDef : dexFile.getClasses()) {
if (!descOfChangedClassesInCurrDex.contains(classDef.getType())) {
continue;
}
Logger.d("Class %s will be added into changed classes dex ...", classDef.getType());
List<BuilderField> builderFields = new ArrayList<>();
for (Field field : classDef.getFields()) {
final BuilderField builderField = dexBuilder.internField(field.getDefiningClass(), field.getName(), field.getType(), field.getAccessFlags(), field.getInitialValue(), field.getAnnotations());
builderFields.add(builderField);
}
List<BuilderMethod> builderMethods = new ArrayList<>();
for (Method method : classDef.getMethods()) {
MethodImplementation methodImpl = method.getImplementation();
if (methodImpl != null) {
methodImpl = new BuilderMutableMethodImplementation(dexBuilder, methodImpl);
}
BuilderMethod builderMethod = dexBuilder.internMethod(method.getDefiningClass(), method.getName(), method.getParameters(), method.getReturnType(), method.getAccessFlags(), method.getAnnotations(), methodImpl);
builderMethods.add(builderMethod);
}
dexBuilder.internClassDef(classDef.getType(), classDef.getAccessFlags(), classDef.getSuperclass(), classDef.getInterfaces(), classDef.getSourceFile(), classDef.getAnnotations(), builderFields, builderMethods);
}
// Write constructed changed classes dex to file and record it in meta file.
String changedDexName = null;
if (changedDexId == 1) {
changedDexName = "classes.dex";
} else {
changedDexName = "classes" + changedDexId + ".dex";
}
final File dest = new File(config.mTempResultDir + "/" + changedDexName);
final FileDataStore fileDataStore = new FileDataStore(dest);
dexBuilder.writeTo(fileDataStore);
final String md5 = MD5.getMD5(dest);
appendMetaLine(metaBuilder, changedDexName, "", md5, md5, 0, 0, 0, dexMode);
++changedDexId;
}
final String meta = metaBuilder.toString();
Logger.d("\nDexDecoder:write changed classes dex meta file data:\n%s", meta);
metaWriter.writeLineToInfoFile(meta);
}
use of com.tencent.tinker.android.dex.Dex in project tinker by Tencent.
the class DexDiffDecoder method diffDexPairAndFillRelatedInfo.
private void diffDexPairAndFillRelatedInfo(File oldDexFile, File newDexFile, RelatedInfo relatedInfo) {
File tempFullPatchDexPath = new File(config.mOutFolder + File.separator + TypedValue.DEX_TEMP_PATCH_DIR);
final String dexName = getRelativeDexName(oldDexFile, newDexFile);
File dexDiffOut = getOutputPath(newDexFile).toFile();
ensureDirectoryExist(dexDiffOut.getParentFile());
try {
DexPatchGenerator dexPatchGen = new DexPatchGenerator(oldDexFile, newDexFile);
dexPatchGen.setAdditionalRemovingClassPatterns(config.mDexLoaderPattern);
logWriter.writeLineToInfoFile(String.format("Start diff between [%s] as old and [%s] as new:", getRelativeStringBy(oldDexFile, config.mTempUnzipOldDir), getRelativeStringBy(newDexFile, config.mTempUnzipNewDir)));
dexPatchGen.executeAndSaveTo(dexDiffOut);
} catch (Exception e) {
throw new TinkerPatchException(e);
}
if (!dexDiffOut.exists()) {
throw new TinkerPatchException("can not find the diff file:" + dexDiffOut.getAbsolutePath());
}
relatedInfo.dexDiffFile = dexDiffOut;
relatedInfo.dexDiffMd5 = MD5.getMD5(dexDiffOut);
Logger.d("\nGen %s patch file:%s, size:%d, md5:%s", dexName, relatedInfo.dexDiffFile.getAbsolutePath(), relatedInfo.dexDiffFile.length(), relatedInfo.dexDiffMd5);
File tempFullPatchedDexFile = new File(tempFullPatchDexPath, dexName);
if (!tempFullPatchedDexFile.exists()) {
ensureDirectoryExist(tempFullPatchedDexFile.getParentFile());
}
try {
new DexPatchApplier(oldDexFile, dexDiffOut).executeAndSaveTo(tempFullPatchedDexFile);
Logger.d(String.format("Verifying if patched new dex is logically the same as original new dex: %s ...", getRelativeStringBy(newDexFile, config.mTempUnzipNewDir)));
Dex origNewDex = new Dex(newDexFile);
Dex patchedNewDex = new Dex(tempFullPatchedDexFile);
checkDexChange(origNewDex, patchedNewDex);
relatedInfo.newOrFullPatchedFile = tempFullPatchedDexFile;
relatedInfo.newOrFullPatchedMd5 = MD5.getMD5(tempFullPatchedDexFile);
relatedInfo.newOrFullPatchedCRC = FileOperation.getFileCrc32(tempFullPatchedDexFile);
} catch (Exception e) {
e.printStackTrace();
throw new TinkerPatchException("Failed to generate temporary patched dex, which makes MD5 generating procedure of new dex failed, either.", e);
}
if (!tempFullPatchedDexFile.exists()) {
throw new TinkerPatchException("can not find the temporary full patched dex file:" + tempFullPatchedDexFile.getAbsolutePath());
}
Logger.d("\nGen %s for dalvik full dex file:%s, size:%d, md5:%s", dexName, tempFullPatchedDexFile.getAbsolutePath(), tempFullPatchedDexFile.length(), relatedInfo.newOrFullPatchedMd5);
}
use of com.tencent.tinker.android.dex.Dex in project tinker by Tencent.
the class DexPatchApplier method executeAndSaveTo.
public void executeAndSaveTo(OutputStream out) throws IOException {
// Before executing, we should check if this patch can be applied to
// old dex we passed in.
byte[] oldDexSign = this.oldDex.computeSignature(false);
if (oldDexSign == null) {
throw new IOException("failed to compute old dex's signature.");
}
if (this.patchFile == null) {
throw new IllegalArgumentException("patch file is null.");
}
byte[] oldDexSignInPatchFile = this.patchFile.getOldDexSignature();
if (CompareUtils.uArrCompare(oldDexSign, oldDexSignInPatchFile) != 0) {
throw new IOException(String.format("old dex signature mismatch! expected: %s, actual: %s", Arrays.toString(oldDexSign), Arrays.toString(oldDexSignInPatchFile)));
}
// Firstly, set sections' offset after patched, sort according to their offset so that
// the dex lib of aosp can calculate section size.
TableOfContents patchedToc = this.patchedDex.getTableOfContents();
patchedToc.header.off = 0;
patchedToc.header.size = 1;
patchedToc.mapList.size = 1;
patchedToc.stringIds.off = this.patchFile.getPatchedStringIdSectionOffset();
patchedToc.typeIds.off = this.patchFile.getPatchedTypeIdSectionOffset();
patchedToc.typeLists.off = this.patchFile.getPatchedTypeListSectionOffset();
patchedToc.protoIds.off = this.patchFile.getPatchedProtoIdSectionOffset();
patchedToc.fieldIds.off = this.patchFile.getPatchedFieldIdSectionOffset();
patchedToc.methodIds.off = this.patchFile.getPatchedMethodIdSectionOffset();
patchedToc.classDefs.off = this.patchFile.getPatchedClassDefSectionOffset();
patchedToc.mapList.off = this.patchFile.getPatchedMapListSectionOffset();
patchedToc.stringDatas.off = this.patchFile.getPatchedStringDataSectionOffset();
patchedToc.annotations.off = this.patchFile.getPatchedAnnotationSectionOffset();
patchedToc.annotationSets.off = this.patchFile.getPatchedAnnotationSetSectionOffset();
patchedToc.annotationSetRefLists.off = this.patchFile.getPatchedAnnotationSetRefListSectionOffset();
patchedToc.annotationsDirectories.off = this.patchFile.getPatchedAnnotationsDirectorySectionOffset();
patchedToc.encodedArrays.off = this.patchFile.getPatchedEncodedArraySectionOffset();
patchedToc.debugInfos.off = this.patchFile.getPatchedDebugInfoSectionOffset();
patchedToc.codes.off = this.patchFile.getPatchedCodeSectionOffset();
patchedToc.classDatas.off = this.patchFile.getPatchedClassDataSectionOffset();
patchedToc.fileSize = this.patchFile.getPatchedDexSize();
Arrays.sort(patchedToc.sections);
patchedToc.computeSizesFromOffsets();
// Secondly, run patch algorithms according to sections' dependencies.
this.stringDataSectionPatchAlg = new StringDataSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.typeIdSectionPatchAlg = new TypeIdSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.protoIdSectionPatchAlg = new ProtoIdSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.fieldIdSectionPatchAlg = new FieldIdSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.methodIdSectionPatchAlg = new MethodIdSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.classDefSectionPatchAlg = new ClassDefSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.typeListSectionPatchAlg = new TypeListSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.annotationSetRefListSectionPatchAlg = new AnnotationSetRefListSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.annotationSetSectionPatchAlg = new AnnotationSetSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.classDataSectionPatchAlg = new ClassDataSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.codeSectionPatchAlg = new CodeSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.debugInfoSectionPatchAlg = new DebugInfoItemSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.annotationSectionPatchAlg = new AnnotationSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.encodedArraySectionPatchAlg = new StaticValueSectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.annotationsDirectorySectionPatchAlg = new AnnotationsDirectorySectionPatchAlgorithm(patchFile, oldDex, patchedDex, oldToPatchedIndexMap);
this.stringDataSectionPatchAlg.execute();
this.typeIdSectionPatchAlg.execute();
this.typeListSectionPatchAlg.execute();
this.protoIdSectionPatchAlg.execute();
this.fieldIdSectionPatchAlg.execute();
this.methodIdSectionPatchAlg.execute();
this.annotationSectionPatchAlg.execute();
this.annotationSetSectionPatchAlg.execute();
this.annotationSetRefListSectionPatchAlg.execute();
this.annotationsDirectorySectionPatchAlg.execute();
this.debugInfoSectionPatchAlg.execute();
this.codeSectionPatchAlg.execute();
this.classDataSectionPatchAlg.execute();
this.encodedArraySectionPatchAlg.execute();
this.classDefSectionPatchAlg.execute();
// Thirdly, write header, mapList. Calculate and write patched dex's sign and checksum.
Dex.Section headerOut = this.patchedDex.openSection(patchedToc.header.off);
patchedToc.writeHeader(headerOut);
Dex.Section mapListOut = this.patchedDex.openSection(patchedToc.mapList.off);
patchedToc.writeMap(mapListOut);
this.patchedDex.writeHashes();
// Finally, write patched dex to file.
this.patchedDex.writeTo(out);
}
Aggregations