From 8250c3da6500d754d41b6283055fe7a24352db7b Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Wed, 4 May 2022 16:31:03 +0800 Subject: [PATCH] Using PlatformClassLoader to resolve `java.*`; fix #2009 --- .../backend/integration-test/src/utils.kt | 55 ++++++++++++++++++- .../src/P.kt | 12 +++- .../internal/plugin/JvmPluginClassLoader.kt | 14 ++++- 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/mirai-console/backend/integration-test/src/utils.kt b/mirai-console/backend/integration-test/src/utils.kt index b204dafc2..18dd33055 100644 --- a/mirai-console/backend/integration-test/src/utils.kt +++ b/mirai-console/backend/integration-test/src/utils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 Mamoe Technologies and contributors. * * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. @@ -10,7 +10,12 @@ package net.mamoe.console.integrationtest import org.junit.jupiter.api.fail +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode import java.io.File +import java.util.UUID internal fun readStringListFromEnv(key: String): MutableList { val size = System.getenv(key)!!.toInt() @@ -35,3 +40,51 @@ public fun File.assertNotExists() { } } // endregion + +// region JVM Utils +public val vmClassfileVersion: Int = runCatching { + val obj = ClassReader("java.lang.Object") + val classobj = ClassNode().also { obj.accept(it, ClassReader.SKIP_CODE) } + classobj.version +}.recoverCatching { + val ccl = object : ClassLoader(null) { + fun canLoad(ver: Int) : Boolean{ + val klass = ClassWriter(ClassWriter.COMPUTE_MAXS) + val cname = + "net/mamoe/console/integrationtest/vtest/C${ver}_${System.currentTimeMillis()}_${UUID.randomUUID()}" + .replace('-', '_') + + klass.visit( + ver, + Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL, + cname, + null, "java/lang/Object", null + ) + klass.visitMethod(Opcodes.ACC_PRIVATE, "", "()V", null, null)!!.also { cinit -> + cinit.visitVarInsn(Opcodes.ALOAD, 0) + cinit.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false) + cinit.visitInsn(Opcodes.RETURN) + cinit.visitMaxs(0, 0) + } + val code = klass.toByteArray() + return kotlin.runCatching { + val k = defineClass(null, code, 0, code.size) + Class.forName(k.name, true, this) + }.isSuccess + } + } + if (ccl.canLoad(Opcodes.V17)) return@recoverCatching Opcodes.V17 + if (ccl.canLoad(Opcodes.V16)) return@recoverCatching Opcodes.V16 + if (ccl.canLoad(Opcodes.V15)) return@recoverCatching Opcodes.V15 + if (ccl.canLoad(Opcodes.V14)) return@recoverCatching Opcodes.V14 + if (ccl.canLoad(Opcodes.V13)) return@recoverCatching Opcodes.V13 + if (ccl.canLoad(Opcodes.V12)) return@recoverCatching Opcodes.V12 + if (ccl.canLoad(Opcodes.V11)) return@recoverCatching Opcodes.V11 + if (ccl.canLoad(Opcodes.V10)) return@recoverCatching Opcodes.V10 + if (ccl.canLoad(Opcodes.V9)) return@recoverCatching Opcodes.V9 + Opcodes.V1_8 +}.getOrElse { Opcodes.V1_8 } // Fallback + +public fun canVmLoad(opversion: Int): Boolean = opversion <= vmClassfileVersion + +// endregion diff --git a/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt b/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt index bde390850..16c272e60 100644 --- a/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt +++ b/mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt @@ -9,9 +9,12 @@ package net.mamoe.console.integrationtest.ep.pddd +import net.mamoe.console.integrationtest.canVmLoad import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin +import net.mamoe.mirai.utils.info +import org.objectweb.asm.Opcodes import kotlin.test.assertEquals /* @@ -35,9 +38,16 @@ internal object P : KotlinPlugin( // console-non-hard-link dependency // mirai-core used 1.64 current + val bc = Class.forName("org.bouncycastle.LICENSE") assertEquals( "1.63.0", - Class.forName("org.bouncycastle.LICENSE").`package`.implementationVersion + bc.`package`.implementationVersion, + message = "$bc <- ${bc.classLoader}" ) + // #2009 + if (canVmLoad(Opcodes.V11)) { + logger.info { "V11" } + Class.forName("java.net.http.HttpClient") + } } } diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt index f37e1e2a7..b28c1d2a8 100644 --- a/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt +++ b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt @@ -120,7 +120,7 @@ internal class DynLibClassLoader( } override fun loadClass(name: String, resolve: Boolean): Class<*> { - if (name.startsWith("java.")) return Class.forName(name, false, null) + if (name.startsWith("java.")) return Class.forName(name, false, JavaSystemPlatformClassLoader) val pt = this.parent val topPt: ClassLoader? = if (pt is DynLibClassLoader) { pt.findButNoSystem(name)?.let { return it } @@ -303,7 +303,7 @@ internal class JvmPluginClassLoaderN : URLClassLoader { override fun loadClass(name: String, resolve: Boolean): Class<*> = loadClass(name) override fun loadClass(name: String): Class<*> { - if (name.startsWith("java.")) return Class.forName(name, false, null) + if (name.startsWith("java.")) return Class.forName(name, false, JavaSystemPlatformClassLoader) if (name.startsWith("io.netty") || name in AllDependenciesClassesHolder.allclasses) { return AllDependenciesClassesHolder.appClassLoader.loadClass(name) } @@ -411,6 +411,16 @@ internal class JvmPluginClassLoaderN : URLClassLoader { } +private val JavaSystemPlatformClassLoader: ClassLoader by lazy { + kotlin.runCatching { + ClassLoader::class.java.methods.asSequence().filter { + it.name == "getPlatformClassLoader" + }.filter { + java.lang.reflect.Modifier.isStatic(it.modifiers) + }.firstOrNull()?.invoke(null) as ClassLoader? + }.getOrNull() ?: ClassLoader.getSystemClassLoader().parent +} + private fun String.pkgName(): String = substringBeforeLast('.', "") internal fun Artifact.depId(): String = "$groupId:$artifactId"