diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs index 0f273a87e33ce2..20a72464b3ac78 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs @@ -69,19 +69,33 @@ public override event ModuleResolveEventHandler? ModuleResolve } [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] - private static extern void GetCodeBase(QCallAssembly assembly, - bool copiedName, + private static extern bool GetCodeBase(QCallAssembly assembly, StringHandleOnStack retString); - internal string? GetCodeBase(bool copiedName) + internal string? GetCodeBase() { string? codeBase = null; RuntimeAssembly runtimeAssembly = this; - GetCodeBase(new QCallAssembly(ref runtimeAssembly), copiedName, new StringHandleOnStack(ref codeBase)); - return codeBase; + if (GetCodeBase(new QCallAssembly(ref runtimeAssembly), new StringHandleOnStack(ref codeBase))) + { + return codeBase; + } + return null; } - public override string? CodeBase => GetCodeBase(false); + public override string? CodeBase + { + get + { + var codeBase = GetCodeBase(); + if (codeBase is null) + { + // Not supported if the assembly was loaded from memory + throw new NotSupportedException(SR.NotSupported_CodeBase); + } + return codeBase; + } + } internal RuntimeAssembly GetNativeHandle() => this; @@ -90,7 +104,7 @@ private static extern void GetCodeBase(QCallAssembly assembly, // is returned. public override AssemblyName GetName(bool copiedName) { - string? codeBase = GetCodeBase(copiedName); + string? codeBase = GetCodeBase(); var an = new AssemblyName(GetSimpleName(), GetPublicKey(), diff --git a/src/coreclr/src/vm/assembly.hpp b/src/coreclr/src/vm/assembly.hpp index 26291d2c0f65e6..4b594c46dc3744 100644 --- a/src/coreclr/src/vm/assembly.hpp +++ b/src/coreclr/src/vm/assembly.hpp @@ -337,7 +337,7 @@ class Assembly } #endif // DACCESS_COMPILE - void GetCodeBase(SString &result) + BOOL GetCodeBase(SString &result) { WRAPPER_NO_CONTRACT; diff --git a/src/coreclr/src/vm/assemblynative.cpp b/src/coreclr/src/vm/assemblynative.cpp index 8f5bb7820ec8a7..432f16785b3fcc 100644 --- a/src/coreclr/src/vm/assemblynative.cpp +++ b/src/coreclr/src/vm/assemblynative.cpp @@ -562,21 +562,24 @@ void QCALLTYPE AssemblyNative::GetLocale(QCall::AssemblyHandle pAssembly, QCall: END_QCALL; } -void QCALLTYPE AssemblyNative::GetCodeBase(QCall::AssemblyHandle pAssembly, BOOL fCopiedName, QCall::StringHandleOnStack retString) +BOOL QCALLTYPE AssemblyNative::GetCodeBase(QCall::AssemblyHandle pAssembly, QCall::StringHandleOnStack retString) { QCALL_CONTRACT; + BOOL ret = TRUE; + BEGIN_QCALL; StackSString codebase; { - pAssembly->GetFile()->GetCodeBase(codebase); + ret = pAssembly->GetFile()->GetCodeBase(codebase); } retString.Set(codebase); - END_QCALL; + + return ret; } INT32 QCALLTYPE AssemblyNative::GetHashAlgorithm(QCall::AssemblyHandle pAssembly) diff --git a/src/coreclr/src/vm/assemblynative.hpp b/src/coreclr/src/vm/assemblynative.hpp index db80d9a8a28250..4ba91275b72ec0 100644 --- a/src/coreclr/src/vm/assemblynative.hpp +++ b/src/coreclr/src/vm/assemblynative.hpp @@ -60,7 +60,7 @@ class AssemblyNative void QCALLTYPE GetLocation(QCall::AssemblyHandle pAssembly, QCall::StringHandleOnStack retString); static - void QCALLTYPE GetCodeBase(QCall::AssemblyHandle pAssembly, BOOL fCopiedName, QCall::StringHandleOnStack retString); + BOOL QCALLTYPE GetCodeBase(QCall::AssemblyHandle pAssembly, QCall::StringHandleOnStack retString); static BYTE * QCALLTYPE GetResource(QCall::AssemblyHandle pAssembly, LPCWSTR wszName, DWORD * length); diff --git a/src/coreclr/src/vm/pefile.cpp b/src/coreclr/src/vm/pefile.cpp index 25d379524977cb..9966c4c1d2e5f9 100644 --- a/src/coreclr/src/vm/pefile.cpp +++ b/src/coreclr/src/vm/pefile.cpp @@ -2128,9 +2128,8 @@ const SString &PEAssembly::GetEffectivePath() // Codebase is the fusion codebase or path for the assembly. It is in URL format. // Note this may be obtained from the parent PEFile if we don't have a path or fusion // assembly. -// -// fCopiedName means to get the "shadow copied" path rather than the original path, if applicable -void PEAssembly::GetCodeBase(SString &result, BOOL fCopiedName/*=FALSE*/) +// Returns false if the assembly was loaded from a bundle, true otherwise +BOOL PEAssembly::GetCodeBase(SString &result) { CONTRACTL { @@ -2142,10 +2141,20 @@ void PEAssembly::GetCodeBase(SString &result, BOOL fCopiedName/*=FALSE*/) } CONTRACTL_END; - // All other cases use the file path. - result.Set(GetEffectivePath()); - if (!result.IsEmpty()) - PathToUrl(result); + auto ilImage = GetILimage(); + if (ilImage == nullptr || !ilImage->IsInBundle()) + { + // All other cases use the file path. + result.Set(GetEffectivePath()); + if (!result.IsEmpty()) + PathToUrl(result); + return TRUE; + } + else + { + result.Set(SString::Empty()); + return FALSE; + } } /* static */ diff --git a/src/coreclr/src/vm/pefile.h b/src/coreclr/src/vm/pefile.h index 383fb53de0534c..32eb4f48334447 100644 --- a/src/coreclr/src/vm/pefile.h +++ b/src/coreclr/src/vm/pefile.h @@ -663,9 +663,7 @@ class PEAssembly : public PEFile // Codebase is the fusion codebase or path for the assembly. It is in URL format. // Note this may be obtained from the parent PEFile if we don't have a path or fusion // assembly. - // - // fCopiedName means to get the "shadow copied" path rather than the original path, if applicable - void GetCodeBase(SString &result, BOOL fCopiedName = FALSE); + BOOL GetCodeBase(SString &result); // Display name is the fusion binding name for an assembly void GetDisplayName(SString &result, DWORD flags = 0); diff --git a/src/installer/tests/Assets/TestProjects/EnvironmentGetCommandLineArgs/Program.cs b/src/installer/tests/Assets/TestProjects/EnvironmentGetCommandLineArgs/Program.cs deleted file mode 100644 index c10cb9c974c9de..00000000000000 --- a/src/installer/tests/Assets/TestProjects/EnvironmentGetCommandLineArgs/Program.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace EnvironmentGetCommandLineArgs -{ - public class Program - { - public static void Main(string[] args) - { - foreach (var arg in Environment.GetCommandLineArgs()) - { - Console.WriteLine(arg); - } - } - } -} diff --git a/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Program.cs b/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Program.cs new file mode 100644 index 00000000000000..7b4ed44b4a3b40 --- /dev/null +++ b/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Program.cs @@ -0,0 +1,39 @@ +using System; + +namespace SingleFileApiTests +{ + public class Program + { + public static void Main(string[] args) + { + switch (args[0]) + { + case "fullyqualifiedname": + var module = typeof(object).Assembly.GetModules()[0]; + Console.WriteLine("FullyQualifiedName: " + module.FullyQualifiedName); + Console.WriteLine("Name: " + module.Name); + return; + + case "cmdlineargs": + Console.WriteLine(Environment.GetCommandLineArgs()[0]); + return; + + case "codebase": + try + { + #pragma warning disable SYSLIB0012 + _ = typeof(Program).Assembly.CodeBase; + #pragma warning restore SYSLIB0012 + } + catch (NotSupportedException) + { + Console.WriteLine("CodeBase NotSupported"); + return; + } + break; + } + + Console.WriteLine("test failure"); + } + } +} diff --git a/src/installer/tests/Assets/TestProjects/EnvironmentGetCommandLineArgs/EnvironmentGetCommandLineArgs.csproj b/src/installer/tests/Assets/TestProjects/SingleFileApiTests/SingleFileApiTests.csproj similarity index 100% rename from src/installer/tests/Assets/TestProjects/EnvironmentGetCommandLineArgs/EnvironmentGetCommandLineArgs.csproj rename to src/installer/tests/Assets/TestProjects/SingleFileApiTests/SingleFileApiTests.csproj diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleEnvironmentGetCommandLineArgs.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/SingleFileApiTests.cs similarity index 61% rename from src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleEnvironmentGetCommandLineArgs.cs rename to src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/SingleFileApiTests.cs index 1126272fc8a73b..ece250d2e8cad7 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleEnvironmentGetCommandLineArgs.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/SingleFileApiTests.cs @@ -6,15 +6,49 @@ namespace AppHost.Bundle.Tests { - public class BundleEnvironmentGetCommandLineArgs : IClassFixture + public class SingleFileApiTests : IClassFixture { private SharedTestState sharedTestState; - public BundleEnvironmentGetCommandLineArgs(SharedTestState fixture) + public SingleFileApiTests(SharedTestState fixture) { sharedTestState = fixture; } + [Fact] + public void FullyQualifiedName() + { + var fixture = sharedTestState.TestFixture.Copy(); + var singleFile = BundleHelper.BundleApp(fixture); + + Command.Create(singleFile, "fullyqualifiedname") + .CaptureStdErr() + .CaptureStdOut() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("FullyQualifiedName: " + + Environment.NewLine + + "Name: "); + } + + [Fact] + public void CodeBaseThrows() + { + var fixture = sharedTestState.TestFixture.Copy(); + var singleFile = BundleHelper.BundleApp(fixture); + + Command.Create(singleFile, "codebase") + .CaptureStdErr() + .CaptureStdOut() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("CodeBase NotSupported"); + } + [Fact] public void GetEnvironmentArgs_0_Returns_Bundled_Executable_Path() { @@ -23,7 +57,7 @@ public void GetEnvironmentArgs_0_Returns_Bundled_Executable_Path() // For single-file, Environment.GetCommandLineArgs[0] // should return the file path of the host. - Command.Create(singleFile) + Command.Create(singleFile, "cmdlineargs") .CaptureStdErr() .CaptureStdOut() .Execute() @@ -42,7 +76,7 @@ public void GetEnvironmentArgs_0_Non_Bundled_App() // For non single-file apps, Environment.GetCommandLineArgs[0] // should return the file path of the managed entrypoint. - dotnet.Exec(appPath) + dotnet.Exec(appPath, "cmdlineargs") .CaptureStdErr() .CaptureStdOut() .Execute() @@ -60,7 +94,7 @@ public class SharedTestState : IDisposable public SharedTestState() { RepoDirectories = new RepoDirectoriesProvider(); - TestFixture = new TestProjectFixture("EnvironmentGetCommandLineArgs", RepoDirectories); + TestFixture = new TestProjectFixture("SingleFileApiTests", RepoDirectories); TestFixture .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages) .PublishProject(runtime: TestFixture.CurrentRid, outputDirectory: BundleHelper.GetPublishPath(TestFixture)); diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 57ed5264638c60..c2d6cea19e41ac 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3775,4 +3775,7 @@ BinaryFormatter serialization and deserialization are disabled within this application. See https://aka.ms/binaryformatter for more information. + + CodeBase is not supported on assemblies loaded from a single-file bundle. + diff --git a/src/libraries/System.Reflection/tests/AssemblyTests.cs b/src/libraries/System.Reflection/tests/AssemblyTests.cs index bc9642718499d4..66d8d41a5675b3 100644 --- a/src/libraries/System.Reflection/tests/AssemblyTests.cs +++ b/src/libraries/System.Reflection/tests/AssemblyTests.cs @@ -185,6 +185,17 @@ public void GetFile_InMemory() Assert.Throws(() => asm.GetFiles(getResourceModules: false)); } + [Fact] + public void CodeBaseInMemory() + { + var inMemBlob = File.ReadAllBytes(SourceTestAssemblyPath); + var asm = Assembly.Load(inMemBlob); + // Should not throw + #pragma warning disable SYSLIB0012 + _ = asm.CodeBase; + #pragma warning restore SYSLIB0012 + } + [Fact] public void GetFiles() {