transformClassesWithComponentPlugin遇到带有签名的JAR时候会报错
Murmurl912 opened this issue · comments
这个问题处在ModifyASMUtilTransform第94行中创建JarFile的方式:
JarFile jarFile = new JarFile(jarInput.getFile());
这种方式默认启用Jar的签名检验,在尝试读取其中的某个class文件时会执行校验逻辑。
如果这个jar被修改,这里会抛出异常,应该使用:
JarFile jarFile = new JarFile(jarInput.getFile(), false);
来创建JarFile,禁用签名校验
你是不是把插件那个位置放的比其他插件下面了
用的新的Variant Api的transformClassesWith接口,这个是在所有的transform前执行的
代码给我看下吧
插件注册代码:
private fun config(project: Project) {
HostnameLog.info("config plugin for project: ${project.name}")
val androidComponents =
project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.onVariants { variant ->
if (unselect(variant)) {
return@onVariants
}
variant.transformClassesWith(
HostnameScanInstrument::class.java,
InstrumentationScope.PROJECT
) { params ->
params.ipv4AddressRegion.set(extension.ipadressRegionMap)
params.hostnameRegion.set(extension.hostnameRegionMap)
params.hostnameFilter.set(extension.hostnameFilterRules)
params.hostnameReportDir.set(outputDir)
params.enable.set(extension.scanHostname.get())
params.ignoredClassPrefix.set(extension.ignoreClassPrefix.get())
}
variant.transformClassesWith(
HostnameInstrument::class.java,
InstrumentationScope.PROJECT
) { params ->
params.removeRule.set(extension.removeHostnameRule)
params.enable.set(extension.removeHostname.get())
params.ignoredClassPrefix.set(extension.ignoreClassPrefix.get())
}
}
}
实现代码
abstract class HostnameInstrument: AsmClassVisitorFactory {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
val targets = config()[classContext.currentClassData.className]
return if (targets != null) {
HostnameLog.info("instrument on class ${classContext.currentClassData.className}")
return HostnameClassVisitor(
Opcodes.ASM7,
nextClassVisitor,
targets,
)
} else {
HostnameLog.info("ignore instrument on class ${classContext.currentClassData.className}" +
", reason: instrument target is not configured")
nextClassVisitor
}
}
override fun isInstrumentable(classData: ClassData): Boolean {
if (!parameters.get().enable.get()) {
return false
}
if (isSignedClass(classData.className)) {
HostnameLog.info("ignore class: ${classData.className}")
return false
}
return config().containsKey(classData.className)
}
private fun isSignedClass(className: String): Boolean {
for (prefix in parameters.get().ignoredClassPrefix.get()) {
if (className.startsWith(prefix)) {
return true
}
}
return false
}
private fun factory() = HostnameRemoveFactory.instance(parameters.get())
private fun config() = factory().config()
}
项目里面还有其它插件使用了transform,都是在transformClassesWithComponentPlugin之后执行的。在我没有使用上面的新插件之前是可以编译通过的,加上了我的插件就不行。上面的插件只是做一些扫描硬编码域名的操作,会通过ClassVisitor来读取class文件。即使不在ClassVisitor中修改类,经过ClassReader和ClassWriter后,得到的class数据还是会发生变化。受AsmClassVisitorFactory 接口的限制,不能直接返回原始的class数据
意思就是你自己写了一个插件是吧. 那你 gradle 中应用插件的顺序的代码 也给我看下
我还是建议把compoent插件中transform读取jar的方式改一下JarFile jarFile = new JarFile(jarInput.getFile(), false);
插件顺序大致如下
apply plugin: 'com.google.firebase.firebase-perf' // firebase性能监测,有个AsmClassesTransform会对一些类进行插桩
apply plugin: 'com.xiaojinzi.component.plugin
apply plugin: 其它插件
Transform的执行顺序
AsmClassesTransform - firebase插件
transformClassesWithComponentPluginForOverseaRelease - component插件
然后会出现:
Caused by: java.lang.SecurityException: SHA1 digest error for org/bouncycastle/LICENSE.class
at com.xiaojinzi.component.plugin.util.IOUtil.readAndWrite(IOUtil.java:22)
at com.xiaojinzi.component.plugin.ModifyASMUtilTransform.transform(ModifyASMUtilTransform.java:146)
at com.android.build.gradle.internal.pipeline.TransformTask$2.call(TransformTask.java:284)
at com.android.build.gradle.internal.profile.NoOpAnalyticsService.recordBlock(NoOpAnalyticsService.kt:72)
at com.android.build.gradle.internal.pipeline.TransformTask.transform(TransformTask.java:242)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
因为你构造JarFile默认把校验签名打开了,通过JarFile.getInputStream会检查签名的。下面是JarFile.getInputStream实现
/**
* Returns an input stream for reading the contents of the specified
* zip file entry.
* @param ze the zip file entry
* @return an input stream for reading the contents of the specified
* zip file entry
* @throws ZipException if a zip file format error has occurred
* @throws IOException if an I/O error has occurred
* @throws SecurityException if any of the jar file entries
* are incorrectly signed.
* @throws IllegalStateException
* may be thrown if the jar file has been closed
*/
public synchronized InputStream getInputStream(ZipEntry ze)
throws IOException
{
maybeInstantiateVerifier();
if (jv == null) {
return super.getInputStream(ze);
}
if (!jvInitialized) {
initializeVerifier();
jvInitialized = true;
// could be set to null after a call to
// initializeVerifier if we have nothing to
// verify
if (jv == null)
return super.getInputStream(ze);
}
// wrap a verifier stream around the real stream
return new JarVerifier.VerifierStream(
getManifestFromReference(),
ze instanceof JarFileEntry ?
(JarEntry) ze : getJarEntry(ze.getName()),
super.getInputStream(ze),
jv);
}
apply plugin: 'com.google.firebase.firebase-perf' // firebase性能监测,有个AsmClassesTransform会对一些类进行插桩
apply plugin: 'com.xiaojinzi.component.plugin
这两句调换顺序, 把我的放前面. 你看下还出现吗?
apply plugin: 'com.xiaojinzi.component.plugin
这句就是放在 com.android.application 这个插件后面就行了
这样是没有问题的,我本地构建时候firebase-perf插件是没有启用的,可以编译通过。但是我还是建议把读取JarFile的地方改一下
那就是 firebase-pref 插件的启用导致的呗.
我那个插件本就生成一个 ASMUtil 这个类而已. 没有其他作用, 本来就放最前面就可以了.
那你把 firebase-perf 插件放我的后面不就完了
是可以这样改,但要是后面的人遇到这个问题,那不得怀疑好几天人生是不是自己的插件写错了
不瞒你说
因为插件顺序导致的问题已经不是一个两个了.
我怎么全部解决, 你这个我还能改代码, 很多其他的连问题都找不到. 我咋解决
我也只能在文档中强调这个事情了, 其他我做不了啥
而且你看看这个 issue 的最开始回复就是叫你看看顺序的问题, 你也没关注