一、背景及收益
升级sdk或更新依赖库后,可能因为库之间依赖的版本号不同,API有变动时会报:NoSuchMethodError 等错误
二、ByteX实现原理
ByteX是一个基于gradle transform api和ASM的字节码而实现的
三、bytex-referCheck 检测插件的整体实现思路:
- 将所有的子插件注册到宿主插件中,并给每个子插件绑定一个TransformFlow【默认为全局MainTransformFlow】
-
宿主插件的Transform方法中遍历执行每个子插件的TransformFlow执行run方法对.class进行处理:
首先遍历工程中所有的构建产物(.class)和 遍历android.jar里的所有class文件生成类结构图【map:key(className),value(类/接口node)】;
然后再遍历一次工程中所有的构建产物,将每个class文件传给bytex-referCheck子插件的transform方法处理 - 子插件ReferCheckPlugin的transform方法中结合第一次遍历的生成类图,与 ASM ClassVisitor 进行合法性验证
四、名词解释
- traverse过程:遍历一次工程中所有的构建产物(一般来说是class文件),单纯做遍历分析,不对输入文件做修改;
- traverseAndroidJar过程:遍历android.jar里的所有class文件 (哪个版本的android.jar由工程中的target api决定),主要是为了形成完整的类图.
- transform:再遍历一次工程中所有的构建产物,并对class文件做处理后输出(可能是直接回写到本地,也可能作为下一个处理过程的输入)
- 类图Graph:Graph里维护一个map集合 key为类名,value 是类或者接口节点
五、关键点
1:构建项目的类图,以遍历工程的构建产物(通常为.class)为例
公共TransformFlow: MainTransformFlow执行runTransform()方法中调用TransformEngine.traverseOnly 遍历产物构建类图
MainTransformFlow.java
private void runTransform() {
...
// 仅执行遍历任务
traverseArtifactOnly(getProcessors(Process.TRAVERSE, new ClassFileAnalyzer(context, Process.TRAVERSE, graphBuilder, new ArrayList<>(handlers))));
...
}
getProcessors:根据传入的Process.TRAVERSE(枚举类型)遍历和FileHandler子类
ClassFileAnalyzer
【后续具体用来处理class】
获取文件处理器数组:FileProcessor[]
private FileProcessor[] getProcessors(Process process, FileHandler fileHandler) {
// 在 traverse(遍历) 和 transform(转换) 的过程中,加入自定义的 FileProcessor,提供更大的灵活性
List<FileProcessor> processors = handlers.stream()
.flatMap((Function<MainProcessHandler, Stream<FileProcessor>>) handler -> handler.process(process).stream())
.collect(Collectors.toList());
switch (process) {
// 增量遍历状态
case TRAVERSE_INCREMENTAL:
// FilterFileProcessor:按照指定的条件过滤掉不需要的 FileData,此时的过滤条件是文件的状态不是 Status.NOTCHANGED
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED));
// IncrementalFileProcessor 增量文件处理器,其中包含一个 ClassFileProcessor(FileHandler) 参数,用于对文件解析使用
processors.add(new IncrementalFileProcessor(new ArrayList<>(handlers), ClassFileProcessor.newInstance(fileHandler)));
break;
case TRAVERSE:
case TRAVERSE_ANDROID:
case TRANSFORM:
// ClassFileProcessor 类文件处理器,其中包含一个 ClassFileProcessor(FileHandler) 参数,用于对文件解析使用。
processors.add(ClassFileProcessor.newInstance(fileHandler));
// FilterFileProcessor 按照指定的条件过滤掉不需要的 FileData,此时的过滤条件是文件的状态不是 Status.NOTCHANGED 和 Status.REMOVED
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED && fileData.getStatus() != Status.REMOVED));
break;
default:
throw new RuntimeException("Unknow Process:" + process);
}
return processors.toArray(new FileProcessor[0]);
}
traverseArtifactOnly:遍历
protected AbsTransformFlow traverseArtifactOnly(FileProcessor... processors) throws IOException, InterruptedException {
transformEngine.traverseOnly(processors);
return this;
}
TransformEngine.java
/**
* 线程池 执行 遍历 任务
* @param processors
*/
public void traverseOnly(FileProcessor... processors) {
Schedulers.FORKJOINPOOL().invoke(new PerformTraverseTask(context.allFiles(), getProcessorList(processors)));
}
线程池执行PerformTraverseTask遍历任务,
context.allFiles()是传入所有文件,
经过一些列的任务转换
PerformTraverseTask->FileTraverseTask->TraverseTask.compute(),在此方法中将processors文件处理集合创建ProcessorChain,对文件进行链式处理
@Override
protected void compute() {
try {
Input input = new Input(fileCache.getContent(), file);
// 调用 ProcessorChain 的 proceed 方法。 初始index 0 链式调用
ProcessorChain chain = new ProcessorChain(processors, input, 0);
chain.proceed(input);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
ProcessorChain的 proceed方法
@Override
public Output proceed(Input input) throws IOException {
if (index >= processors.size()) throw new AssertionError();
FileProcessor next = processors.get(index);
return next.process(new ProcessorChain(processors, input, index + 1));
}
这里主要看 ClassFileProcessor即class文件处理器的process方法
@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
FileData fileData = input.getFileData();
if (fileData.getRelativePath().endsWith(".class")) {
// case 1、MainTransformFlow 里执行的是遍历任务 traverseArtifactOnly、traverseAndroidJarOnly的话 传入的 FileHandler 为 ClassFileAnalyzer 那么遍历任务会执行 ClassFileAnalyzer里的 handle方法处理
// case 2、MainTransformFlow 里执行的是transform 任务: 传入的FileHandler 为 ClassFileTransformer 并且 如果 fileData 是.class 文件 ,则调用 ClassFileTransformer 的 handle 方法进行处理。
handler.handle(fileData);
}
// 2、非 类 使用 FileProcessor 列表 最后一个 BackupFileProcessor 的 proceed 则 结束链式调用
return chain.proceed(input);
}
判断如果是class文件,则调用 handler.handle(fileData);处理文件,而handler就是在getProcessors时传入的ClassFileAnalyzer。
ClassFileAnalyzer的handle方法:
@Override
public void handle(FileData fileData) {
try {
...
...
...
byte[] raw = fileData.getBytes();
String relativePath = fileData.getRelativePath();
// 构建 ClassReader 对象,解析文件
ClassReader cr = new ClassReader(raw);
int flag = getFlag(handlers);
// 创建 ClassVisitorChain 对象,本质是 ClassWriter
ClassVisitorChain chain = getClassVisitorChain(relativePath);
// 如果类图构建器为空,则创建 GenerateGraphClassVisitor 类读取类文件,并构建类图
if (this.mGraphBuilder != null) {
// GenerateGraphClassVisitor 根据ClassReader 传入的构建产物.class 然后构建class实体 添加到mGraphBuilder 里 生成类图
chain.connect(new GenerateGraphClassVisitor(process == TRAVERSE_ANDROID, mGraphBuilder));
}
// 根据不同的状态,回调 Plugin 对应的处理过程
pluginList.forEach(plugin -> {
switch (process) {
case TRAVERSE_INCREMENTAL:
plugin.traverseIncremental(fileData, chain);
break;
case TRAVERSE:
plugin.traverse(relativePath, chain);
break;
case TRAVERSE_ANDROID:
// 遍历 Android.jar
plugin.traverseAndroidJar(relativePath, chain);
break;
default:
throw new RuntimeException("Unsupported Process");
}
});
ClassNode cn = new SafeClassNode();
chain.append(cn);
chain.accept(cr, flag);
...
...
...
} catch (Exception e) {
...
}
}
根据ASM ClassVisitor 实现类类GenerateGraphClassVisitor 对类进行访问 创建对应类的节点:
public class GenerateGraphClassVisitor extends BaseClassVisitor {
/**
* 根据构建产物 生成的 class 实体 用于构建类图
*/
private ClassEntity entity;
/**
* 是否来源于 Android SDK
*/
private boolean fromAndroidSDK;
/**
* 类图构造者
*/
private GraphBuilder mGraphBuilder;
private AtomicInteger count = new AtomicInteger();
public GenerateGraphClassVisitor(boolean fromAndroidSDK, @Nonnull GraphBuilder graphBuilder) {
this.fromAndroidSDK = fromAndroidSDK;
this.mGraphBuilder = graphBuilder;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// 创建 class 实体
entity = new ClassEntity(access, name, superName, interfaces == null ? Collections.emptyList() : Arrays.asList(interfaces));
entity.fromAndroid = fromAndroidSDK;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if(name.equals("wrappedEntity")){
Log.e("flag","-----:"+name+"---:"+entity.name);
}
entity.fields.add(new FieldEntity(access, entity.name, name, desc, signature));
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
entity.methods.add(new MethodEntity(access, entity.name, name, desc, exceptions));
return super.visitMethod(access, name, desc, signature, exceptions);
}
/**
* 访问完后 把每个元素 添加到 类图构建器里
*/
@Override
public void visitEnd() {
super.visitEnd();
mGraphBuilder.add(entity);
Log.e("tag","-----count"+count.incrementAndGet()+"---:"+entity.name);
}
}
对类访问完后,把构建的元素添加到类图构建器里,内部维护一个map,添加到指定的父类或者接口节点下,来拼装完整的类图
public void add(ClassEntity entity, boolean fromCache) {
// 1、查找元素
final Node current = getOrPutEmpty((entity.access & Opcodes.ACC_INTERFACE) != 0, entity.name);
// 2、检察器: 没检察过 就进入 并且将标记 设为 true
if (!current.defined.compareAndSet(false, true)) {
if (fromCache) {
//先正式添加后面再添加cache,防止cache覆盖了新的数据,此处return
return;
}
if (!entity.fromAndroid && !isCacheValid()) {
String msg = String.format("We found duplicate %s class files in the project.", current.entity.name);
if (BooleanProperty.ENABLE_DUPLICATE_CLASS_CHECK.value() && !"module-info".equals(current.entity.name)) {
throw new DuplicateClassException(msg);
} else {
LevelLog.sDefaultLogger.e(msg);
}
}
}
// 3、记录父类节点,,并将 子类 添加到父类的 子类节点集合里
ClassNode superNode = null;
List<InterfaceNode> interfaceNodes = Collections.emptyList();
// 父类 不为空
if (entity.superName != null) {
// 获取父类节点
Node node = getOrPutEmpty(false, entity.superName);
if (node instanceof ClassNode) {
superNode = (ClassNode) node;
// all interfaces extends java.lang.Object
// make java.lang.Object subclasses purely
// 当前也是类节点, 则将当前类 添加到 父类节点的子节点集合里
if (current instanceof ClassNode) {
synchronized (superNode) {
if (superNode.children == Collections.EMPTY_LIST) {
superNode.children = new LinkedList<>();
}
superNode.children.add((ClassNode) current);
}
}
} else {
throw new RuntimeException(String.format("%s is not a class. Maybe there are duplicate class files in the project.", entity.superName));
}
}
// 4、判断 当前 类实体 所实现的接口 不为空 作用处理接口
if (entity.interfaces.size() > 0) {
interfaceNodes = entity.interfaces.stream()
.map(i -> {
Node node = getOrPutEmpty(true, i);
if (node instanceof InterfaceNode) {
final InterfaceNode interfaceNode = (InterfaceNode) node;
synchronized (interfaceNode) {
// 添加到子接口集合里
if (current instanceof InterfaceNode) {
if (interfaceNode.children == Collections.EMPTY_LIST) {
interfaceNode.children = new LinkedList<>();
}
interfaceNode.children.add((InterfaceNode) current);
} else if (current instanceof ClassNode) {
// 添加到实现类 节点集合里
if (interfaceNode.implementedClasses == Collections.EMPTY_LIST) {
interfaceNode.implementedClasses = new LinkedList<>();
}
interfaceNode.implementedClasses.add((ClassNode) current);
}
}
return (InterfaceNode) node;
} else {
throw new RuntimeException(String.format("%s is not a interface. Maybe there are duplicate class files in the project.", i));
}
})
.collect(Collectors.toList());
}
current.entity = entity;
current.parent = superNode;
current.interfaces = interfaceNodes;
}
遍历完工程的构建产物还会遍历android jar里的产物,流程与上述类似。全部遍历完成后就生成了完整的工程类图:包含类关系的map 节点集合
2:根据类图再次遍历所有class验证类、接口、方法、字段合法性
再次遍历执行的处理类型是Process.TRANSFORM
FileHandler子类
ClassFileTransformer
->后续具体用来处理class
transform(getProcessors(Process.TRANSFORM, new ClassFileTransformer(context, new ArrayList<>(handlers), needPreVerify(), needVerify())));
执行任务流程一样,最终构建ProcessorChain,不过这次的ClassFileProcessor class文件处理器的FileHandler是上边传入的ClassFileTransformer,直接看它的handle方法,其中关键代码:遍历子插件 将class路径 和 ClassVisitorChain 传给子插件的transform方法
for (MainProcessHandler handler : handlers) {
// 3、遍历执行所有 plugin 的 transform。其内部会使用 chain.connect(new ReferCheckClassVisitor(context)) 的方式
// 比如 ReferCheckPlugin 里的 transform 方法
if (!handler.transform(relativePath, chain)) {
fileData.delete();
return;
}
}
即ReferCheckPlugin,非法引用插件的transform方法中:
@Override
public boolean transform(@Nonnull String relativePath, @Nonnull ClassVisitorChain chain) {
ReferCheckClassVisitor referCheckClassVisitor = new ReferCheckClassVisitor(context.extension.isCheckInaccessOverrideMethodStrictly(), context, context.getClassGraph(), context.getBlockMethodCallMatcher());
referCheckClassVisitor.setReferCheckContext(context);
chain.connect(referCheckClassVisitor);
return super.transform(relativePath, chain);
}
在该子插件中借助ASM api classVisitor对每个类,方法,字段进行访问验证
对类的验证:
ReferCheckClassVisitor.java classvisitor的方法都是按顺序执行的,首先访问visit方法
/**
* 第一个被执行
* des:访问类的头部
* @param version 指类创建时使用的 JDK 的版本,比如 50 代表 JDK1.6、51 代表 JDK1.7
* @param access 代表类的访问权限,比如 public 、private
* @param name 表示类名
* @param signature 表示类的签名,如果类不是泛型或者没有继承泛型类,那么signature 值为空
* @param superName 表示父类的名称
* @param interfaces 表示实现的接口
*/
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {}
重写方法的验证:执行visitMethod
/**
* 倒数第二被执行
* des: 访问方法
* @param access 代表方法 的访问权限,比如 public 、private
* @param methodName 方法名字
* @param desc 方法的描述包名
* @param signature
* @param exceptions 方法的异常类的内部名称
* @return
*/
@Override
public MethodVisitor visitMethod(int access, String methodName, String desc, String signature, String[] exceptions) {
...
...
checkOverride()
...
...
}
/**
* 检查重写的方法 : 重写后 子类 重写的方法 访问权限 不能低于 父类方法的访问权限
* @param node 父类节点
* @param access 方法 的访问权限,比如 public 、private
* @param methodName 方法名字
* @param desc 方法的描述 包名
*/
private void checkOverride(ClassNode node, int access, String methodName, String desc) {}
首先递归调用checkOverride 检测重写 父类 或者父接口的方法:
非重写的方法验证:原始定义方法节点为抽象方法
ReferCheckMethodVisitor.java里借助ASM的MethodVisitor,验证非native 非重写的方法
/**
* 开始检测方法
* @param opcode 要访问的类型指令的操作码。此操作码可以是INVOKEVIRTUAL、INVOKESPECIAL、INVOKESTATIC或INVOKEINTERFACE。
* @param owner 方法所在的类名
* @param name 方法名
* @param desc 方法描述
* @param itf 是否是接口
*/
private void checkMethod(final int opcode, final String owner, final String name, final String desc, final boolean itf) {}
非重写的方法验证:原始定义方法节点为非抽象方法
/**
* 验证是否能访问 指定的方法/字段
* @param opcode 要访问的类型指令的操作码。此操作码可以是
* INVOKEVIRTUAL[调用实例方法]、
* INVOKESPECIAL【调用构造器方法,private方法,或者超类方法】、
* INVOKESTATIC【调用静态方法】
* INVOKEINTERFACE【调用接口方法】。
* @param originMember 原始的方法实体
* @return
*/
private boolean accessible(int opcode, MemberEntity originMember) {
// 是否是静态方法
boolean isStaticMember = TypeUtil.isStatic(originMember.access());
// 1 opcode 指令是 要调用静态方法 且 当前方法也是静态方法
// 2 GETSTATIC 设置 / 获取静态字段 且 字段的也是静态的
if ((opcode == Opcodes.INVOKESTATIC) == isStaticMember ||
(opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC) == isStaticMember) {
return accessible(originMember);
}
return false;
}
/**
* 验证是否能访问指定的方法
* @param member
* @return
*/
private boolean accessible(MemberEntity member) {
// 当前类是 方法/字段 所在的类
if (className.equals(member.className())) {
return true;
}
// 方法/字段 是public
if (TypeUtil.isPublic(member.access())) {
return true;
} else if (TypeUtil.isProtected(member.access())) {
//同包名或者继承关系 At same package or inheritance relationship
return Utils.getPackage(className).equals(Utils.getPackage(member.className())) ||
graph.get(this.className).inheritFrom(graph.get(member.className()));
} else if (TypeUtil.isPrivate(member.access())) {
// 私有方法/字段 就无法访问
return false;
} else {
//同包名
return Utils.getPackage(className).equals(Utils.getPackage(member.className()));
}
}
visitFieldInsn验证字段可访问:
/**
* 检查字段
* @param opcode 要访问的类型指令的操作码。此操作码为GETSTATIC、PUTSTATIC、GETFIELD或PUTFIELD。
* @param name 字段名
* @param desc 字段的描述
* @param owner 字段所属的类名
*/
private void checkField(int opcode, String name, String desc, String owner) {}
六、bytex-referCheck原理总结
主要三个过程
- 将检测子插件ReferCheckPlugin与bytex宿主插件关联
- 在宿主插件的transform中遍历子插件的transform【默认为全局的MainTransformFlow】对遍历产物处理,借助ASM API对类,接口,方法,字段等进行访问,构建相应实体进而构建完整类图
- 再次遍历构建产物,将class交给子插件ReferCheckPlugin 借助ASM API 进行类/接口,方法,字段访问,借助完整工程的类结构图,进行合法性验证
七、检测常见错误
错误类型一:找不到方法 [Method Not Found]
[exec] [Method Not Found] in com/wuba/commoncode/network/toolbox/HttpClientStack(HttpClientStack.java:155)
[exec] static org.apache.http.client.methods.HttpUriRequest createHttpRequest(com.wuba.commoncode.network.Request,java.util.Map){
[exec] ->void com.wuba.commoncode.network.toolbox.HttpClientStack$HttpPatch.addHeader(java.lang.String,java.lang.String)
[exec] }
错误类型二:方法未实现 [Method Not Implement]
[exec] [Method Not Implement] in com/wuba/wubacomponentapi/net/INetWork(INetWork.java:0)
[exec] public rx.Observable uploadBytesAsync(java.lang.String,java.util.Map,java.util.Map,java.lang.String,java.lang.String,byte[]){
[exec] ->public rx.Observable com.wuba.newcar.base.hybrid.INetWorkImpl.uploadBytesAsync(java.lang.String,java.util.Map,java.util.Map,java.lang.String,java.lang.String,byte[])
[exec] }
[exec] [Method Not Implement] in com/wuba/wubacomponentapi/net/INetWork(INetWork.java:0)
[exec] public java.lang.String uploadBytesSync(java.lang.String,java.util.Map,java.util.Map,java.lang.String,java.lang.String,byte[]){
[exec] ->public java.lang.String com.wuba.newcar.base.hybrid.INetWorkImpl.uploadBytesSync(java.lang.String,java.util.Map,java.util.Map,java.lang.String,java.lang.String,byte[])
[exec] }
错误类型三:无法访问重写的方法 [Can Not Access Overridden Method ]
[exec] [Can Not Access Overridden Method] in com/uc/webview/export/internal/android/h(U4Source:-1)
[exec] protected final com.uc.webview.export.WebHistoryItem createItem(android.webkit.WebHistoryItem){
[exec] ->public com.uc.webview.export.WebHistoryItem com.uc.webview.export.WebBackForwardList.createItem(android.webkit.WebHistoryItem)
[exec] }
错误原因:父类 WebBackForwardList的 createItem 方法修饰符是public 子类com/uc/webview/export/internal/android/h重写后改为protected