Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ namespace System
{
public static partial class AppContext
{
// Keep in sync with IsKnownHostProperty in src/coreclr/dlls/mscoree/exports.cpp.
Comment thread
elinor-fung marked this conversation as resolved.
private static bool IsKnownHostProperty(string name)
=> name is "TRUSTED_PLATFORM_ASSEMBLIES"
or "NATIVE_DLL_SEARCH_DIRECTORIES"
or "PLATFORM_RESOURCE_ROOTS"
or "APP_PATHS";

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AppContext_TryGetHostPropertyValue", StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool TryGetHostPropertyValue(string name, StringHandleOnStack retValue);

[UnmanagedCallersOnly]
private static unsafe void OnProcessExit(Exception* pException)
{
Expand Down
51 changes: 49 additions & 2 deletions src/coreclr/dlls/mscoree/exports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ class ConstWStringArrayHolder : public NewArrayHolder<LPCWSTR>
}
};

// Returns true for known host properties that do not need to be stored as CLR configuration.
// The runtime parses these into binder/AppDomain structures during AppDomain creation, so it
// does not need to also keep the original raw strings around in the CLR config knobs.
static bool IsKnownHostProperty(LPCSTR key)
{
return strcmp(key, HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES) == 0
|| strcmp(key, HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES) == 0
|| strcmp(key, HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS) == 0
|| strcmp(key, HOST_PROPERTY_APP_PATHS) == 0;
}

// Convert 8 bit string to unicode
static LPCWSTR StringToUnicode(LPCSTR str)
{
Expand Down Expand Up @@ -294,8 +305,29 @@ int coreclr_initialize(
Bundle::AppBundle = &bundle;
}

// This will take ownership of propertyKeysWTemp and propertyValuesWTemp
Configuration::InitializeConfigurationKnobs(propertyCount, propertyKeysW, propertyValuesW);
// Build a filtered set of properties for the CLR config knobs that excludes probing path
// properties (TPA, NATIVE_DLL_SEARCH_DIRECTORIES, PLATFORM_RESOURCE_ROOTS, APP_PATHS).
// Those are parsed into binder/AppDomain structures during CreateAppDomainWithManager,
// so the runtime does not need a duplicate copy held forever in the CLR config knobs.
// The TPA list in particular can be tens of KB.
LPCWSTR* configKeysW = new (nothrow) LPCWSTR[propertyCount];
ASSERTE_ALL_BUILDS(configKeysW != nullptr);
LPCWSTR* configValuesW = new (nothrow) LPCWSTR[propertyCount];
ASSERTE_ALL_BUILDS(configValuesW != nullptr);

int configPropertyCount = 0;
for (int i = 0; i < propertyCount; ++i)
{
if (IsKnownHostProperty(propertyKeys[i]))
continue;

configKeysW[configPropertyCount] = propertyKeysW[i];
configValuesW[configPropertyCount] = propertyValuesW[i];
++configPropertyCount;
}

// Configuration takes ownership of the filtered arrays and the strings they reference.
Configuration::InitializeConfigurationKnobs(configPropertyCount, configKeysW, configValuesW);

#ifdef TARGET_UNIX
if (Configuration::GetKnobBooleanValue(W("System.Runtime.CrashReportBeforeSignalChaining"), CLRConfig::INTERNAL_CrashReportBeforeSignalChaining))
Expand Down Expand Up @@ -324,6 +356,21 @@ int coreclr_initialize(
propertyValuesW,
(DWORD *)domainId);

// The binder/AppDomain has now parsed the host probing path properties into its own

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.

Perhaps a RAII style class that handles the allocations done in ConvertConfigPropertiesToUnicode()?

// structures and the managed AppContext.Setup has copied each property value into a
// managed string. Free the wide-string copies for the excluded properties (the rest are
// owned by the CLR config above) and the full property arrays themselves.
Comment thread
elinor-fung marked this conversation as resolved.
for (int i = 0; i < propertyCount; ++i)
{
Comment thread
elinor-fung marked this conversation as resolved.
if (IsKnownHostProperty(propertyKeys[i]))
{
delete[] (WCHAR*)propertyKeysW[i];
delete[] (WCHAR*)propertyValuesW[i];
}
}
delete[] propertyKeysW;
delete[] propertyValuesW;
Comment on lines +370 to +371

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.

Can we make these holders now and avoid the delete[]?


if (SUCCEEDED(hr))
{
host.SuppressRelease();
Expand Down
115 changes: 115 additions & 0 deletions src/coreclr/vm/appdomainnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#include "appdomain.inl"
#include "eventtrace.h"
#include "../binder/inc/defaultassemblybinder.h"
#include "../binder/inc/applicationcontext.hpp"
#include <corehost/host_runtime_contract.h>
#include "stringarraylist.h"

// static
extern "C" void QCALLTYPE AppDomain_CreateDynamicAssembly(QCall::ObjectHandleOnStack assemblyLoadContext, NativeAssemblyNameParts* pAssemblyNameParts, INT32 hashAlgorithm, INT32 access, QCall::ObjectHandleOnStack retAssembly)
Expand Down Expand Up @@ -112,6 +115,118 @@ extern "C" void QCALLTYPE AssemblyNative_GetLoadedAssemblies(QCall::ObjectHandle
END_QCALL;
}

namespace
{
// Append all paths from a StringArrayList into 'output', separated by PATH_SEPARATOR_CHAR_W.
void AppendStringArrayList(StringArrayList* pList, SString& output)
{
if (pList == NULL)
return;
Comment on lines +123 to +124

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.

Needs contract. Make assert. Functions like this shouldn't normally need these defensive checks given all callers of it already check the invariant.


for (DWORD i = 0; i < pList->GetCount(); ++i)
{
if (i != 0)
output.Append(PATH_SEPARATOR_CHAR_W);

output.Append(pList->Get(i));
}
}
}

// Get the value of a known host property from the binder/AppDomain state.
extern "C" BOOL QCALLTYPE AppContext_TryGetHostPropertyValue(LPCWSTR name, QCall::StringHandleOnStack retValue)
{
QCALL_CONTRACT;

BOOL found = FALSE;

BEGIN_QCALL;

AppDomain* pDomain = AppDomain::GetCurrentDomain();
DefaultAssemblyBinder* pBinder = pDomain->GetDefaultBinder();
BINDER_SPACE::ApplicationContext* pAppContext = pBinder->GetAppContext();

if (u16_strcmp(name, _T(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES)) == 0)
{
if (pAppContext->IsTpaListProvided())
{
BINDER_SPACE::SimpleNameToFileNameMap* pMap = pAppContext->GetTpaList();
_ASSERTE(pMap != NULL);

StackSString result;

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.

Suggested change
StackSString result;
SString result;

This will never be sufficient for the TPA. I would just start with SString.

BINDER_SPACE::SimpleNameToFileNameMap::Iterator i = pMap->Begin();
BINDER_SPACE::SimpleNameToFileNameMap::Iterator end = pMap->End();
while (i != end)
{
if (i->m_wszILFileName != NULL)
{
if (!result.IsEmpty())
result.Append(PATH_SEPARATOR_CHAR_W);

result.Append(i->m_wszILFileName);
}

++i;
}

if (!result.IsEmpty())
{
retValue.Set(result);
found = TRUE;
}
}
}
else if (u16_strcmp(name, _T(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES)) == 0)
{
StackSString result;

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.

Suggested change
StackSString result;
SString result;

AppDomain::PathIterator iter = pDomain->IterateNativeDllSearchDirectories();
while (iter.Next())
{
if (!result.IsEmpty())
result.Append(PATH_SEPARATOR_CHAR_W);

result.Append(*iter.GetPath());
}

if (!result.IsEmpty())
{
retValue.Set(result);
found = TRUE;
}
}
else if (u16_strcmp(name, _T(HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS)) == 0)
{
StringArrayList* pList = pAppContext->GetPlatformResourceRoots();
if (pList != NULL && pList->GetCount() > 0)
{
StackSString result;

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.

Suggested change
StackSString result;
SString result;

AppendStringArrayList(pList, result);
retValue.Set(result);
found = TRUE;
}
}
else if (u16_strcmp(name, _T(HOST_PROPERTY_APP_PATHS)) == 0)
{
StringArrayList* pList = pAppContext->GetAppPaths();
if (pList != NULL && pList->GetCount() > 0)
{
StackSString result;

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.

Suggested change
StackSString result;
SString result;

AppendStringArrayList(pList, result);
retValue.Set(result);
found = TRUE;
}
}
else
{
// Caller is expected to only request known properties.
_ASSERTE(!"AppContext_TryGetHostPropertyValue called with unknown name");
}

END_QCALL;

return found;
}

extern "C" void QCALLTYPE String_IsInterned(QCall::StringHandleOnStack str)
{
QCALL_CONTRACT;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/appdomainnative.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extern "C" void QCALLTYPE String_IsInterned(QCall::StringHandleOnStack str);

extern "C" void QCALLTYPE AppDomain_CreateDynamicAssembly(QCall::ObjectHandleOnStack assemblyLoadContext, NativeAssemblyNameParts* pAssemblyName, INT32 hashAlgorithm, INT32 access, QCall::ObjectHandleOnStack retAssembly);
extern "C" void QCALLTYPE AppContext_SetFirstChanceExceptionHandler();
extern "C" BOOL QCALLTYPE AppContext_TryGetHostPropertyValue(LPCWSTR name, QCall::StringHandleOnStack retValue);

extern "C" void QCALLTYPE AssemblyNative_GetLoadedAssemblies(QCall::ObjectHandleOnStack retAssemblies);

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ static const Entry s_QCall[] =
DllImportEntry(String_IsInterned)
DllImportEntry(AppDomain_CreateDynamicAssembly)
DllImportEntry(AppContext_SetFirstChanceExceptionHandler)
DllImportEntry(AppContext_TryGetHostPropertyValue)
DllImportEntry(ThreadNative_Start)
DllImportEntry(ThreadNative_SetPriority)
DllImportEntry(ThreadNative_GetCurrentThread)
Expand Down
49 changes: 44 additions & 5 deletions src/installer/tests/HostActivation.Tests/RuntimeProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

using System;
using System.IO;
using System.Text.RegularExpressions;
using FluentAssertions;
using Microsoft.DotNet.Cli.Build;
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.DotNet.CoreSetup.Test;
using Microsoft.DotNet.TestUtils;
using Xunit;
Expand All @@ -29,7 +32,7 @@ public void AppConfigProperty_AppCanGetData()
.Execute()
.Should().Pass()
.And.HaveStdErrContaining($"Property {SharedTestState.AppTestPropertyName} = {SharedTestState.AppTestPropertyValue}")
.And.HaveStdOutContaining($"AppContext.GetData({SharedTestState.AppTestPropertyName}) = {SharedTestState.AppTestPropertyValue}");
.And.HavePropertyValue(SharedTestState.AppTestPropertyName, SharedTestState.AppTestPropertyValue);
}

[Fact]
Expand All @@ -40,7 +43,7 @@ public void FrameworkConfigProperty_AppCanGetData()
.Execute()
.Should().Pass()
.And.HaveStdErrContaining($"Property {SharedTestState.FrameworkTestPropertyName} = {SharedTestState.FrameworkTestPropertyValue}")
.And.HaveStdOutContaining($"AppContext.GetData({SharedTestState.FrameworkTestPropertyName}) = {SharedTestState.FrameworkTestPropertyValue}");
.And.HavePropertyValue(SharedTestState.FrameworkTestPropertyName, SharedTestState.FrameworkTestPropertyValue);
}

[Fact]
Expand All @@ -56,7 +59,7 @@ public void DuplicateConfigProperty_AppConfigValueUsed()
.Execute()
.Should().Pass()
.And.HaveStdErrContaining($"Property {SharedTestState.FrameworkTestPropertyName} = {SharedTestState.AppTestPropertyValue}")
.And.HaveStdOutContaining($"AppContext.GetData({SharedTestState.FrameworkTestPropertyName}) = {SharedTestState.AppTestPropertyValue}");
.And.HavePropertyValue(SharedTestState.FrameworkTestPropertyName, SharedTestState.AppTestPropertyValue);
}

[Fact]
Expand All @@ -77,7 +80,7 @@ public void HostFxrPathProperty_NotVisibleFromApp()
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining($"Property '{SharedTestState.HostFxrPathPropertyName}' was not found.");
.And.NotFindProperty(SharedTestState.HostFxrPathPropertyName);
}

[Fact]
Expand Down Expand Up @@ -112,7 +115,24 @@ public void SpecifiedInConfigAndDevConfig_DevConfigWins()
.Execute()
.Should().Pass()
.And.HaveStdErrContaining($"Property {SharedTestState.AppTestPropertyName} = {devConfigValue}")
.And.HaveStdOutContaining($"AppContext.GetData({SharedTestState.AppTestPropertyName}) = {devConfigValue}");
.And.HavePropertyValue(SharedTestState.AppTestPropertyName, devConfigValue);
}

[Fact]
public void KnownHostProperty_AppCanGetData()
{
sharedState.DotNet.Exec(sharedState.App.AppDll, PrintProperties,
"TRUSTED_PLATFORM_ASSEMBLIES", "NATIVE_DLL_SEARCH_DIRECTORIES",
"PLATFORM_RESOURCE_ROOTS", "APP_PATHS")
.EnableTracingAndCaptureOutputs()
.Execute()
Comment thread
elinor-fung marked this conversation as resolved.
.Should().Pass()
.And.HavePropertyContaining("TRUSTED_PLATFORM_ASSEMBLIES",
Path.Combine(sharedState.DotNet.GreatestVersionSharedFxPath, "System.Private.CoreLib.dll"))
.And.HavePropertyContaining("NATIVE_DLL_SEARCH_DIRECTORIES",
sharedState.DotNet.GreatestVersionSharedFxPath)
.And.NotFindProperty("PLATFORM_RESOURCE_ROOTS")
.And.NotFindProperty("APP_PATHS");
}

public class SharedTestState : IDisposable
Expand Down Expand Up @@ -159,4 +179,23 @@ public void Dispose()
}
}
}

internal static class RuntimePropertyCommandResultExtensions
{
public static AndConstraint<CommandResultAssertions> NotFindProperty(this CommandResultAssertions assertion, string propertyName)
{
return assertion.HaveStdOutContaining($"Property '{propertyName}' was not found.");
}

public static AndConstraint<CommandResultAssertions> HavePropertyValue(this CommandResultAssertions assertion, string propertyName, string expectedValue)
{
return assertion.HaveStdOutContaining($"AppContext.GetData({propertyName}) = {expectedValue}");
}

public static AndConstraint<CommandResultAssertions> HavePropertyContaining(this CommandResultAssertions assertion, string propertyName, string expectedSubstring)
{
return assertion.HaveStdOutMatching(
$@"AppContext\.GetData\({Regex.Escape(propertyName)}\) = .*{Regex.Escape(expectedSubstring)}");
}
}
}
28 changes: 24 additions & 4 deletions src/libraries/System.Private.CoreLib/src/System/AppContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,25 @@ public static partial class AppContext
if (s_dataStore == null)
return null;

object? data;
lock (s_dataStore)
{
s_dataStore.TryGetValue(name, out data);
if (s_dataStore.TryGetValue(name, out object? data))
return data;
}
return data;

#if !MONO && !NATIVEAOT
if (IsKnownHostProperty(name))
{
string? value = null;
if (TryGetHostPropertyValue(name, new StringHandleOnStack(ref value)))
{
SetData(name, value);
return value;
}
}
#endif
Comment thread
elinor-fung marked this conversation as resolved.

return null;
}

/// <summary>
Expand Down Expand Up @@ -205,7 +218,14 @@ internal static unsafe void Setup(char** pNames, char** pValues, int count, Exce
s_dataStore = new Dictionary<string, object?>(count);
for (int i = 0; i < count; i++)
{
s_dataStore.Add(new string(pNames[i]), new string(pValues[i]));
string name = new string(pNames[i]);

// Avoid retaining a managed string copy of known host properties.
// They will be retrieved if explicitly requested.
if (IsKnownHostProperty(name))
continue;

s_dataStore.Add(name, new string(pValues[i]));
}
}
catch (Exception ex)
Expand Down
Loading