Fix CLR_E_SHIM_RUNTIMELOAD in RAR's IMetaDataDispenser activation - #13899
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a .NET Framework RAR regression when activating CLSID_CorMetaDataDispenser in hosts that embed MSBuild without going through the mscoree.dll shim startup path (triggering CLR_E_SHIM_RUNTIMELOAD). It does so by bypassing the shim and activating the metadata dispenser via clr.dll’s internal class-object export, then adds targeted net472-only regression tests around AssemblyInformation lifetime/activation.
Changes:
- Update
AssemblyInformationandManifestUtil.MetadataReader(net472 path) to activateIMetaDataDispenserviaComClassFactory.TryCreateFromModule("clr.dll", ..., "DllGetClassObjectInternal", ...)instead of rawCoCreateInstance. - Extend
ComClassFactorywith module-export-based activation helpers and document the mscoree-shim failure mechanism. - Add new net472-only
AssemblyInformation_Testscovering mapping lifetime and activation sanity.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Tasks/ManifestUtil/MetadataReader.cs | Switch net472 COM activation to module-export path and preserve legacy HR-tolerance behavior. |
| src/Tasks/AssemblyDependency/AssemblyInformation.cs | Same activation change; move COM pointer cleanup to unmanaged dispose path to cover finalizer scenarios. |
| src/Tasks.UnitTests/AssemblyDependency/AssemblyInformation_Tests.cs | New net472-only regression tests for mapping lifetime and activation sanity. |
| src/Framework/Windows/Win32/System/Com/ComClassFactory.cs | Add TryCreateFromModule overloads and CLR-specific export constant/docs. |
| src/Framework/NativeMethods.txt | Add GetModuleHandle to the Win32 import list. |
There was a problem hiding this comment.
Review of PR #13899 — Fix CLR_E_SHIM_RUNTIMELOAD regression in embedded-host scenarios
The fix correctly identifies the root cause: raw CoCreateInstance on CLSID_CorMetaDataDispenser delegates to mscoree.dll, whose LoadLibraryShim fails (0x80131700) in native-embedded hosts where the shim's startup state isn't initialized. Calling DllGetClassObjectInternal directly on the already-loaded clr.dll bypasses the shim entirely and reproduces what the CLR's own managed-COM activation path does internally. The approach is sound.
The HRESULT-tolerance changes for GetAssemblyFromScope and GetPEKind correctly restore the legacy [PreserveSig] contract that was inadvertently tightened by #13853.
No BLOCKING or MAJOR issues found.
| # | Dimension | Verdict |
|---|---|---|
| 22 | Correctness | 🟡 1 MODERATE — DisposeUnmanagedResources + managed Dispose is safe but comment is incomplete |
| 24 | Security | 🟡 1 MODERATE — LoadLibrary DLL search order; GetModuleHandle was added to NativeMethods.txt but unused |
| 3 | Performance | 🟡 1 MODERATE — LoadLibrary+GetProcAddress+DllGetClassObject per construction; no caching |
| 14 | Naming/NIT | 🔵 3 NITs — GetModuleHandle dead entry, machine variable, public const on internal class |
✅ 20/24 dimensions clean.
- Correctness —
DisposeUnmanagedResourcescomment should explain why callingAgileComPointer.Dispose()from the finalizer path is safe (CLR field-reachability guarantee +Interlocked.Exchangeguard in AgileComPointer) - Security/Correctness —
GetModuleHandlewas added toNativeMethods.txtbutLoadLibrary(with DLL search order) is used instead;GetModuleHandleis the right call for a module that must already be loaded - Performance —
LoadLibrary+GetProcAddress+DllGetClassObjectInternalon everyAssemblyInformationconstruction; cache at least theFARPROC
Note
🔒 Integrity filter blocked 2 items
The following items were blocked because they don't meet the GitHub integrity level.
- #13899
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - #13899
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
To allow these resources, lower min-integrity in your GitHub frontmatter:
tools:
github:
min-integrity: approved # merged | approved | unapproved | noneGenerated by Expert Code Review (on open) for issue #13899 · ● 3.7M
Commit 5682672 (dotnet#13853) migrated AssemblyInformation / MetadataReader from RCW-based COM to struct-based COM through CsWin32 and AgileComPointer. The new code activates CLSID_CorMetaDataDispenser via raw CoCreateInstance. That loads mscoree.dll (the .NET Framework shim), which in turn calls LoadLibraryShim to bind a runtime before delegating to MetaDataDllGetClassObject. In hosts that did not enter the CLR via mscoree (notably the VC cppxplatdev test harness, which embeds MSBuild in-process via BuildManager) the shim's bound-runtime state is uninitialized and LoadLibraryShim returns CLR_E_SHIM_RUNTIMELOAD (0x80131700) "Failed to load the runtime". RAR catches that as a DependencyResolutionException and silently drops every transitive dependency for the failing reference, manifesting as VC's P2PReferences.08 regression (Referenced_C_IJW.dll missing from Referencing_A_IJW\Debug). The fix activates the dispenser by calling clr.dll's exported DllGetClassObjectInternal directly via a new ComClassFactory.TryCreateFromModule overload, bypassing the shim entirely. This is what the CLR's own managed-COM activation does internally for CLSIDs in IsClrHostedLegacyComObject. The approach is also AOT-friendly (no Type.GetTypeFromCLSID, no Activator, no RCW), so .NET 10+ source-generated COM can use it unchanged. * ComClassFactory: add two public TryCreateFromModule overloads (standard DllGetClassObject and caller-specified export name) plus ClrDllGetClassObjectInternalExportName constant. Single Stdcall function- pointer path serves both TFMs. * AssemblyInformation, MetadataReader: switch dispenser activation to TryCreateFromModule("clr.dll", ..., ClrDllGetClassObjectInternalExportName). Tolerate GetAssemblyFromScope / GetPEKind failures (preserve [PreserveSig] semantics from the previous RCW path). * NativeMethods.txt: add GetModuleHandle (kept for completeness; LoadLibrary, FreeLibrary, GetProcAddress were already present). * AssemblyInformation_Tests: new net472-only tests covering both file-mapping lifetime (delete/overwrite after Dispose) and dispenser activation.
88fbf5f to
5b3733c
Compare
|
Bypassing macOS since the queues are long and this is a Windows-only fix. |
Updated [Microsoft.Build.Utilities.Core](https://github.com/dotnet/msbuild) from 18.8.2 to 18.9.6. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Build.Utilities.Core's releases](https://github.com/dotnet/msbuild/releases)._ ## 18.9.6 ## What's Changed * [vs18.6] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13793 * [vs18.0] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13859 * [vs18.6] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13858 * [vs18.7] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13863 * CsWin32 follow-up: CLR metadata + TypeLib interop migration by @JeremyKuhne in dotnet/msbuild#13853 * Update vmr-sb-validation.yml for Azure Pipelines by @meghnave in dotnet/msbuild#13871 * Test: keep shell alive 15s in ToolTaskCanChangeCanonicalErrorFormat (#13734) by @jankratochvilcz in dotnet/msbuild#13878 * Add vs18.8 to merge-flow config by @OvesN in dotnet/msbuild#13877 * Stable branding for 18.8 release by @OvesN in dotnet/msbuild#13883 * Bump main to 18.9.0 after vs18.8 snap by @OvesN in dotnet/msbuild#13880 * Avoid checkout in insertion pipeline by @rainersigwald in dotnet/msbuild#13887 * Report actual launch path in MSB4216 for Runtime="NET" task host by @ViktorHofer in dotnet/msbuild#13889 * Migrate Tlblmp and AxImp to Multithreaded Execution by @AlesProkop in dotnet/msbuild#13708 * Replace ErrorUtilities assertion methods with Assumed API and BCL throw helpers by @DustinCampbell in dotnet/msbuild#13790 * Fix CLR_E_SHIM_RUNTIMELOAD in RAR's IMetaDataDispenser activation by @JeremyKuhne in dotnet/msbuild#13899 * [main] Update dependencies from nuget/nuget.client by @dotnet-maestro[bot] in dotnet/msbuild#13905 * [main] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13907 * [main] Update dependencies from dotnet/roslyn by @dotnet-maestro[bot] in dotnet/msbuild#13910 * [vs17.14] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13908 * [vs18.0] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13906 * Fix ToolTask output loss: increase EOF pipe timeout from 2s to 30s by @huulinhnguyen-dev in dotnet/msbuild#13767 * Improve symlink cycle condition by @GangWang01 in dotnet/msbuild#13901 * [vs18.6] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13904 * Add flaky-test detection and auto-fix agentic workflows by @ViktorHofer in dotnet/msbuild#13915 * Quote --ignore-exit-code values so the quarantine pipeline does not shell-split on Unix by @ViktorHofer in dotnet/msbuild#13918 * Fix flaky-test detector PR-evidence loss and raise scan limits by @ViktorHofer in dotnet/msbuild#13919 * Tighten the pr review agent by @JanKrivanek in dotnet/msbuild#13921 * Add environment variables for governance detection by @ViktorHofer in dotnet/msbuild#13920 * Change IsPackable to true and add IsShipping flag by @ViktorHofer in dotnet/msbuild#13924 * [vs18.7] Update dependencies from dotnet/arcade by @dotnet-maestro[bot] in dotnet/msbuild#13911 * Make flaky detector verify recurrence postdates the fix before commenting by @ViktorHofer in dotnet/msbuild#13930 * Fix AbsolutePath.GetCanonicalForm process state leak on Windows by @OvesN in dotnet/msbuild#13788 * Fix flaky detector: unblock dnceng feed, fail fast, and defer quarantine to a second run by @ViktorHofer in dotnet/msbuild#13936 * Update documentation for ImplicitUsings element by @drewnoakes in dotnet/msbuild#13900 * [vs18.6] Point OptProf bootstrapper at rel/stable instead of int.main by @AlesProkop in dotnet/msbuild#13923 * Add CS8618 suppressor for required MSBuild task properties by @AArnott in dotnet/msbuild#13926 * Tighten NodeLaunchData.EnvironmentOverrides nullability to IDictionary<string, string?>? by @OvesN with @Copilot in dotnet/msbuild#13815 * Fix ToolTask EOF wait to be STA-safe via CountdownEvent (MSB4018 in AspNetCompiler) by @YuliiaKovalova in dotnet/msbuild#13917 * [automated] Merge branch 'vs18.6' => 'vs18.7' by @github-actions[bot] in dotnet/msbuild#13941 * Localized file check-in by OneLocBuild Task: Build definition ID 9434: Build ID 14192258 by @dotnet-bot in dotnet/msbuild#13849 * Bumping to 10.0.8 runtime packages by @OvesN in dotnet/msbuild#13898 * Flaky-test workflow: reassure on empty PR list + drop local reproduction (quarantine-first) by @ViktorHofer in dotnet/msbuild#13938 * [Flaky Test] Un-quarantine 5 consistently-green tests by @github-actions[bot] in dotnet/msbuild#13952 * Flaky-test detector: open PRs ready-for-review; drop newly-filed-issues section from PR body by @ViktorHofer in dotnet/msbuild#13958 * [Flaky Test] Quarantine 4 flaky tests by @github-actions[bot] in dotnet/msbuild#13937 * Flaky-test: fix duplicate-issue bug by switching dedup key to a visible code-block key by @ViktorHofer in dotnet/msbuild#13963 * CsWin32 follow-up: WindowsNative + VS Setup Configuration + remaining hand-rolled interop by @JeremyKuhne in dotnet/msbuild#13872 * Add the reviewer release skill checking if the Learn article Change waves is updated by @GangWang01 in dotnet/msbuild#13840 * [main] Source code updates from dotnet/dotnet by @dotnet-maestro[bot] in dotnet/msbuild#13977 ... (truncated) Commits viewable in [compare view](dotnet/msbuild@v18.8.2...v18.9.6). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context
Fixes a regression in RAR introduced by #13853 (CsWin32 CLR metadata + TypeLib interop migration), reported by the VC team as
VC.Tests.MsBuild.VC.MsBuild.P2PReferences.08(CLR+CLR+CLR P2P chain):Referenced_C_IJW.dllis missing fromReferencing_A_IJW\Debug\after build.Root cause
The migration switched
AssemblyInformation/MetadataReaderfrom RCW-based COM to struct-based COM through CsWin32 andAgileComPointer, and along the way activatesCLSID_CorMetaDataDispenservia rawCoCreateInstance.CLSID_CorMetaDataDispenseris registered withmscoree.dll(the .NET Framework shim) as itsInprocServer32.CoCreateInstancetherefore loads the shim, which then has to callLoadLibraryShimto bind a runtime before it can delegate toMetaDataDllGetClassObject.In hosts that did not enter the CLR via mscoree's startup path — notably the VC
cppxplatdevtest harness, which embeds MSBuild in-process viaBuildManagerfrom a non-mscoree-rooted process — the shim's bound-runtime state is uninitialized andLoadLibraryShimfails withCLR_E_SHIM_RUNTIMELOAD(0x80131700, "Failed to load the runtime"). RAR catches that as aDependencyResolutionExceptionand silently drops every transitive dependency for the failing reference. End-to-end symptom is the missingReferenced_C_IJW.dllcopy.Standalone
msbuild.execannot reproduce because the msbuild.exe process enters the CLR via the standard mscoree startup path, so the shim is bound.Fix
Activate the dispenser by calling
clr.dll's exportedDllGetClassObjectInternaldirectly via a newComClassFactory.TryCreateFromModuleoverload, bypassing the shim entirely. This is exactly what the CLR's own managed-COM activation does internally for CLSIDs on itsIsClrHostedLegacyComObjectlist. The approach is AOT-friendly (noType.GetTypeFromCLSID, noActivator, no RCW), so .NET 10+ source-generated COM can use it unchanged.ComClassFactoryTryCreateFromModuleoverloads (standardDllGetClassObjectand caller-specified export name).ClrDllGetClassObjectInternalExportNameconstant with documentation of the shim mechanism.delegate* unmanaged[Stdcall]<...>function-pointer path serves both net472 and .NET Core.Callers
AssemblyInformationandMetadataReadernow useTryCreateFromModule("clr.dll", CLSID, ClrDllGetClassObjectInternalExportName, ...).[PreserveSig]tolerance forGetAssemblyFromScopeandGetPEKindfailures (the legacy RCW code silently ignored those HRs).DisposeUnmanagedResourcesso the finalizer path still revokes GIT cookies if the user forgets to dispose.Other
NativeMethods.txt: addGetModuleHandle(kept for completeness —LoadLibrary,FreeLibrary,GetProcAddresswere already present).AssemblyInformation_Testscovering both file-mapping lifetime (delete/overwrite after Dispose, including a repeated open-close stress loop) and dispenser activation.Validation
MetadataReader_Testspass on net472 x86.Microsoft.Build.Framework.dll+Microsoft.Build.Tasks.Core.dlldeployed into a local VS18 install, ran the failingProject.vcxprojdirectly withmsbuild.exeagainst the actual VC P2PReferences asset — bothReferenced_B_IJW.dllandReferenced_C_IJW.dllnow land inReferencing_A_IJW\Debug\.Cc / fyi @rainersigwald @JaynieBai @YuliiaKovalova — needs the VC team to re-run
P2PReferences.08with these binaries to confirm in their CI.