Skip to content

[clr-ios] Fix SIGSEGV in open virtual delegate dispatch with interpreter#126199

Open
kotlarmilos wants to merge 13 commits intodotnet:mainfrom
kotlarmilos:bugfix/clr-interpreter-open-delegate
Open

[clr-ios] Fix SIGSEGV in open virtual delegate dispatch with interpreter#126199
kotlarmilos wants to merge 13 commits intodotnet:mainfrom
kotlarmilos:bugfix/clr-interpreter-open-delegate

Conversation

@kotlarmilos
Copy link
Copy Markdown
Member

@kotlarmilos kotlarmilos commented Mar 27, 2026

Description

Fix a crash when the interpreter invokes a compiled open virtual delegate target.

When the interpreter's call delegate handler detects an open virtual delegate whose resolved target is compiled (targetIp == NULL), the code previously fell through to a generic path that called GetTarget() on the delegate. For open delegates, GetTarget() returns null, which corrupted the argument slot and caused a SIGSEGV.

The fix adds a dedicated path for open virtual delegates with compiled targets. It uses memmove to shift the call arguments down by one slot (removing the delegate object) while preserving 16-byte alignment for V128 arguments, then jumps to CALL_INTERP_METHOD to invoke the resolved targetMethod directly. The same shifting is applied for interpreted targets in the non-tail-call path. The alignment-preserving shift logic is extracted into a ShiftDelegateCallArgs() helper to avoid duplication.

Additionally, this re-enables test suites previously excluded under #124325 (except System.Runtime.Serialization.Xml.Tests, which hits a native stack overflow unrelated to this fix).

Copilot AI review requested due to automatic review settings March 27, 2026 13:09
@kotlarmilos kotlarmilos self-assigned this Mar 27, 2026
@kotlarmilos kotlarmilos added this to the 11.0.0 milestone Mar 27, 2026
@kotlarmilos kotlarmilos added the os-ios Apple iOS label Mar 27, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad, @janvorli, @kg
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an interpreter crash on iOS when invoking compiled open virtual delegate targets, and updates AppleMobile/CoreCLR libraries test selection.

Changes:

  • Handle open virtual delegates whose resolved target has no interpreter bytecode (targetIp == NULL) by skipping the delegate argument slot and directly dispatching via CALL_INTERP_METHOD.
  • Remove several AppleMobile/CoreCLR test project exclusions from src/libraries/tests.proj.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/libraries/tests.proj Removes previously-added AppleMobile/CoreCLR test exclusions (re-enables several Serialization test projects).
src/coreclr/vm/interpexec.cpp Adds a NULL-targetIp open-virtual delegate path that avoids GetTarget() and dispatches the resolved MethodDesc directly.

@kotlarmilos
Copy link
Copy Markdown
Member Author

/azp run runtime-ioslike

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Wrap the GetTarget/NonVirtualEntry2MethodDesc block in braces so the
CALL_DELEGATE_INVOKE goto can legally jump past the OBJECTREF variable
initialization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 31, 2026 09:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings April 8, 2026 08:19
Extract the duplicated memmove logic for removing the delegate object
from call arguments into a static ShiftDelegateCallArgs() helper.
Re-add System.Runtime.Serialization.Xml.Tests exclusion (dotnet#124325).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos kotlarmilos force-pushed the bugfix/clr-interpreter-open-delegate branch from 2f8a225 to 6b0cbc0 Compare April 8, 2026 08:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/libraries/Common/tests/System/Runtime/Serialization/Utils.cs:380

  • AssemblyDependencyResolver is unsupported on several platforms (notably Android/iOS/tvOS/Browser). This helper now special-cases Browser + AppleMobile, but it can still attempt to construct/use AssemblyDependencyResolver on Android (and potentially WASI), which would throw PlatformNotSupportedException if this helper is used there.

Consider switching these guards to PlatformDetection.IsMobile (as used in other tests) or explicitly excluding all platforms where AssemblyDependencyResolver is unsupported.

        public TestAssemblyLoadContext(string name, bool isCollectible, string mainAssemblyToLoadPath = null) : base(name, isCollectible)
        {
            if (!PlatformDetection.IsBrowser && !PlatformDetection.IsAppleMobile)
                _resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath ?? Assembly.GetExecutingAssembly().Location);
        }

        protected override Assembly Load(AssemblyName name)
        {
            if (PlatformDetection.IsBrowser || PlatformDetection.IsAppleMobile)
            {
                return base.Load(name);
            }

            string assemblyPath = _resolver.ResolveAssemblyToPath(name);
            if (assemblyPath != null)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 8, 2026 08:36
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

- Fix ShiftDelegateCallArgs memmove length to use totalArgsSize - firstAlignedDstOffset
  instead of totalArgsSize - sizeOfArgsUpto16ByteAlignment to avoid reading past
  actual argument data when pre-alignment size is not 16-byte aligned.
- Extend INTOP_CALLDELEGATE_TAIL to carry sizeOfArgsUpto16ByteAlignment and
  targetArgsSize (oplength 4 -> 6) so the open virtual compiled target path
  works correctly for tail calls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
Copy link
Copy Markdown
Member Author

/azp run runtime-ioslike

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

targetArgsSize = ALIGN_UP_TO(targetArgsSize, argAlignment);
targetArgsSize += size;
}
if (!found16ByteAligned)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed now that you loop over all the arguments? It seems that in case no 16 byte aligned arg was found, the sizeOfArgsUpto16ByteAlignment would already be equal to the targetArgsSize.

Copy link
Copy Markdown
Member Author

@kotlarmilos kotlarmilos Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. When no 16-byte aligned arg is found, the loop already accumulates all sizes into sizeOfArgsUpto16ByteAlignment, so it equals targetArgsSize at the end.

OPDEF(INTOP_CALLDELEGATE, "call.delegate", 5, 1, 1, InterpOpMethodHandle)
OPDEF(INTOP_CALLDELEGATE_TAIL, "call.delegate.tail", 4, 1, 1, InterpOpMethodHandle)
OPDEF(INTOP_CALLDELEGATE, "call.delegate", 6, 1, 1, InterpOpMethodHandle)
OPDEF(INTOP_CALLDELEGATE_TAIL, "call.delegate.tail", 6, 1, 1, InterpOpMethodHandle)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we expanding the INTOP_CALLDELEGATE_TAIL to 6 too?

Copy link
Copy Markdown
Member Author

@kotlarmilos kotlarmilos Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted. Tail calls do not need the size metadata. The interpreted path uses pTargetMethod->argsSize, and the compiled open virtual path now skips the delegate slot with callArgsOffset += INTERP_STACK_SLOT_SIZE, matching the interpreted tail path.

Copy link
Copy Markdown
Member Author

@kotlarmilos kotlarmilos Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it is needed since the callArgsOffset += INTERP_STACK_SLOT_SIZE approach breaks 16-byte alignment for V128 arguments (as Vlad noted in #126199 (comment)). Both tail and non-tail paths now use ShiftDelegateCallArgs which needs the size metadata.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really meant to ask why the call.delegate.tail size grows by 2, I would expect it to grow by 1.

…check

- Revert INTOP_CALLDELEGATE_TAIL to oplength 4; tail calls use the simpler
  callArgsOffset += INTERP_STACK_SLOT_SIZE approach matching the interpreted
  tail path.
  already makes sizeOfArgsUpto16ByteAlignment == targetArgsSize when no V128
  arg exists.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 10, 2026 16:07
- Expand INTOP_CALLDELEGATE_TAIL to oplength 6 with size metadata
- Use ShiftDelegateCallArgs in interpreted tail path instead of
  callArgsOffset + INTERP_STACK_SLOT_SIZE which breaks 16-byte alignment
- Use ShiftDelegateCallArgs unconditionally in compiled open virtual path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/coreclr/vm/interpexec.cpp:3224

  • In the open-virtual + compiled-target path, the tailcall case adjusts callArgsOffset by one slot and then jumps to CALL_INTERP_METHOD. If CALL_INTERP_METHOD ends up invoking a compiled method (targetIp == NULL), the call stub expects the argument block to follow interpreter stack alignment rules from offset 0 (including 16-byte alignment for V128). Bumping the base pointer by 8 can violate those assumptions. Consider either applying the same alignment-preserving shift for this tailcall case (which likely means carrying the size metadata on INTOP_CALLDELEGATE_TAIL too), or falling back to the normal delegate invoke/shuffle-thunk path for tail calls when the resolved target is compiled.

                    if ((targetMethod = NonVirtualEntry2MethodDesc(targetAddress)) != NULL)
                    {
                        // In this case targetMethod holds a pointer to the MethodDesc that will be called by using targetMethodObj as
                        // the this pointer. This may be the final method (in the case of instance method delegates), or it may be a

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants