[
https://issues.apache.org/jira/browse/GROOVY-12364?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18112109#comment-18112109
]
ASF GitHub Bot commented on GROOVY-12364:
-----------------------------------------
testlens-app[bot] commented on PR #2888:
URL: https://github.com/apache/groovy/pull/2888#issuecomment-5562225087
## ✅ All tests passed ✅
🏷️ Commit: 7bf538004903ba5145c128388a4ea02a09bdaacc
▶️ Tests: 117636 executed
⚪️ Checks: 32/32 completed
---
_Learn more about TestLens at
[testlens.app/docs](https://testlens.app/docs/features/pr-comment/)._
> Native image: dynamic call to a @CallerSensitive method (e.g.
> Logger.getLogger) is fatal under AOT link mode
> ------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12364
> URL: https://issues.apache.org/jira/browse/GROOVY-12364
> Project: Groovy
> Issue Type: Bug
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
>
> In a GraalVM native image, a *dynamic* Groovy call to a JDK method annotated
> {{@CallerSensitive}} whose JDK implementation has a
> {{@CallerSensitiveAdapter}} variant (JDK 18+) kills the process with an
> uncatchable VM error. {{java.util.logging.Logger.getLogger(String)}} is the
> everyday case, so any dynamic Groovy class with a {{static final Logger LOG =
> Logger.getLogger(...)}} field cannot even initialise in a native image:
> {noformat}
> Fatal error: Cannot invoke method that has a @CallerSensitiveAdapter without
> an explicit caller
> {noformat}
> followed by a native crash dump. The Java frames in the dump are (innermost
> first):
> {noformat}
> com.oracle.svm.shared.util.VMError.shouldNotReachHere(VMError.java:91)
> com.oracle.svm.core.reflect.SubstrateMethodAccessor.methodHandleInvoke(SubstrateMethodAccessor.java:130)
> com.oracle.svm.core.methodhandles.Util_java_lang_invoke_MethodHandle.invokeInternal(Target_java_lang_invoke_MethodHandle.java:263)
> java.lang.invoke.MethodHandle.invokeBasic(MethodHandle.java:119)
> com.oracle.svm.core.methodhandles.MethodHandleIntrinsicImpl.execute(MethodHandleIntrinsicImpl.java:178)
> ... (LambdaForm.interpretWithArguments / invokeBasic repeated for each link
> of Groovy's guarded chain) ...
> java.lang.invoke.MethodHandleImpl.guardWithCatch(MethodHandleImpl.java:957)
> org.codehaus.groovy.vmplugin.v8.IndyInterface.aotDispatch(IndyInterface.java:604)
> Svc.<clinit>(NativeLogProbe.groovy:6) // static final Logger JUL =
> Logger.getLogger('svc')
> {noformat}
> h3. Reproducer
> Precompile and build with the agent-recorded metadata as in the Groovy 6
> native-image instructions (GraalVM CE 25.2.4, Groovy 6.0.0-SNAPSHOT master):
> {code:groovy}
> class CsNative {
> static void main(String[] args) {
> switch (args[0]) {
> case 'logger': println
> java.util.logging.Logger.getLogger('x').name; break // fatal
> case 'lookup': println
> java.lang.invoke.MethodHandles.lookup().lookupClass(); break // fatal
> case 'forname': println Class.forName('java.lang.String'); break
> // ok
> case 'syslog': println System.getLogger('y').name; break // ok
> case 'ok': println Integer.parseInt('42'); break // ok
> }
> }
> }
> {code}
> Results, same binary:
> ||call||native image||JVM with {{-Dgroovy.indy.aot.link=true}}||
> |{{Logger.getLogger('x')}}|Fatal error|ok|
> |{{MethodHandles.lookup()}}|Fatal error|ok|
> |{{Class.forName(...)}}, {{System.getLogger(...)}}|ok|ok|
> |{{Logger.getMethod('getLogger', String).invoke(null, 'r')}} from
> {{@CompileStatic}} code|ok|ok|
> |{{MethodHandles.lookup().unreflect(getLogger).invokeWithArguments('m')}}
> from {{@CompileStatic}} code|ok|ok|
> So neither the method nor reflection nor method handles as such are the
> problem; only Groovy's *runtime-built* handle for the target is.
> h3. Cause
> {{Selector.MethodSelector.unreflect}} obtains the target handle with
> {{callSite.getLookup().unreflect(method)}}. For a caller-sensitive method the
> JDK returns a handle bound to the lookup class (the Groovy caller;
> {{MethodHandles.Lookup#unreflect}} / {{MethodHandleImpl.bindCaller}}), which
> on HotSpot gives exactly the right caller semantics. The AOT link mode
> (GROOVY-12234) then invokes the guarded chain from {{aotDispatch}}. GraalVM
> cannot intrinsify a handle that is only constructed at run time, so it
> executes the chain in its MethodHandle interpreter, and when the interpreter
> reaches the caller-sensitive leaf,
> {{SubstrateMethodAccessor.methodHandleInvoke}} requires the adapter form with
> an explicit caller argument, which the bound handle does not supply, and it
> aborts with {{VMError.shouldNotReachHere}} rather than throwing. The last two
> rows of the table show the same handle works when GraalVM can see its
> construction at build time, and that GraalVM's reflective path handles
> caller-sensitive methods fine.
> {{Class.forName}} and {{System.getLogger}} survive because GraalVM
> substitutes them directly.
> h3. Proposed fix
> Groovy already knows which targets are caller-sensitive:
> {{CachedMethod.isCallerSensitive()}} (annotation probe plus the serialization
> classes, conservative when undecidable), used today to keep such methods off
> the reflective cold tier on HotSpot, where {{Method.invoke}} would misreport
> the caller. Under AOT link mode the trade-off is the opposite: the handle
> path is fatal and the reflective path works. So for an AOT-linked site whose
> selected method is caller-sensitive, dispatch through {{Method.invoke}} (the
> reflective wrapper already used as the AOT steady state) instead of building
> the handle chain; static targets such as {{Logger.getLogger}} need to be
> admitted to that path, which currently accepts instance methods only.
> Interface selections should probe the receiver's implementation as the cold
> tier does.
> Consequence to document: in a native image the observed caller of such a
> method becomes Groovy's runtime rather than the calling class. For
> {{Logger.getLogger}}, {{System.getLogger}} and resource bundles that only
> affects module/loader resolution, which is a single application module in an
> image anyway; for {{MethodHandles.lookup()}} it means the returned lookup is
> not the caller's, so that call should be static-compiled or avoided in
> dynamic native code. Not crashing is still strictly better than the current
> fatal error.
> Longer term this is worth reporting to GraalVM: a caller-bound handle
> produced by {{Lookup.unreflect}} should be invocable from the MethodHandle
> interpreter (the caller is known at bind time), and a VM-fatal error for a
> user-triggerable condition is disproportionate; an
> {{UnsupportedOperationException}} would at least let Groovy fall back at run
> time.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)