You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ch...@apache.org on 2026/09/20 16:27:48 UTC
(kyuubi) branch branch-1.12 updated: [KYUUBI #7723][UTIL] Answer false from isClassLoadable when the class fails to link or initialize
This is an automated email from the ASF dual-hosted git repository.
pan3793 pushed a commit to branch branch-1.12
in repository https://gitbox.apache.org/repos/asf/kyuubi.git
The following commit(s) were added to refs/heads/branch-1.12 by this push:
new 5b3a10d231 [KYUUBI #7723][UTIL] Answer false from isClassLoadable when the class fails to link or initialize
5b3a10d231 is described below
commit 5b3a10d2311dd573114c3b2956f1ef4c8aa6cf82
Author: YangJie <ya...@baidu.com>
AuthorDate: Mon Sep 21 00:22:45 2026 +0800
[KYUUBI #7723][UTIL] Answer false from isClassLoadable when the class fails to link or initialize
### Why are the changes needed?
Closes #7723.
`ReflectUtils.isClassLoadable` wrapped its `DynClasses` probe in `scala.util.Try`, which catches only `NonFatal`. The probe resolves the name with `Class.forName(className, true, loader)`, so a class that is present but fails to link or initialize raises a `LinkageError` (`NoClassDefFoundError`, `ExceptionInInitializerError`) rather than a `ClassNotFoundException`. `LinkageError` is not `NonFatal`, so it escaped the probe and crashed the caller instead of answering `false`.
This replaces the `Try` with an explicit `try`/`catch` that answers `false` on `ClassNotFoundException` and `LinkageError`. The two call sites (`Logging` probing the SLF4J bridge, `JDBCMetadataStore` probing the MySQL driver) read the result as a boolean and are only made safer. `OutOfMemoryError`/`StackOverflowError` are `VirtualMachineError`, not `LinkageError`, so they stay fatal exactly as they did under `Try`.
### How was this patch tested?
Added a `ReflectUtilsSuite` case that probes a top-level object whose static initializer throws, by its binary name, and asserts `isClassLoadable` returns `false`. Reverting the production change makes the suite abort as the `ExceptionInInitializerError` escapes, which pins the regression.
```
build/mvn test -pl kyuubi-util-scala -am
build/mvn scalastyle:check spotless:check -pl kyuubi-util-scala -am
```
Module green on Zulu 17.0.18 (22/22), scalastyle 0 errors, spotless clean.
### Was this patch assisted by generative AI tooling?
Assisted-by: Claude Opus 4.8
Closes #7724 from LuciferYang/kyuubi-util-scala-classloadable-linkageerror.
Closes #7723
a6b223c5f [yangjie01] [KYUUBI #7723][UTIL] Answer false from isClassLoadable when the class fails to link or initialize
Authored-by: YangJie <ya...@baidu.com>
Signed-off-by: Cheng Pan <ch...@apache.org>
(cherry picked from commit c002a42b3b8cb649e892e38b58fe93e0a29df8e9)
Signed-off-by: Cheng Pan <ch...@apache.org>
---
.../org/apache/kyuubi/util/reflect/ReflectUtils.scala | 14 ++++++++++----
.../org/apache/kyuubi/util/reflect/ReflectUtilsSuite.scala | 13 +++++++++++++
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/kyuubi-util-scala/src/main/scala/org/apache/kyuubi/util/reflect/ReflectUtils.scala b/kyuubi-util-scala/src/main/scala/org/apache/kyuubi/util/reflect/ReflectUtils.scala
index f985e40670..950db8a1ed 100644
--- a/kyuubi-util-scala/src/main/scala/org/apache/kyuubi/util/reflect/ReflectUtils.scala
+++ b/kyuubi-util-scala/src/main/scala/org/apache/kyuubi/util/reflect/ReflectUtils.scala
@@ -22,12 +22,12 @@ import java.util.ServiceLoader
import scala.collection.JavaConverters._
import scala.language.existentials
import scala.reflect.ClassTag
-import scala.util.Try
object ReflectUtils {
/**
- * Determines whether the provided class is loadable
+ * Determines whether the provided class is loadable, answering false when it cannot
+ * be found, linked, or initialized.
* @param className the class name
* @param cl the class loader
* @return is the class name loadable with the class loader
@@ -35,9 +35,15 @@ object ReflectUtils {
def isClassLoadable(
className: String,
cl: ClassLoader = Thread.currentThread().getContextClassLoader): Boolean =
- Try {
+ try {
DynClasses.builder().loader(cl).impl(className).buildChecked()
- }.isSuccess
+ true
+ } catch {
+ // scala.util.Try does not catch LinkageError, so a class that fails to link or
+ // initialize would escape the probe instead of answering false
+ case _: ClassNotFoundException => false
+ case _: LinkageError => false
+ }
/**
* get the field value of the given object
diff --git a/kyuubi-util-scala/src/test/scala/org/apache/kyuubi/util/reflect/ReflectUtilsSuite.scala b/kyuubi-util-scala/src/test/scala/org/apache/kyuubi/util/reflect/ReflectUtilsSuite.scala
index 8f0090d5dd..0cd25f177c 100644
--- a/kyuubi-util-scala/src/test/scala/org/apache/kyuubi/util/reflect/ReflectUtilsSuite.scala
+++ b/kyuubi-util-scala/src/test/scala/org/apache/kyuubi/util/reflect/ReflectUtilsSuite.scala
@@ -32,6 +32,15 @@ class ReflectUtilsSuite extends AnyFunSuite {
assert(!isClassLoadable("org.apache.kyuubi.NonExistClass"))
}
+ test("check class that fails to initialize is not loadable") {
+ // Class.forName reports a failing static initializer as ExceptionInInitializerError,
+ // a LinkageError that Try does not catch; a loadability probe must answer false
+ // instead of letting the error escape
+ // probe by binary name: touching the object reference would run the failing
+ // initialization outside the code under test and abort the suite
+ assert(!isClassLoadable("org.apache.kyuubi.util.reflect.FailingInit$"))
+ }
+
test("check invokeAs on base class") {
assertResult("method1")(invokeAs[String](obj1, "method1"))
assertResult("method2")(invokeAs[String](obj1, "method2"))
@@ -103,3 +112,7 @@ object ObjectA {
def method5(): String = "method5"
private def method6(): String = "method6"
}
+
+object FailingInit {
+ throw new IllegalStateException("static init failed")
+}