/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.Configuration;
import com.taobao.dex.ClassDef;
import com.taobao.dex.Dex;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Created by tangyinsheng on 2016/4/14.
*/
public final class ExcludedClassModifiedChecker {
private static final int STMCODE_START = 0x00;
private static final int STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING = 0x01;
private static final int STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING = 0x02;
private static final int STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX = 0x03;
private static final int STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH = 0x04;
private static final int STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX = 0x05;
private static final int STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX = 0x06;
private static final int STMCODE_ERROR_LOADER_CLASS_CHANGED = 0x07;
private static final int STMCODE_END = 0x08;
private final Configuration config;
private final DexClassesComparator dexCmptor;
private Dex oldDex = null;
private Dex newDex = null;
private List<DexClassesComparator.DexClassInfo> deletedClassInfos = null;
private List<DexClassesComparator.DexClassInfo> addedClassInfos = null;
private Map<String, DexClassesComparator.DexClassInfo[]> changedClassInfosMap = null;
private Set<String> oldClassesDescToCheck = new HashSet<String>();
private Set<String> newClassesDescToCheck = new HashSet<String>();
public ExcludedClassModifiedChecker(Configuration config) {
this.config = config;
this.dexCmptor = new DexClassesComparator(config.mDexLoaderPattern);
}
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, deleted 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 = 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 (deletedClassInfos.isEmpty() && addedClassInfos.isEmpty()) {
// class descriptor is completely matches, see if any contents changes.
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<Pattern>();
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(
"loader classes in old primary dex are mismatched to those in new primary dex, \n"
+ "if deleted classes is not empty, check if your dex division strategy is fine. \n"
+ "added classes: " + Utils.collectionToString(addedClassInfos) + "\n"
+ "deleted classes: " + Utils.collectionToString(deletedClassInfos)
);
}
case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX: {
throw new TinkerPatchException("loader classes are found in old secondary dex. Found classes: " + Utils.collectionToString(oldClassesDescToCheck));
}
case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX: {
throw new TinkerPatchException("loader classes are found in new secondary dex. Found classes: " + Utils.collectionToString(newClassesDescToCheck));
}
case STMCODE_ERROR_LOADER_CLASS_CHANGED: {
String msg =
"some loader class has been changed in new 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;
}
}
}
}
public boolean isPrimaryDex(File dexFile) {
return true;
}
}