Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permissive handling for runtimeconfig.json #92037

Merged
merged 12 commits into from
Oct 8, 2023
84 changes: 65 additions & 19 deletions src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,66 @@ public Ijwhost(SharedTestState sharedTestState)
sharedState = sharedTestState;
}

[Fact]
public void LoadLibrary()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void LoadLibrary(bool no_runtimeconfig)
{
string [] args = {
"ijwhost",
sharedState.IjwLibraryPath,
"NativeEntryPoint"
};
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet)
.Execute();
// make a copy of a portion of the shared state because we will modify it
using (var app = sharedState.IjwApp.Copy())
{
string [] args = {
"ijwhost",
app.AppDll,
"NativeEntryPoint"
};
if (no_runtimeconfig)
{
File.Delete(app.RuntimeConfigJson);
}

CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet)
.Execute();

if (no_runtimeconfig)
{
result.Should().Fail()
.And.HaveStdErrContaining($"Expected active runtime context because runtimeconfig.json [{app.RuntimeConfigJson}] does not exist.");
}
else
{
result.Should().Pass()
.And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class")
.And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
}
}
}

result.Should().Pass()
.And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class")
.And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
[Fact]
public void LoadLibraryWithoutRuntimeConfigButActiveRuntime()
{
// make a copy of a portion of the shared state because we will modify it
using (var app = sharedState.IjwApp.Copy())
{
// construct runtimeconfig.json
var startupConfigPath = Path.Combine(Path.GetDirectoryName(app.RuntimeConfigJson),"host.runtimeconfig.json");
string [] args = {
"ijwhost",
app.AppDll,
"NativeEntryPoint",
sharedState.HostFxrPath, // optional 4th and 5th arguments that tell nativehost to start the runtime before loading the C++/CLI library
startupConfigPath
};

File.Move(app.RuntimeConfigJson, startupConfigPath);

CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet)
.Execute();

result.Should().Pass()
.And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class")
.And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
}
}

[Theory]
Expand All @@ -45,7 +91,7 @@ public void ManagedHost(bool selfContained)
{
string [] args = {
"ijwhost",
sharedState.IjwLibraryPath,
sharedState.IjwApp.AppDll,
"NativeEntryPoint"
};
TestProjectFixture fixture = selfContained ? sharedState.ManagedHostFixture_SelfContained : sharedState.ManagedHostFixture_FrameworkDependent;
Expand All @@ -63,24 +109,24 @@ public void ManagedHost(bool selfContained)

public class SharedTestState : SharedTestStateBase
{
public string IjwLibraryPath { get; }

public string HostFxrPath { get; }
public TestProjectFixture ManagedHostFixture_FrameworkDependent { get; }
public TestProjectFixture ManagedHostFixture_SelfContained { get; }
public TestApp IjwApp {get;}

public SharedTestState()
{
var dotNet = new Microsoft.DotNet.Cli.Build.DotNetCli(RepoDirectories.BuiltDotnet);
HostFxrPath = dotNet.GreatestVersionHostFxrFilePath;
string folder = Path.Combine(BaseDirectory, "ijw");
Directory.CreateDirectory(folder);

IjwApp = new TestApp(folder, "ijw");
// Copy over ijwhost
string ijwhostName = "ijwhost.dll";
File.Copy(Path.Combine(RepoDirectories.HostArtifacts, ijwhostName), Path.Combine(folder, ijwhostName));

// Copy over the C++/CLI test library
string ijwLibraryName = "ijw.dll";
IjwLibraryPath = Path.Combine(folder, ijwLibraryName);
File.Copy(Path.Combine(RepoDirectories.HostTestArtifacts, ijwLibraryName), IjwLibraryPath);
File.Copy(Path.Combine(RepoDirectories.HostTestArtifacts, ijwLibraryName), Path.Combine(folder, ijwLibraryName));

// Create a runtimeconfig.json for the C++/CLI test library
new RuntimeConfig(Path.Combine(folder, "ijw.runtimeconfig.json"))
Expand Down
5 changes: 3 additions & 2 deletions src/native/corehost/comhost/comhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ namespace
delegates.delegate_no_load_cxt = nullptr;

get_function_pointer_fn get_function_pointer;
int status = load_fxr_and_get_delegate(
int status = load_fxr_and_get_delegate(
szilvaa-adsk marked this conversation as resolved.
Show resolved Hide resolved
hostfxr_delegate_type::hdt_get_function_pointer,
[app_path](const pal::string_t& host_path, pal::string_t* config_path_out)
{
Expand Down Expand Up @@ -123,7 +123,8 @@ namespace
*load_context = nullptr; // Default context
}
},
reinterpret_cast<void**>(&get_function_pointer)
reinterpret_cast<void**>(&get_function_pointer),
false // do not ignore missing config file if there's an active context
);
if (status != StatusCode::Success)
return status;
Expand Down
51 changes: 31 additions & 20 deletions src/native/corehost/fxr_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace fxr_resolver
}

template<typename THostPathToConfigCallback, typename TBeforeRunCallback>
int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TBeforeRunCallback on_before_run, void** delegate)
int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TBeforeRunCallback on_before_run, void** delegate, bool try_ignore_missing_config)
{
pal::dll_t fxr;

Expand Down Expand Up @@ -67,34 +67,45 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb
if (status != StatusCode::Success)
return status;

hostfxr_initialize_parameters parameters {
sizeof(hostfxr_initialize_parameters),
host_path.c_str(),
dotnet_root.c_str()
};

hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast<hostfxr_set_error_writer_fn>(pal::get_symbol(fxr, "hostfxr_set_error_writer"));

{
propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn);
if (!try_ignore_missing_config || pal::file_exists(config_path))
{
hostfxr_initialize_parameters parameters {
sizeof(hostfxr_initialize_parameters),
host_path.c_str(),
dotnet_root.c_str()
};

hostfxr_handle context;
int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), &parameters, &context);
if (!STATUS_CODE_SUCCEEDED(rc))
return rc;
hostfxr_handle context;
int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), &parameters, &context);
if (!STATUS_CODE_SUCCEEDED(rc))
return rc;

on_before_run(fxr, context);

on_before_run(fxr, context);
rc = hostfxr_get_runtime_delegate(context, type, delegate);

rc = hostfxr_get_runtime_delegate(context, type, delegate);
int rcClose = hostfxr_close(context);
if (rcClose != StatusCode::Success)
{
assert(false && "Failed to close host context");
trace::verbose(_X("Failed to close host context: 0x%x"), rcClose);
}

int rcClose = hostfxr_close(context);
if (rcClose != StatusCode::Success)
return rc;
}
else
{
assert(false && "Failed to close host context");
trace::verbose(_X("Failed to close host context: 0x%x"), rcClose);
// null context means use the current one, if none exists it will fail
int rc = hostfxr_get_runtime_delegate(nullptr, type, delegate);
if (rc == StatusCode::HostInvalidState)
{
trace::error(_X("Expected active runtime context because runtimeconfig.json [%s] does not exist."), config_path.c_str());
}
return rc;
}

return rc;
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/native/corehost/ijwhost/ijwhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m
return StatusCode::Success;
},
[](pal::dll_t fxr, hostfxr_handle context){ },
reinterpret_cast<void**>(&get_function_pointer)
reinterpret_cast<void**>(&get_function_pointer),
true // ignore missing config file if there's an active context
);
if (status != StatusCode::Success)
return status;
Expand Down
25 changes: 23 additions & 2 deletions src/native/corehost/test/nativehost/nativehost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,19 @@ int main(const int argc, const pal::char_t *argv[])
return -1;
}

// 2 optional arguments indicating whether we should start the runtime
if (argc > 5)
{
const pal::string_t hostfxr_path = argv[4];
const pal::char_t* config_path = argv[5];
pal::stringstream_t test_output;
if (!host_context_test::config(host_context_test::check_properties::none, hostfxr_path, config_path, 0, nullptr, test_output))
{
std::cout << "Failed to start runtime from path: " << tostr(hostfxr_path).data() << std::endl;
return EXIT_FAILURE;
}
}

const pal::string_t ijw_library_path = argv[2];
std::vector<char> entry_point_name = tostr(argv[3]);

Expand All @@ -543,8 +556,16 @@ int main(const int argc, const pal::char_t *argv[])
std::cout << "Failed to find entry point: " << entry_point_name.data() << std::endl;
return EXIT_FAILURE;
}

entry_point();
try
{
entry_point();
}
catch (...)
{
// entry_point will throw in some tests, this is expected.
// We must catch this exception to ensure that the CRT does not pop a modal dialog
return EXIT_FAILURE;
szilvaa-adsk marked this conversation as resolved.
Show resolved Hide resolved
}
return EXIT_SUCCESS;
}
#endif
Expand Down