/*
* 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.DexPatcherLogger;
import com.taobao.dex.ClassData;
import com.taobao.dex.Code;
import com.taobao.dex.Dex;
import com.taobao.dx.instruction.InstructionReader;
import com.taobao.dx.instruction.ShortArrayCodeInput;
import java.io.EOFException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Created by tangyinsheng on 2016/10/8.
*/
public final class SmallDexClassInfoCollector {
private static final String TAG = "SmallDexClassInfoCollector";
private static final DexPatcherLogger logger = new DexPatcherLogger();
private final Set<String> loaderClassPatterns = new HashSet<String>();
public SmallDexClassInfoCollector setLoaderClassPatterns(Collection<String> loaderClassPatterns) {
this.loaderClassPatterns.clear();
this.loaderClassPatterns.addAll(loaderClassPatterns);
return this;
}
public SmallDexClassInfoCollector addLoaderClassPattern(String loaderClassPattern) {
this.loaderClassPatterns.add(loaderClassPattern);
return this;
}
public SmallDexClassInfoCollector clearLoaderClassPattern() {
this.loaderClassPatterns.clear();
return this;
}
public SmallDexClassInfoCollector setLogger(DexPatcherLogger.IDexPatcherLogger loggerImpl) {
this.logger.setLoggerImpl(loggerImpl);
return this;
}
public Set<DexClassesComparator.DexClassInfo> doCollect(DexClassesComparator.DexGroup oldDexGroup, DexClassesComparator.DexGroup newDexGroup) {
DexClassesComparator dexClassesCmp = new DexClassesComparator("*");
dexClassesCmp.setCompareMode(DexClassesComparator.COMPARE_MODE_CAUSE_REF_CHANGE_ONLY);
dexClassesCmp.setIgnoredRemovedClassDescPattern(this.loaderClassPatterns);
dexClassesCmp.startCheck(oldDexGroup, newDexGroup);
Set<String> refAffectedClassDescs
= dexClassesCmp.getChangedClassDescToInfosMap().keySet();
Set<DexClassesComparator.DexClassInfo> classInfosInNewDexGroup
= newDexGroup.getClassInfosInDexesWithDuplicateCheck();
Set<DexClassesComparator.DexClassInfo> classInfosOfSmallDex = new HashSet<DexClassesComparator.DexClassInfo>();
for (DexClassesComparator.DexClassInfo patchedClassInfo : classInfosInNewDexGroup) {
if (patchedClassInfo.classDef.classDataOffset == 0) {
continue;
}
ClassData patchedClassData
= patchedClassInfo.owner.readClassData(patchedClassInfo.classDef);
boolean shouldAdd = isClassMethodReferenceToRefAffectedClass(
patchedClassInfo.owner,
patchedClassData.directMethods,
refAffectedClassDescs
);
if (!shouldAdd) {
shouldAdd = isClassMethodReferenceToRefAffectedClass(
patchedClassInfo.owner,
patchedClassData.virtualMethods,
refAffectedClassDescs
);
}
if (shouldAdd) {
logger.i(TAG, "Add class %s to small dex.", patchedClassInfo.classDesc);
classInfosOfSmallDex.add(patchedClassInfo);
}
}
// So far we get descriptors of classes we need to add additionally,
// while we still need to do a fully compare to collect added classes
// and replaced classes since they may use items in their owner dex which
// is not modified.
dexClassesCmp.setCompareMode(DexClassesComparator.COMPARE_MODE_NORMAL);
dexClassesCmp.startCheck(oldDexGroup, newDexGroup);
Collection<DexClassesComparator.DexClassInfo> addedClassInfos = dexClassesCmp.getAddedClassInfos();
for (DexClassesComparator.DexClassInfo addClassInfo : addedClassInfos) {
logger.i(TAG, "Add class %s to small dex.", addClassInfo.classDesc);
classInfosOfSmallDex.add(addClassInfo);
}
Collection<DexClassesComparator.DexClassInfo[]> changedOldPatchedClassInfos =
dexClassesCmp.getChangedClassDescToInfosMap().values();
// changedOldPatchedClassInfo[1] means changedPatchedClassInfo
for (DexClassesComparator.DexClassInfo[] changedOldPatchedClassInfo : changedOldPatchedClassInfos) {
logger.i(TAG, "Add class %s to small dex.", changedOldPatchedClassInfo[1].classDesc);
classInfosOfSmallDex.add(changedOldPatchedClassInfo[1]);
}
return classInfosOfSmallDex;
}
private boolean isClassMethodReferenceToRefAffectedClass(
Dex owner,
ClassData.Method[] methods,
Collection<String> affectedClassDescs
) {
if (affectedClassDescs.isEmpty() || methods == null || methods.length == 0) {
return false;
}
for (ClassData.Method method : methods) {
if (method.codeOffset == 0) {
continue;
}
Code code = owner.readCode(method);
RefToRefAffectedClassInsnVisitor refInsnVisitor =
new RefToRefAffectedClassInsnVisitor(owner, method, affectedClassDescs, logger);
InstructionReader insnReader =
new InstructionReader(new ShortArrayCodeInput(code.instructions));
try {
insnReader.accept(refInsnVisitor);
if (refInsnVisitor.isMethodReferencedToRefAffectedClass) {
return true;
}
} catch (EOFException e) {
throw new IllegalStateException(e);
}
}
return false;
}
}