Uh oh!
There was an error while loading. Please reload this page.
Pass TPA through host-runtime contract instead of property string - #132861
Pass TPA through host-runtime contract instead of property string#132861elinor-fung wants to merge 20 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e234f49e-502f-4c6e-bacd-0971bb2033e7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e234f49e-502f-4c6e-bacd-0971bb2033e7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e234f49e-502f-4c6e-bacd-0971bb2033e7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e234f49e-502f-4c6e-bacd-0971bb2033e7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e234f49e-502f-4c6e-bacd-0971bb2033e7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e234f49e-502f-4c6e-bacd-0971bb2033e7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e234f49e-502f-4c6e-bacd-0971bb2033e7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 11aa1554-f420-4cb0-91cb-1bba7ec51725
|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
| HRESULT pathResult = S_OK; | ||
| IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ false, fileName, simpleName)); | ||
| if (pathResult == S_FALSE) | ||
| for (size_t i = 0; i < assemblyCount; i++) |
There was a problem hiding this comment.
Would it save even more memory allocations and copying if we stopped building the hashtable here and asked the host to resolve the assembly name to a filepath on demand every time?
There was a problem hiding this comment.
Good call. But maybe still keep the names only? The runtime does a case-insensitive hash for the names, which I don't know that we want to make any host's responsibility.
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
There are a couple of correctness/robustness issues in newly added/updated tests that should be addressed before merge.
Review tier: Lite
Findings: 1
Pre-existing issues (1)
| Severity | Finding |
|---|---|
src/mono/mono/mini/monovm.c — Mono AppContext.TryGetHostPropertyValue currently reconstructs TRUSTED_PLATFORM_ASSEMBLIES only via… View comment |
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs — HostApiInvokerApp's managed host_runtime_contract struct updates the size by adding… View resolved comment |
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/installer/tests/HostActivation.Tests/DependencyResolution/DependencyResolutionCommandResultExtensions.cs:70
- Splitting stderr using Environment.NewLine can miss lines if the captured output uses a different newline convention (e.g., "\n" only). Using a split that handles both "\r\n" and "\n" makes the assertion more robust across platforms and output sources.
src/tests/Loader/TrustedPlatformAssembliesProperty/TrustedPlatformAssemblies.cs:24 - The test uses StringComparer.OrdinalIgnoreCase unconditionally when checking that the CoreLib path is present in TRUSTED_PLATFORM_ASSEMBLIES. On case-sensitive file systems (typical on Linux), this can hide casing issues and make the assertion weaker than intended. Consider using a case-insensitive comparison only where appropriate (e.g., Windows).
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Mono’s new AppContext host-property ICALL currently uses a hard g_assert for a reachable condition, which can abort the process in debug builds instead of failing gracefully.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/mono/mono/mini/monovm.c — ves_icall_System_AppContext_TryGetHostPropertyValue uses `g_assert(trusted_platform_assemblies->as… |
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
src/mono/mono/mini/monovm.c — Mono AppContext.TryGetHostPropertyValue currently reconstructs TRUSTED_PLATFORM_ASSEMBLIES only via… View resolved comment |
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
steveisok
commented
Sep 2, 2026
@elinor-fung in case this helps at all. I could never land it - #100503 |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Mono’s new ves_icall_System_AppContext_TryGetHostPropertyValue uses property_value without declaring/initializing it, which is a build-breaking defect.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/mono/mono/mini/monovm.c — property_value is used but never declared/initialized in `ves_icall_System_AppContext_TryGetHostPr… |
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
src/mono/mono/mini/monovm.c — ves_icall_System_AppContext_TryGetHostPropertyValue uses `g_assert(trusted_platform_assemblies->as… View resolved comment |
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
There are at least two correctness issues in changed code paths (mockcoreclr contract pointer deref and corerun get_assembly_names returning false for empty results) that should be fixed before merge.
Review tier: Lite
Findings: None
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
src/mono/mono/mini/monovm.c — property_value is used but never declared/initialized in `ves_icall_System_AppContext_TryGetHostPr… View resolved comment |
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/coreclr/hosts/corerun/corerun.cpp:330
- The host-runtime contract allows
get_assembly_namesto succeed withcount == 0(both CoreCLR and Mono treat that as a valid result). Returningfalsewhen the list is empty makes the runtime fall back to the legacy TPA property string even though the callback mechanism is supported, and can become a hard failure if corerun is in CALLBACK mode (no property provided).
src/native/corehost/test/mockcoreclr/mockcoreclr.cpp:64 coreclr_initializein the mock runtime unconditionally dereferences the parsedhost_runtime_contract*(viacontract->size) without validating that the pointer string parsed to a non-null pointer. If a test passes an invalid/emptyHOST_RUNTIME_CONTRACTvalue, this will AV the mock instead of reporting 0 names (or otherwise continuing), making negative tests brittle.
| for (SString::Iterator i = sTrustedPlatformAssemblies.Begin(); i != sTrustedPlatformAssemblies.End(); ) | ||
| const char* const* assemblyNames; | ||
| size_t assemblyCount; | ||
| if (HostInformation::GetAssemblyNames(&assemblyNames, &assemblyCount)) |
There was a problem hiding this comment.
Possibly the same as Jan's comment below - I don't understand why we need to get the full list from the host - why not just build it on the fly. That way if the app doesn't need a certain assembly, it would never even allocate the TPA entry for it.
There was a problem hiding this comment.
I kept the names so that the case-insensitive hash would remain the runtime's responsibility. We could do something like replicate SString::HashCaseInsensitive in the host, but I didn't really want to push that responsibility onto the host (or custom host if they augment the TPA via the callbacks). It felt like since the runtime's binding behaviour is case-insensitive, it should stay the one to enforce that for the TPA too.
There was a problem hiding this comment.
I'm thinking about future improvements - the hostpolicy currently has to process all of the framework's JSON's in order to figure out the full list of assemblies - with this change this is really only needed here. If we remove this somehow, in theory the hostpolicy would not need to parse the JSON's or do version resolution until a specific assembly is asked for by the runtime.
You're right that it would mean that the host must resolve assemblies using case-insensitive lookup, but only once, the runtime could still maintain the hash table of all loaded assemblies, and any future loads would resolve from that table if already present.
It's not a big deal though - current hostpolicy would not be able to take advantage of this to optimize things, we would need to restructure it quite a bit.
There was a problem hiding this comment.
in theory the hostpolicy would not need to parse the JSON's or do version resolution until a specific assembly is asked for by the runtime
I had briefly wanted to do this (before actually starting on this PR), but didn't come up with a way that seemed particularly good to me. I basically had:
- parse JSONs on first assembly request (basically immediate?), keep some representation of the unresolved assets, and resolve to path on request
- re-parse and resolve every request
- augment deps.json in some way that we can index on asset name easily (only parse what is needed if possible?)
But honestly, I didn't actually try any of those. I think it'd warrant some experimentation, but the current change seemed like a clear 'do less and hold on to less' - so I just kind of went with 'should still work with anything we add now if we try more improvements later'.
You're right that it would mean that the host must resolve assemblies using case-insensitive lookup, but only once, the runtime could still maintain the hash table of all loaded assemblies, and any future loads would resolve from that table if already present.
Fair enough. I mostly looked at the case-insensitive hash implementation and thought it seemed like complex and possibly error-prone logic to push to hosts. (I also just found that docs call out that even though the runtime does case-insensitive binding, we recommend people treat assembly names as case-sensitive.)
There was a problem hiding this comment.
OK - I don't feel to strongly about this - as you say it's not easy to take advantage of it in the host as it currently works anyway (and if/when we rewrite hostpolicy to C# and move it into corelib, it can work differently anyway).
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| #include "hostinformation.h" | ||
| static struct host_runtime_contract host_contract; |
There was a problem hiding this comment.
@jkotas Are these changes to mono appropriate for .NET 12?
There was a problem hiding this comment.
We are keeping Mono working for a few more months until we complete the transition. We do not need to be improving it. So, choose the simplest approach that fits within these constraints. I’m not able to tell whether the current changes in this PR represent that simplest approach.
There was a problem hiding this comment.
The one simpler thing could be to not put this in a separate hostinformation file (stick it all in monovm.c). Other than that, I was just going for 'keep working' for Mono.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
There are merge-blocking correctness issues in CoreCLR binder allocation handling and in corerun’s get_assembly_names callback behavior that can cause crashes or unintended fallback paths.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/coreclr/hosts/corerun/corerun.cpp — get_assembly_names returns false when the host-resolved assembly list is empty. Per the… |
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/coreclr/binder/applicationcontext.cpp:111
wszSimpleNameis allocated with plainnew []and used unconditionally. In the binder we generally usenew (nothrow)+ an explicitE_OUTOFMEMORYpath (see SAFE_NEW in bindertypes.hpp); otherwise a failed allocation can lead to a null deref inwcscpy_s(and in builds without C++ exceptions, plainnewmay return null).
This issue also appears on line 134 of the same file.
src/coreclr/binder/applicationcontext.cpp:141
- Same allocation issue here for both
wszSimpleNameandwszFileName: plainnew []is used with no failure handling. This can turn an OOM into a crash (or an unhandled exception, depending on the build configuration).
LPWSTR wszSimpleName = new WCHAR[simpleName.GetCount() + 1];
wcscpy_s(wszSimpleName, simpleName.GetCount() + 1, simpleName.GetUnicode());
LPWSTR wszFileName = new WCHAR[fileName.GetCount() + 1];
wcscpy_s(wszFileName, fileName.GetCount() + 1, fileName.GetUnicode());
SimpleNameToFileNameMapEntry mapEntry{ wszSimpleName, wszFileName };
m_pTrustedPlatformAssemblyMap->AddOrReplace(mapEntry);
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
There are merge-blocking correctness issues in the updated binding/loader paths (notably missing allocation-failure checks in CoreCLR binder and a Mono preload-hook early-termination case) that should be fixed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 3
New issues introduced by this change (4)
| Severity | Finding |
|---|---|
src/coreclr/binder/applicationcontext.cpp — The TPA-name path uses new WCHAR[...] without any null/OOM check, but this function otherwise… | |
src/mono/mono/mini/monovm.c — When using host-resolved TPA entries, failing to resolve a single assembly path currently breaks… | |
src/native/corehost/hostpolicy/hostpolicy_context.cpp — name is derived by stripping only the last extension from the resolved path. This can produce a… | |
src/installer/tests/HostActivation.Tests/DependencyResolution/DependencyResolutionCommandResultExtensions.cs — Splitting stderr with Split(Environment.NewLine) is brittle if the captured output uses different… |
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
src/coreclr/hosts/corerun/corerun.cpp — get_assembly_names returns false when the host-resolved assembly list is empty. Per the… View resolved comment |
Suppressed comments (1)
src/coreclr/binder/applicationcontext.cpp:138
- Same issue in the legacy TPA-property parsing path:
wszSimpleName/wszFileNameallocations no longer check for failure beforewcscpy_s, despite this method using HRESULT-style control flow for OOM elsewhere.
LPWSTR wszSimpleName = new WCHAR[simpleName.GetCount() + 1];
wcscpy_s(wszSimpleName, simpleName.GetCount() + 1, simpleName.GetUnicode());
LPWSTR wszFileName = new WCHAR[fileName.GetCount() + 1];
wcscpy_s(wszFileName, fileName.GetCount() + 1, fileName.GetUnicode());
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.



Pass trusted platform assemblies from the host to the runtime through
host_runtime_contractcallbacks instead of as a large string with path separators.The host provides the resolved assembly names during initialization and the runtime requests each resolved path when needed. This reduces unnecessary parsing, reduces the amount we allocate as part of startup, and allows running applications with paths including a path separator.
The
TRUSTED_PLATFORM_ASSEMBLIESproperty can still be explicitly queried (reconstructed on demand) and can still be explicitly supplied by custom hosts.Resolves#3163
Resolves#75882
Resolves#104402
cc @dotnet/appmodel @AaronRobinsonMSFT
Windows x64, basic console app:
Startup was measured with the dotnet/performance
TimeToMain2ETW harness, from process start until a minimal console app emitted its first event. Native allocations were with the minimal app blocked onConsole.ReadLine().