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/10/26 10:20:30 UTC

(kyuubi) branch master updated: [KYUUBI #7731][UTIL] Stop re-labeling invocation failures as missing members in ReflectUtils

This is an automated email from the ASF dual-hosted git repository.

pan3793 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new d7ca98566a [KYUUBI #7731][UTIL] Stop re-labeling invocation failures as missing members in ReflectUtils
d7ca98566a is described below

commit d7ca98566a6eac3de2b53da898f401b2c8a4a84f
Author: YangJie <ya...@baidu.com>
AuthorDate: Mon Oct 26 18:20:20 2026 +0800

    [KYUUBI #7731][UTIL] Stop re-labeling invocation failures as missing members in ReflectUtils
    
    ### Why are the changes needed?
    
    Closes #7731.
    
    `ReflectUtils.getField` and `invokeAs` wrapped the whole body in one `try` and rewrote any `Exception` as `RuntimeException(s"$clz does not have ...", e)`. Because `bind`/`get`/`invoke` ran inside that same `try`, a failure thrown by the found field or the invoked method itself was relabeled as a missing member, and the real exception survived only as the cause.
    
    This moves the `try`/`catch` to wrap only the lookup (`DynFields`/`DynMethods` `build`/`buildChecked`). The "does not have" message now applies only when the lookup fails; a failure while binding, reading, or invoking the found member keeps its own type and message.
    
    ### How was this patch tested?
    
    Added two `ReflectUtilsSuite` cases: an invoked method that throws `IllegalArgumentException("bad input")` must propagate that exception rather than a "does not have" message, and binding a `Class` target to an instance field must surface the field wrapper's "Cannot bind" `IllegalArgumentException`. Both fail if the catch is widened back over the invocation.
    
    ```
    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 (23/23), scalastyle 0 errors, spotless clean.
    
    ### Was this patch assisted by generative AI tooling?
    
    Assisted-by: Claude Opus 4.8
    
    Closes #7732 from LuciferYang/kyuubi-util-scala-reflectutils-errormessage.
    
    Closes #7731
    
    35281db5e [yangjie01] [KYUUBI #7731][UTIL] Stop re-labeling invocation failures as missing members in ReflectUtils
    
    Authored-by: YangJie <ya...@baidu.com>
    Signed-off-by: Cheng Pan <ch...@apache.org>
---
 .../apache/kyuubi/util/reflect/ReflectUtils.scala  | 58 ++++++++++++----------
 .../kyuubi/util/reflect/ReflectUtilsSuite.scala    | 17 +++++++
 2 files changed, 49 insertions(+), 26 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 950db8a1ed..6b634c07d8 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
@@ -54,19 +54,22 @@ object ReflectUtils {
    */
   def getField[T](target: AnyRef, fieldName: String): T = {
     val (clz, obj) = getTargetClass(target)
-    try {
-      val field = DynFields.builder
-        .hiddenImpl(clz, fieldName)
-        .impl(clz, fieldName)
-        .build[T]
-      if (field.isStatic) {
-        field.asStatic.get
-      } else {
-        field.bind(obj).get
+    // only the lookup is re-labeled as a missing field; a failure thrown while
+    // binding or reading the found field keeps its own message
+    val field =
+      try {
+        DynFields.builder
+          .hiddenImpl(clz, fieldName)
+          .impl(clz, fieldName)
+          .build[T]
+      } catch {
+        case e: Exception =>
+          throw new RuntimeException(s"$clz does not have $fieldName field", e)
       }
-    } catch {
-      case e: Exception =>
-        throw new RuntimeException(s"$clz does not have $fieldName field", e)
+    if (field.isStatic) {
+      field.asStatic.get
+    } else {
+      field.bind(obj).get
     }
   }
 
@@ -82,21 +85,24 @@ object ReflectUtils {
   def invokeAs[T](target: AnyRef, methodName: String, args: (Class[_], AnyRef)*): T = {
     val (clz, obj) = getTargetClass(target)
     val argClasses = args.map(_._1)
-    try {
-      val method = DynMethods.builder(methodName)
-        .hiddenImpl(clz, argClasses: _*)
-        .impl(clz, argClasses: _*)
-        .buildChecked
-      if (method.isStatic) {
-        method.asStatic.invoke[T](args.map(_._2): _*)
-      } else {
-        method.bind(obj).invoke[T](args.map(_._2): _*)
+    // only the lookup is re-labeled as a missing method; a failure thrown by the
+    // invoked method itself keeps its own type and message
+    val method =
+      try {
+        DynMethods.builder(methodName)
+          .hiddenImpl(clz, argClasses: _*)
+          .impl(clz, argClasses: _*)
+          .buildChecked
+      } catch {
+        case e: Exception =>
+          throw new RuntimeException(
+            s"$clz does not have $methodName${argClasses.map(_.getName).mkString("(", ", ", ")")}",
+            e)
       }
-    } catch {
-      case e: Exception =>
-        throw new RuntimeException(
-          s"$clz does not have $methodName${argClasses.map(_.getName).mkString("(", ", ", ")")}",
-          e)
+    if (method.isStatic) {
+      method.asStatic.invoke[T](args.map(_._2): _*)
+    } else {
+      method.bind(obj).invoke[T](args.map(_._2): _*)
     }
   }
 
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 0cd25f177c..d5afb987f3 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
@@ -87,6 +87,21 @@ class ReflectUtilsSuite extends AnyFunSuite {
     }("class org.apache.kyuubi.util.reflect.ObjectA$ does not have methodNotExists(" +
       "java.lang.String, java.lang.String)")
   }
+
+  test("invokeAs propagates the failure thrown by the invoked method") {
+    // the lookup succeeded and the method ran; re-labeling its failure as
+    // "does not have" points readers at a lookup problem that does not exist
+    interceptContains[IllegalArgumentException](invokeAs[String](obj2, "methodThrows"))(
+      "bad input")
+  }
+
+  test("getField reports the bind failure for an instance field on a class target") {
+    // binding a class target to an instance field fails with the truthful
+    // IllegalArgumentException from the field wrapper, not a missing-field message
+    interceptContains[IllegalArgumentException](getField[String](
+      classOf[ClassA],
+      "field0"))("Cannot bind")
+  }
 }
 
 class ClassA(val field0: String = "field0") {
@@ -103,6 +118,8 @@ class ClassB extends ClassA {
 
   def method3(): String = "method3"
   private def method4(): String = "method4"
+
+  def methodThrows(): String = throw new IllegalArgumentException("bad input")
 }
 
 object ObjectA {