terminal/src/cascadia/WinRTUtils/LibraryResources.cpp

111 lines
3.2 KiB
C++
Raw Normal View History

Introduce a WinRT utils library and "checked resources" (#3350) This commit introduces a C++/WinRT utility library and moves ScopedResourceLoader into it. I decided to get uppity and introduce something I like to call "checked resources." The idea is that every resource reference from a library is knowable at compile time, and we should be able to statically ensure that all resources exist. This is a system that lets us immediately failfast (on launch) when a library makes a static reference to a resource that doesn't exist at runtime. It exposes two new (preprocessor) APIs: * `RS_(wchar_t)`: loads a localizable string resource by name. * `USES_RESOURCE(wchar_t)`: marks a resource key as used, but is intended for loading images or passing static resource keys as parameters to functions that will look them up later. Resource checking relies on diligent use of `USES_RESOURCE()` and `RS_()` (which uses `USES_RESOURCE`), but can make sure we don't ship something that'll blow up at runtime. It works like this: **IN DEBUG MODE** - All resource names referenced through `USES_RESOURCE()` are emitted alongside their referencing filenames and line numbers into a static section of the binary. That section is named `.util$res$m`. - We emit two sentinel values into two different sections, `.util$res$a` and `.util$res$z`. - The linker sorts all sections alphabetically before crushing them together into the final binary. - When we first construct a library's scoped resource loader, we iterate over every resource reference between `$a` and `$z` and check residency. **IN RELEASE MODE** - All checked resource code is compiled out. Fixes #2146. Macros are the only way to do something this cool, incidentally. ## Validation Steps Performed Made references to a bunch of bad resources, tried to break it a lot. It looks like this when it fails: ### App.cpp ``` 36 static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels { 37 USES_RESOURCE(L"NoProfilesText"), 38 USES_RESOURCE(L"AllProfilesHiddenText_HA_JUST_KIDDING") 39 }; ``` ``` WinRTUtils\LibraryResources.cpp(68)\TerminalApp.dll: FailFast(1) tid(1034) 8000FFFF Catastrophic failure Msg:[Resource AllProfilesHiddenText_HA_JUST_KIDDING not found in scope TerminalApp/Resources (App.cpp:38)] [EnsureAllResourcesArePresent] ```
2019-11-01 23:47:05 +01:00
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ScopedResourceLoader.h"
#include "LibraryResources.h"
/*
CHECKED RESOURCES
This is the support infrastructure for "checked resources", a system that lets
us immediately failfast (on launch) when a library makes a static reference to
a resource that doesn't exist at runtime.
Resource checking relies on diligent use of USES_RESOURCE() and RS_() (which
uses USES_RESOURCE), but can make sure we don't ship something that'll blow up
at runtime.
It works like this:
** IN DEBUG MODE **
- All resource names referenced through USES_RESOURCE() are emitted alongside
their referencing filenames and line numbers into a static section of the
binary.
That section is named .util$res$m.
- We emit two sentinel values into two different sections, .util$res$a and
.util$res$z.
- The linker sorts all sections alphabetically before crushing them together
into the final binary.
- When we first construct our library's scoped resource loader, we iterate over
every resource reference between $a and $z and check residency.
** IN RELEASE MODE **
- All checked resource code is compiled out.
*/
extern const wchar_t* g_WinRTUtilsLibraryResourceScope;
#ifdef _DEBUG
#pragma detect_mismatch("winrt_utils_debug", "1")
#pragma section(".util$res$a", read)
#pragma section(".util$res$z", read)
__declspec(allocate(".util$res$a")) static const ::Microsoft::Console::Utils::StaticResource* debugResFirst{ nullptr };
__declspec(allocate(".util$res$z")) static const ::Microsoft::Console::Utils::StaticResource* debugResLast{ nullptr };
static void EnsureAllResourcesArePresent(const ScopedResourceLoader& loader)
{
for (auto resp = &debugResFirst; resp != &debugResLast; ++resp)
{
if (*resp)
{
const auto& res = **resp;
if (!loader.HasResourceWithName(res.resourceKey))
{
auto filename = wcsrchr(res.filename, L'\\');
if (!filename)
{
filename = res.filename;
}
else
{
filename++; // skip the '\'
}
FAIL_FAST_MSG("Resource %ls not found in scope %ls (%ls:%u)", res.resourceKey, g_WinRTUtilsLibraryResourceScope, filename, res.line);
}
}
}
}
#else // _DEBUG
#pragma detect_mismatch("winrt_utils_debug", "0")
#endif
static ScopedResourceLoader GetLibraryResourceLoader()
Introduce a WinRT utils library and "checked resources" (#3350) This commit introduces a C++/WinRT utility library and moves ScopedResourceLoader into it. I decided to get uppity and introduce something I like to call "checked resources." The idea is that every resource reference from a library is knowable at compile time, and we should be able to statically ensure that all resources exist. This is a system that lets us immediately failfast (on launch) when a library makes a static reference to a resource that doesn't exist at runtime. It exposes two new (preprocessor) APIs: * `RS_(wchar_t)`: loads a localizable string resource by name. * `USES_RESOURCE(wchar_t)`: marks a resource key as used, but is intended for loading images or passing static resource keys as parameters to functions that will look them up later. Resource checking relies on diligent use of `USES_RESOURCE()` and `RS_()` (which uses `USES_RESOURCE`), but can make sure we don't ship something that'll blow up at runtime. It works like this: **IN DEBUG MODE** - All resource names referenced through `USES_RESOURCE()` are emitted alongside their referencing filenames and line numbers into a static section of the binary. That section is named `.util$res$m`. - We emit two sentinel values into two different sections, `.util$res$a` and `.util$res$z`. - The linker sorts all sections alphabetically before crushing them together into the final binary. - When we first construct a library's scoped resource loader, we iterate over every resource reference between `$a` and `$z` and check residency. **IN RELEASE MODE** - All checked resource code is compiled out. Fixes #2146. Macros are the only way to do something this cool, incidentally. ## Validation Steps Performed Made references to a bunch of bad resources, tried to break it a lot. It looks like this when it fails: ### App.cpp ``` 36 static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels { 37 USES_RESOURCE(L"NoProfilesText"), 38 USES_RESOURCE(L"AllProfilesHiddenText_HA_JUST_KIDDING") 39 }; ``` ``` WinRTUtils\LibraryResources.cpp(68)\TerminalApp.dll: FailFast(1) tid(1034) 8000FFFF Catastrophic failure Msg:[Resource AllProfilesHiddenText_HA_JUST_KIDDING not found in scope TerminalApp/Resources (App.cpp:38)] [EnsureAllResourcesArePresent] ```
2019-11-01 23:47:05 +01:00
{
ScopedResourceLoader loader{ g_WinRTUtilsLibraryResourceScope };
#ifdef _DEBUG
EnsureAllResourcesArePresent(loader);
#endif
return loader;
}
winrt::hstring GetLibraryResourceString(const std::wstring_view key)
try
Introduce a WinRT utils library and "checked resources" (#3350) This commit introduces a C++/WinRT utility library and moves ScopedResourceLoader into it. I decided to get uppity and introduce something I like to call "checked resources." The idea is that every resource reference from a library is knowable at compile time, and we should be able to statically ensure that all resources exist. This is a system that lets us immediately failfast (on launch) when a library makes a static reference to a resource that doesn't exist at runtime. It exposes two new (preprocessor) APIs: * `RS_(wchar_t)`: loads a localizable string resource by name. * `USES_RESOURCE(wchar_t)`: marks a resource key as used, but is intended for loading images or passing static resource keys as parameters to functions that will look them up later. Resource checking relies on diligent use of `USES_RESOURCE()` and `RS_()` (which uses `USES_RESOURCE`), but can make sure we don't ship something that'll blow up at runtime. It works like this: **IN DEBUG MODE** - All resource names referenced through `USES_RESOURCE()` are emitted alongside their referencing filenames and line numbers into a static section of the binary. That section is named `.util$res$m`. - We emit two sentinel values into two different sections, `.util$res$a` and `.util$res$z`. - The linker sorts all sections alphabetically before crushing them together into the final binary. - When we first construct a library's scoped resource loader, we iterate over every resource reference between `$a` and `$z` and check residency. **IN RELEASE MODE** - All checked resource code is compiled out. Fixes #2146. Macros are the only way to do something this cool, incidentally. ## Validation Steps Performed Made references to a bunch of bad resources, tried to break it a lot. It looks like this when it fails: ### App.cpp ``` 36 static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels { 37 USES_RESOURCE(L"NoProfilesText"), 38 USES_RESOURCE(L"AllProfilesHiddenText_HA_JUST_KIDDING") 39 }; ``` ``` WinRTUtils\LibraryResources.cpp(68)\TerminalApp.dll: FailFast(1) tid(1034) 8000FFFF Catastrophic failure Msg:[Resource AllProfilesHiddenText_HA_JUST_KIDDING not found in scope TerminalApp/Resources (App.cpp:38)] [EnsureAllResourcesArePresent] ```
2019-11-01 23:47:05 +01:00
{
static auto loader{ GetLibraryResourceLoader() };
return loader.GetLocalizedString(key);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return winrt::hstring{};
}
bool HasLibraryResourceWithName(const std::wstring_view key)
try
{
static auto loader{ GetLibraryResourceLoader() };
return loader.HasResourceWithName(key);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return false;
}