xiaojinzi123 / Component

A powerful componentized framework.一个强大、100% 兼容、支持 AndroidX、支持 Kotlin并且灵活的组件化框架. 大家尽量用 纯 Kotlin 的 KComponent 呀!!!

Home Page:https://github.com/xiaojinzi123/Component/wiki/Component-%E5%92%8C-ARouter-%E6%AF%94%E8%BE%83

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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);

image
你先把类似这个配置给我看下

还有就是我改不是问题. 那我得弄懂先, 改了会不会出其他问题

插件顺序大致如下
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 的最开始回复就是叫你看看顺序的问题, 你也没关注