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

 更新时间:2023年05月24日 08:24:52   作者:Aitozi  
这篇文章主要介绍了用rust 写一个jar包 class冲突检测工具 的相关资料,需要的朋友可以参考下

Rust很适合写命令行工具,特别是使用clap crate 更加方便,这篇文章介绍使用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<String>,
}

通过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<Item = &str> {
self.shared.names_map.keys().map(|s| s.as_str())
}
/// Search for a file entry by name
pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
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_index 和 by_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<Item = &'a str> {…}返回值的生命周期和 入参的 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。

<artifactSet combine.self="override">
<includes>
<include>commons-dbcp:commons-dbcp</include>
<include>commons-pool:commons-pool</include>
<include>mysql:mysql-connector-java</include>
</includes>
</artifactSet>

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

<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>javax/**</exclude>
<exclude>org/apache/flink/fnexecution/**</exclude>
<exclde>org/slf4j/**</exclde>
</excludes>
</filter>
</filters>

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

<relocation>
<pattern>org.apache.http</pattern>
<shadedPattern>com.alipay.flink.sls.shaded.org.apache.http</shadedPattern>
</relocation>

到此这篇关于用rust 写一个jar包 class冲突检测工具 的文章就介绍到这了,更多相关rust 写jar包 class冲突检测工具 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Rust  利用 chrono 库实现日期和字符串互相转换的示例

    Rust  利用 chrono 库实现日期和字符串互相转换的示例

    在Rust中,chrono库提供了强大的日期和时间处理功能,使得日期与字符串之间的转换变得简单,本文介绍了如何在Rust中使用chrono库将日期转换成字符串,以及如何将字符串解析为日期,对于需要进行日期时间格式化、解析或进行时区处理的开发者来说,chrono库是一个不可或缺的工具
    2024-11-11
  • Rust 数据类型详解

    Rust 数据类型详解

    本文介绍了Rust编程语言中的标量类型和复合类型,标量类型包括整数、浮点数、布尔和字符,而复合类型则包括元组和数组,标量类型用于表示单个值,具有不同的表示和范围,本文介绍的非常详细,感兴趣的朋友一起看看吧
    2025-01-01
  • Rust 搭建一个小程序运行环境的方法详解

    Rust 搭建一个小程序运行环境的方法详解

    rust是一门比较新的编程语言,2015年5月15日,Rust编程语言核心团队正式宣布发布Rust 1.0版本,本文给大家介绍Rust 搭建一个小程序运行环境,以iOS 为例介绍开发环境的准备,感兴趣的朋友跟随小编一起看看吧
    2022-05-05
  • Windows系统下安装Rust环境超详细教程

    Windows系统下安装Rust环境超详细教程

    这篇文章主要介绍了如何在Windows系统上安装mingw64和Rust,mingw64是一个轻便的C语言编译环境,可以替代Rust默认使用的Visual Studio,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2025-02-02
  • 详解Rust中三种循环(loop,while,for)的使用

    详解Rust中三种循环(loop,while,for)的使用

    我们常常需要重复执行同一段代码,针对这种场景,Rust 提供了多种循环(loop)工具。一个循环会执行循环体中的代码直到结尾,并紧接着回到开头继续执行。而 Rust 提供了 3 种循环:loop、while 和 for,下面逐一讲解
    2022-09-09
  • Rust 配置文件内容及使用全面讲解

    Rust 配置文件内容及使用全面讲解

    这篇文章主要为大家介绍了Rust 配置文件内容及使用全面讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Rust 中 Deref Coercion讲解

    Rust 中 Deref Coercion讲解

    Rust 的设计理念一向是显式比隐式好,也就是说所有的行为尽量在代码中表现出来,这篇文章主要介绍了Rust 中 Deref Coercion 介绍,需要的朋友可以参考下
    2022-10-10
  • 一步到位,教你如何在Windows成功安装Rust

    一步到位,教你如何在Windows成功安装Rust

    一步到位:轻松学会在Windows上安装Rust!想快速掌握Rust编程语言?别再为复杂教程头疼!这份指南将手把手带你顺利完成Windows平台上的Rust安装全过程,从此编码之旅更加顺畅无阻,立即阅读,开始你的Rust编程旅程吧!
    2024-01-01
  • 深入探究在Rust中函数、方法和关联函数有什么区别

    深入探究在Rust中函数、方法和关联函数有什么区别

    在 Rust 中,函数、方法和关联函数都是用来封装行为的,它们之间的区别主要在于它们的定义和调用方式,本文将通过一个简单的rust代码示例来给大家讲讲Rust中函数、方法和关联函数区别,需要的朋友可以参考下
    2023-08-08
  • 详解rust 自动化测试、迭代器与闭包、智能指针、无畏并发

    详解rust 自动化测试、迭代器与闭包、智能指针、无畏并发

    这篇文章主要介绍了rust 自动化测试、迭代器与闭包、智能指针、无畏并发,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-11-11

最新评论