Skip to content

Fix vararg P/Invoke cross-assembly metadata lookup using wrong module#126106

Open
Copilot wants to merge 3 commits intomainfrom
copilot/fix-vararg-pinvoke-issues
Open

Fix vararg P/Invoke cross-assembly metadata lookup using wrong module#126106
Copilot wants to merge 3 commits intomainfrom
copilot/fix-vararg-pinvoke-issues

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 25, 2026

When a vararg P/Invoke defined in assembly A is called from assembly B, stub creation looks up method implementation flags (e.g. PreserveSig) using the wrong module's metadata importer — the caller's module rather than the defining module.

Root Cause

CreatePInvokeStubAccessMetadata used pSigDesc->m_pModule to obtain the IMDInternalImport, but for cross-assembly vararg calls m_pModule is the caller's module (holds the call-site vararg signature), while m_tkMethodDef is a token from the defining assembly. The distinction is explicitly documented in dllimport.h:

m_pMetadataModule — Module that owns any metadata that influences interop behavior. This can differ with vararg P/Invokes, where the calling assembly's module is assigned to m_pModule.

Results in either a BadImageFormatException (token out of range in caller's module) or silent stack corruption (PreserveSig flag incorrectly read from a different method in the caller's module).

Fix

src/coreclr/vm/dllimport.cpp — one-line change in CreatePInvokeStubAccessMetadata:

// Before
IMDInternalImport* pInternalImport = pSigDesc->m_pModule->GetMDImport();
// After
IMDInternalImport* pInternalImport = pSigDesc->m_pMetadataModule->GetMDImport();

Test

src/tests/Interop/PInvoke/Varargs/CrossAssembly/ — new cross-assembly test following the MultipleAssembliesWithSamePInvoke pattern:

  • VarArgsPInvokeLib — separate library assembly that declares the vararg [DllImport]
  • CrossAssemblyVarargsTest — calls through the lib from the main test assembly, validating correct output

The VarArgsPInvokeLib support project omits any CMakeProjectReference — the Varargs native assets are already referenced as Windows-only from the parent Interop.csproj (via WindowsOnlyCMakeProjectReference), so no additional reference is needed in the library project.

Customer Impact

Vararg P/Invoke calls made from a different assembly than the one declaring the import either throw BadImageFormatException or silently corrupt the stack depending on token layout. Workaround is to co-locate caller and P/Invoke declaration in the same assembly.

Regression

No — code inspection suggests this has been present since .NET Framework.

Testing

New CrossAssemblyVarargsTest exercises the exact failing scenario (vararg P/Invoke defined in a library assembly, called from a separate test assembly). Guarded by IsVarArgSupported consistent with existing varargs tests.

Risk

Low. Single-line fix confined to the cross-assembly vararg path; the m_pMetadataModule field was already being used correctly for the IsRuntimeMarshallingEnabled check in the same area. Non-vararg P/Invokes are unaffected (m_pModule == m_pMetadataModule in that case).

Original prompt

This section details on the original issue you should resolve

<issue_title>Calling a Vararg P/Invoke from another assembly fails to call underlying native function</issue_title>
<issue_description>### Description

When calling a vararg P/Invoke defined in another assembly, the runtime tries to look up the metadata from the wrong module. This can cause a variety of issues, from throwing a BadImageFormatException to emitting an incorrect stub signature.

Reproduction Steps

Define the following P/Invoke in assembly A:

[DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern int _scprintf(string format, __arglist);

Call it from another assembly (Assembly B):

_scprintf("%d\n", __arglist(42));

Expected behavior

"42" is printed out to the console.

Actual behavior

BadImageFormatException if the method token for _scprintf is higher than the highest valid method token in assembly B.

Stack corruption if the method token for _scprintf in Assembly A corresponds to a method in Assembly B without the preservesig metadata bit set.

Regression?

Based on code inspection, this issue has likely been present since .NET Framework.

Known Workarounds

Move the call to the P/Invoke to the same assembly as the caller (or vice versa).

Configuration

.NET 7.0.3 Windows x86

Other information

The failure is due to this line using m_pModule instead of m_pMetadataModule:

IMDInternalImport* pInternalImport = pSigDesc->m_pModule->GetMDImport();
</issue_description>

Comments on the Issue (you are @copilot in this section)


⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.

…Module

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/4b003785-29cc-445b-9d52-64fa68ba9af2
Copilot AI changed the title [WIP] Fix vararg P/Invoke call issues across assemblies Fix vararg P/Invoke cross-assembly metadata lookup using wrong module Mar 25, 2026
Copilot AI requested a review from jkoritzinsky March 25, 2026 17:15
@jkoritzinsky jkoritzinsky marked this pull request as ready for review March 25, 2026 17:17
Copilot AI review requested due to automatic review settings March 25, 2026 17:17
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 CoreCLR vararg P/Invoke stub generation for cross-assembly calls by ensuring interop behavior flags (e.g., PreserveSig) are read from the defining module’s metadata, and adds a regression test that calls a vararg [DllImport] declared in a referenced library assembly.

Changes:

  • CoreCLR: Use m_pMetadataModule (defining module) instead of m_pModule (caller module) when reading MethodImplProps during P/Invoke stub creation.
  • Tests: Add a new cross-assembly varargs scenario via a support library (VarArgsPInvokeLib) plus a calling test (CrossAssemblyVarargsTest).
  • Build/test wiring: Reference the new support project from Interop.csproj and exclude its .cs from merged wrapper compilation.

Reviewed changes

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

Show a summary per file
File Description
src/coreclr/vm/dllimport.cpp Fix metadata importer selection for cross-assembly vararg P/Invoke stub flag lookup.
src/tests/Interop/PInvoke/Varargs/CrossAssembly/CrossAssemblyVarargsTest.cs New regression test that calls vararg P/Invoke across assemblies and validates output.
src/tests/Interop/PInvoke/Varargs/CrossAssembly/VarArgsPInvokeLib/VarArgsPInvokeLib.cs New library that defines the vararg [DllImport] used by the regression test.
src/tests/Interop/PInvoke/Varargs/CrossAssembly/VarArgsPInvokeLib/VarArgsPInvokeLib.csproj New support library project; currently references native CMake project.
src/tests/Interop/Interop.csproj Adds support project reference and excludes library source from merged compilation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Calling a Vararg P/Invoke from another assembly fails to call underlying native function

3 participants