Sfoglia il codice sorgente

Using PlatformClassLoader to resolve `java.*`; fix #2009

Karlatemp 3 anni fa
parent
commit
8250c3da65

+ 54 - 1
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<String> {
     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, "<init>", "()V", null, null)!!.also { cinit ->
+                cinit.visitVarInsn(Opcodes.ALOAD, 0)
+                cinit.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()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

+ 11 - 1
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")
+        }
     }
 }

+ 12 - 2
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"