bytex-refercheck原理解析详细

一、背景及收益

升级sdk或更新依赖库后,可能因为库之间依赖的版本号不同,API有变动时会报:NoSuchMethodError 等错误

二、ByteX实现原理

ByteX是一个基于gradle transform api和ASM的字节码而实现的

三、bytex-referCheck 检测插件的整体实现思路:

  1. 将所有的子插件注册到宿主插件中,并给每个子插件绑定一个TransformFlow【默认为全局MainTransformFlow】
  2. 宿主插件的Transform方法中遍历执行每个子插件的TransformFlow执行run方法对.class进行处理:
    首先遍历工程中所有的构建产物(.class)和 遍历android.jar里的所有class文件生成类结构图【map:key(className),value(类/接口node)】;
    然后再遍历一次工程中所有的构建产物,将每个class文件传给bytex-referCheck子插件的transform方法处理
  3. 子插件ReferCheckPlugin的transform方法中结合第一次遍历的生成类图,与 ASM ClassVisitor 进行合法性验证

四、名词解释

  1. traverse过程:遍历一次工程中所有的构建产物(一般来说是class文件),单纯做遍历分析,不对输入文件做修改;
  2. traverseAndroidJar过程:遍历android.jar里的所有class文件 (哪个版本的android.jar由工程中的target api决定),主要是为了形成完整的类图.
  3. transform:再遍历一次工程中所有的构建产物,并对class文件做处理后输出(可能是直接回写到本地,也可能作为下一个处理过程的输入)
  4. 类图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) {}

类的验证.png

重写方法的验证:执行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 检测重写 父类 或者父接口的方法:

重写方法验证.png

非重写的方法验证:原始定义方法节点为抽象方法

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) {}

方法的检测.png

非重写的方法验证:原始定义方法节点为非抽象方法

    /**
     *  验证是否能访问  指定的方法/字段
     * @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) {}

字段单独验证.png

六、bytex-referCheck原理总结

主要三个过程

  1. 将检测子插件ReferCheckPlugin与bytex宿主插件关联
  2. 在宿主插件的transform中遍历子插件的transform【默认为全局的MainTransformFlow】对遍历产物处理,借助ASM API对类,接口,方法,字段等进行访问,构建相应实体进而构建完整类图
  3. 再次遍历构建产物,将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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值