This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new b7ba750296 refactor: replace sun.reflect.Reflection with StackWalker
in Reflect (#3151)
b7ba750296 is described below
commit b7ba75029662a77933ce82f3a122e4040763a37a
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Tue Jun 23 19:32:59 2026 +0800
refactor: replace sun.reflect.Reflection with StackWalker in Reflect (#3151)
* refactor: replace sun.reflect.Reflection with StackWalker in Reflect
Motivation:
Reflect.getCallerClass uses sun.reflect.Reflection.getCallerClass via
reflection, a JDK 8 internal API that was moved to
jdk.internal.reflect.Reflection in JDK 9+. On JDK 17+ without
--add-opens for jdk.internal.reflect, this always falls back to None.
Modification:
Replace with java.lang.StackWalker (JDK 9) with RETAIN_CLASS_REFERENCE
option, which is the official supported API for accessing caller
class information. The API contract is preserved: given a frame depth
index, returns the Class at that depth (or null if beyond stack).
Result:
Official JDK API instead of internal reflection hack. StackWalker
was already established in the codebase (LoggerClass.scala,
TestKitUtils.scala). The findClassLoader fallback now works reliably
on JDK 17+.
Tests:
sbt "actor/compile" — passed
References:
Refs #3136
* test: add directional tests for Reflect.getCallerClass and findClassLoader
Motivation:
The main PR replaced `sun.reflect.Reflection.getCallerClass` (a JDK 8
internal API that returns `None` on JDK 17 because
`Class.forName("sun.reflect.Reflection")` no longer resolves) with the
official `java.lang.StackWalker` API. The previous spec had no
coverage of `getCallerClass` itself, so a silent regression — for
example reverting the implementation back to the `sun.reflect` path —
would not be caught.
Modification:
Add four tests to `ReflectSpec`:
- `Reflect.getCallerClass` is defined on JDK 17+ (the old
implementation always returned `None`; this is the directional test
that fails on the pre-PR code).
- `getCaller(0)` returns a non-null `Class` whose name starts with
`org.apache.pekko.util.Reflect`, validating the frame-skip
semantics.
- `getCaller(Int.MaxValue)` returns `null` for a depth beyond the
stack, matching the `findCaller` contract that filters `ne null`.
- `Reflect.findClassLoader()` returns a non-null `ClassLoader` for
the end-to-end path.
Result:
StackWalker migration is now protected by a directional test that
would fail on the pre-PR implementation.
Tests:
sbt "actor-tests/Test/testOnly org.apache.pekko.util.ReflectSpec" --
10/10 passed (4 new + 6 pre-existing findConstructor tests).
References:
Discovered during internal review of PR #3151.
* refactor: use imported StackWalker instead of fully-qualified
java.lang.StackWalker
Motivation:
Code review feedback to simplify the code by importing StackWalker
instead of using fully-qualified java.lang.StackWalker references.
Modification:
Add import for java.lang.StackWalker and replace all fully-qualified
references with the short name.
Result:
Cleaner, more readable code with standard Scala import conventions.
References:
Refs #3151
---
.../scala/org/apache/pekko/util/ReflectSpec.scala | 28 ++++++++++++++++++++++
.../main/scala/org/apache/pekko/util/Reflect.scala | 11 +++++----
2 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/actor-tests/src/test/scala/org/apache/pekko/util/ReflectSpec.scala
b/actor-tests/src/test/scala/org/apache/pekko/util/ReflectSpec.scala
index d3b51763c3..d786578cef 100644
--- a/actor-tests/src/test/scala/org/apache/pekko/util/ReflectSpec.scala
+++ b/actor-tests/src/test/scala/org/apache/pekko/util/ReflectSpec.scala
@@ -64,4 +64,32 @@ class ReflectSpec extends AnyWordSpec with Matchers {
}
}
+ "Reflect#getCallerClass" must {
+
+ "be defined on JDK 17+ (StackWalker is always available)" in {
+ Reflect.getCallerClass shouldBe defined
+ }
+
+ "return a non-null class for a valid stack depth" in {
+ val getCaller = Reflect.getCallerClass.get
+ // Frame 0 is the lambda inside Reflect$ calling StackWalker.walk
+ val frame0 = getCaller(0)
+ frame0 should not be null
+ frame0.getName should startWith("org.apache.pekko.util.Reflect")
+ }
+
+ "return null for index beyond stack depth" in {
+ val getCaller = Reflect.getCallerClass.get
+ getCaller(Int.MaxValue) shouldBe null
+ }
+ }
+
+ "Reflect#findClassLoader" must {
+
+ "return a non-null ClassLoader" in {
+ val cl = Reflect.findClassLoader()
+ cl should not be null
+ }
+ }
+
}
diff --git a/actor/src/main/scala/org/apache/pekko/util/Reflect.scala
b/actor/src/main/scala/org/apache/pekko/util/Reflect.scala
index 14d7850590..45203e718b 100644
--- a/actor/src/main/scala/org/apache/pekko/util/Reflect.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/Reflect.scala
@@ -12,6 +12,7 @@
*/
package org.apache.pekko.util
+import java.lang.StackWalker
import java.lang.reflect.Constructor
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
@@ -36,15 +37,17 @@ private[pekko] object Reflect {
* This optionally holds a function which looks N levels above itself
* on the call stack and returns the `Class[_]` object for the code
* executing in that stack frame. Implemented using
- * `sun.reflect.Reflection.getCallerClass` if available, None otherwise.
+ * `java.lang.StackWalker` (JDK 9+).
*
* Hint: when comparing to Thread.currentThread().getStackTrace, add two
levels.
*/
val getCallerClass: Option[Int => Class[?]] = {
try {
- val c = Class.forName("sun.reflect.Reflection")
- val m = c.getMethod("getCallerClass", Array(classOf[Int]): _*)
- Some((i: Int) => m.invoke(null,
Array[AnyRef](i.asInstanceOf[java.lang.Integer]): _*).asInstanceOf[Class[?]])
+ val walker = StackWalker.getInstance(
+ java.util.Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE))
+ Some((i: Int) =>
+ walker.walk((s: java.util.stream.Stream[StackWalker.StackFrame]) =>
+
s.skip(i.toLong).map[Class[?]](_.getDeclaringClass).findFirst().orElse(null)))
} catch {
case NonFatal(_) => None
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]