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