用rust 写一个jar包 class冲突检测工具

时间:2023-05-23 23:49:41 来源: 博客园


Rust很适合写命令行工具,特别是使用clapcrate 更加方便,这篇文章介绍使用rust写一个jar包class冲突检测的工具。项目地址: https://github.com/Aitozi/jar_conflict_detector首先jar包class冲突的现象是多个jar包中有同名的class,并且class的md5还不一样,那么就意味着该class存在多个版本,那么就存在冲突的可能。思路比较简单,就是遍历每个jar包,记录ClassName 和 对应 CRC 校验码 及 jar 包的对应关系。通过clap的derive api就可以快速定义个命令行的参数解析器。


(相关资料图)

#[derive(Parser, Debug)]#[command(author, version, about, long_about = None)]struct Args {    #[arg(        short,        long = "jars",        required = true,        help = "The jar list joined by semicolon"    )]    jar_list: String,    #[arg(long, help = "Disable the crc check", action = clap::ArgAction::SetTrue)]    #[arg(default_value_t = false)]    disable_crc: bool,    #[arg(short, long, action = clap::ArgAction::Append, help = "The exclude package prefix")]    exclude: Vec,}

通过zip读取jar包中的entry, 过滤只处理.class文件,并从zip_file中读取crc32的元数据,这样可以避免读取原始数据生成md5,可以大大加快处理速度。中间编写的时候遇到了一个常见的rust borrow checker的问题。以下代码为例

fn main() {    let path = "/tmp/a.jar";    let jar = File::open(path).unwrap();    let mut zip = ZipArchive::new(jar).unwrap();    for name in zip.file_names() {        let entry = zip.by_name(name);        println!("name: {}, size: {}", name, entry.unwrap().size());    }}

我是想通过遍历ZipArchive#file_names然后根据文件名获取ZipFile但是会有如下编译错误

pub fn file_names(&self) -> impl Iterator {    self.shared.names_map.keys().map(|s| s.as_str())}
/// Search for a file entry by namepub fn by_name<"a>(&"a mut self, name: &str) -> ZipResult> {    Ok(self.by_name_with_optional_password(name, None)?.unwrap())}

但是用以下的方式就没有问题

let path = "/tmp/a.jar";let jar = File::open(path).unwrap();let mut zip = ZipArchive::new(jar).unwrap();for i in 0..zip.len() {    let entry = zip.by_index(i).unwrap();    println!("name: {}, size: {}", entry.name(), entry.size());}

这里我比较奇怪的是从方法签名上看 len()file_names()都会发生immutable borrow,而后面by_indexby_name都会发生mutable borrow。为什么会一个可以通过检查,一个不行。

pub fn len(&self) -> usize {    self.shared.files.len()}

len函数实际的签名应该是fn len<"a>(&"a self) -> usize返回值是usize,所以函数调用完成后就不再和借用有关了。所以 immutable borrow 就结束了。而file_names实际签名是fn file_names<"a>(&"a self) -> impl Iterator {…}返回值的生命周期和 入参的 immutable ref周期相同,所以后续就检测出同时存在可变和不可变引用了。详细解释: https://users.rust-lang.org/t/borrow-check-understanding/94260/2

命令行频繁被Killed问题

问题现象是当使用cargo build打包出binary后,通过cp 到 /tmp/jcd执行 会出现 Killed的情况,不是必现,但是当出现之后后续就一直会这样,百思不得其解。

$ /tmp/jcd[1]    16957 killed     /tmp/jcd

后通过在rust user 论坛提问找到答案,不得不说回复效率很高。https://users.rust-lang.org/t/rust-command-line-tools-keeps-beeing-killed/94179原因应该是和苹果电脑上的 Code sign机制有关,在苹果没有解决这个问题之前,建议通过ditto替代cp命令来copy程序。经过检查系统日志确实有出现 Code Signature Invalid的报错

相同的Class CRC和MD5却不一样

问题是发现在集成这个工具到内部的插件框架中,集成过程中发现一个Jar包被另一个module依赖,经过shade插件打包(没有对相关class进行relocate) 后,生成的class crc32不同,被识别为会冲突的类。通过javap -v查看两个class对比发现里面的仅仅是一些constant pool 不同。那么怀疑就是maven-shade-plugin 做了什么操作,翻阅了下代码,查看了shade的处理流程.看到以下这段,发现这不就是我遇到的问题么。查阅了相应的issue: https://issues.apache.org/jira/browse/MSHADE-391在3.3.0 才解决,而我使用的版本正好是3.2.4。升级插件重新生成校验码一致了。

解决冲突的Class

最后再回到最初的目的,当我们通过工具检测出冲突的class应该怎么解决呢。首先我们需要判断这个class是否是运行时所需要的。如果不是所需要的那么我们就应该直接排掉他,排除有两种手段(这里针对的是maven shade的打包方式),如果在dependency tree中可以看到相应package的依赖,那么可以直接通过如下的白名单 include 或者 exclude 掉某个 artifact。

      commons-dbcp:commons-dbcp    commons-pool:commons-pool    mysql:mysql-connector-java  

但是不排除这个依赖包本身就是fatjar,那么直接通过这种方式就排不掉这个依赖,可以通过filters 配置文件 粒度的匹配过滤

      *:*          META-INF/*.SF      META-INF/*.DSA      META-INF/*.RSA      javax/**      org/apache/flink/fnexecution/**      org/slf4j/**      

如果这个冲突的class是运行时需要的,那么可以通过relocation的方式给各自的插件包中shade成带特殊前缀的class名,解决同名冲突。

    org.apache.http    com.alipay.flink.sls.shaded.org.apache.http
关键词:

网站简介 网站团队 本网动态 友情链接 版权声明 我要投稿

Copyright© 2014-2020 中原网视台(www.hnmdtv.com) All rights reserved.