From d872b8da6c16f3c3dfdcd40327db60afeb0e61e8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 5 Mar 2025 13:07:44 -0800 Subject: [PATCH 1/2] Handle relative path loading on non-Windows for AppContext.BaseDirectory on NativeAOT --- .../src/System/AppContext.NativeAot.cs | 62 ++++++++++++++++--- .../System/AppContext/AppContextTests.cs | 6 ++ 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs index e4b354899dd8aa..f40e8b8b3633a5 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs @@ -3,7 +3,9 @@ using Internal.Runtime.Augments; using System.Collections.Generic; +using System.IO; using System.Runtime; +using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Text; @@ -40,18 +42,60 @@ internal static void OnUnhandledException(object e) private static unsafe string GetRuntimeModulePath() { - // We aren't going to call this method, we just need an address that we know is in this module. - // As this code is NativeAOT only, we know that this method will be AOT compiled into the executable, - // so the entry point address will be in the module. - void* ip = (void*)(delegate*)&GetRuntimeModulePath; - if (RuntimeAugments.TryGetFullPathToApplicationModule((nint)ip, out _) is string modulePath) + return RuntimeModulePathHolder.RuntimeModulePath; + } + + internal static class RuntimeModulePathHolder + { + private static string? s_workingDirectoryAtStartup; + private static string WorkingDirectoryAtStartup + { + // In case a user's module initializer tries to get AppContext.BaseDirectory + // before our module initializer has run, we need to set up the getter to initialize + // the value. + get => s_workingDirectoryAtStartup ??= Environment.CurrentDirectory; + } + + #pragma warning disable CA2255 // The 'ModuleInitializer' attribute is only intended to be used in application code or advanced source generator scenarios + // We need to capture this value at startup in case the user changes Environment.CurrentDirectory + // before the first execution of AppContext.BaseDirectory. + [ModuleInitializer] + #pragma warning restore CA2255 + public static void Initialize() { - return modulePath; + s_workingDirectoryAtStartup = Environment.CurrentDirectory; } - // If this method isn't in a dynamically loaded module, - // then it's in the executable. In that case, we can use the process path. - return Environment.ProcessPath; + public static string RuntimeModulePath { get; } = GetRuntimeModulePath(); + + private static unsafe string GetRuntimeModulePath() + { + // We aren't going to call this method, we just need an address that we know is in this module. + // As this code is NativeAOT only, we know that this method will be AOT compiled into the executable, + // so the entry point address will be in the module. + void* ip = (void*)(delegate*)&GetRuntimeModulePath; + if (RuntimeAugments.TryGetFullPathToApplicationModule((nint)ip, out _) is string modulePath) + { + if (!Path.IsPathRooted(modulePath)) + { + // If the path is not rooted, it is relative to the working directory. + // We need to make it absolute. + modulePath = Path.Combine(WorkingDirectoryAtStartup, modulePath); + if (!Path.Exists(modulePath)) + { + // If this path doesn't exist, that means that this module was loaded + // from a different relative path using something like LD_LIBRARY_PATH. + // In this case, we'll say we can't get the module path and return the process path. + return Environment.ProcessPath; + } + } + return modulePath; + } + + // If this method isn't in a dynamically loaded module, + // then it's in the executable. In that case, we can use the process path. + return Environment.ProcessPath; + } } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/AppContext/AppContextTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/AppContext/AppContextTests.cs index 9b47feed876c80..7d344d0f69122d 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/AppContext/AppContextTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/AppContext/AppContextTests.cs @@ -28,5 +28,11 @@ public void AppContext_ThrowTest() { AssertExtensions.Throws("name", () => AppContext.SetData(null, 123)); } + + [Fact] + public void BaseDirectory_PathRooted() + { + Assert.True(Path.IsPathRooted(AppContext.BaseDirectory), "BaseDirectory should be a rooted path"); + } } } From 33ca2f7e99052dafede2ee8c2dd5ff5cb71b57db Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 5 Mar 2025 13:32:28 -0800 Subject: [PATCH 2/2] Add missing using --- .../System.Runtime.Tests/System/AppContext/AppContextTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/AppContext/AppContextTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/AppContext/AppContextTests.cs index 7d344d0f69122d..6cf9ce9ac7f995 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/AppContext/AppContextTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/AppContext/AppContextTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using Xunit; namespace System.Tests