From 47a953e99b2d61a03ad84aa99d54730ca6510f32 Mon Sep 17 00:00:00 2001 From: Ben Hillis Date: Mon, 13 Jul 2026 20:26:42 -0700 Subject: [PATCH] Make char->wide formatter usage conformant for Linux SDK 1.23.0 (libc++ 21) libc++ 21 (Linux SDK 1.23.0) and current MSVC STL delete the std::formatter specializations per C++23 [format.formatter.spec], which collides with WSL's own char->wide formatter specializations in stringshared.h. Remove those specializations and widen narrow arguments at the format boundary instead: - stringshared.h: drop the char->wide formatter specializations; add a WideFormatArg() boundary helper that widens narrow args (char*, const char*, char[N], std::string/std::string_view) to wide, identity otherwise. On non-Windows it is always identity. - generateLocalizationHeader.ps1: wrap every Message*() argument with WideFormatArg via lifetime-extended locals, so localization call sites need no per-site changes. - ExecutionContext.h: normalize narrow user-error messages to wide via ToUserErrorMessage() in the THROW_HR_WITH_USER_ERROR macros. - Fix the remaining direct std::format(L"...") / reporter sinks that pass narrow args (ConsommeNetworking, VersionCommand, ImageProgressCallback, ImageTasks, WslClient version macros, tests). Prefer compile-time STRING_TO_WIDE_STRING for known-narrow literals. - packages.config: bump Microsoft.WSL.LinuxSdk to 1.23.0. --- packages.config | 4 +- src/shared/inc/stringshared.h | 108 +++++++----------- src/windows/common/ConsommeNetworking.cpp | 3 +- src/windows/common/ExecutionContext.h | 16 ++- src/windows/common/WslClient.cpp | 13 ++- src/windows/wslc/commands/VersionCommand.cpp | 2 +- .../wslc/services/ImageProgressCallback.cpp | 12 +- src/windows/wslc/tasks/ImageTasks.cpp | 2 +- test/windows/Common.cpp | 3 +- test/windows/Common.h | 9 +- test/windows/DrvFsTests.cpp | 2 +- test/windows/SimpleTests.cpp | 4 +- test/windows/UnitTests.cpp | 19 ++- test/windows/WSLCTests.cpp | 34 +++--- .../wslc/e2e/WSLCE2EContainerAttachTests.cpp | 8 +- .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 10 +- .../wslc/e2e/WSLCE2EContainerExecTests.cpp | 12 +- .../wslc/e2e/WSLCE2EContainerRunTests.cpp | 6 +- test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp | 2 +- test/windows/wslc/e2e/WSLCE2EHelpers.cpp | 11 +- .../wslc/e2e/WSLCE2EImageDeleteTests.cpp | 4 +- tools/generateLocalizationHeader.ps1 | 8 +- 22 files changed, 169 insertions(+), 123 deletions(-) diff --git a/packages.config b/packages.config index ef3760988e..86c77a614c 100644 --- a/packages.config +++ b/packages.config @@ -1,4 +1,4 @@ - + @@ -21,7 +21,7 @@ - + diff --git a/src/shared/inc/stringshared.h b/src/shared/inc/stringshared.h index 978bd9cbd7..ce3aff4769 100644 --- a/src/shared/inc/stringshared.h +++ b/src/shared/inc/stringshared.h @@ -16,6 +16,9 @@ Module Name: #include #include #include +#include +#include +#include #include #include #include @@ -659,6 +662,40 @@ inline std::wstring MultiByteToWide(const std::string& string) return MultiByteToWide(string.c_str()); } +// +// Boundary helper used when narrow string arguments must be fed into a wide (wchar_t) std::format +// context (e.g. the generated Localization::Message*() formatters, and Windows-only test helpers). +// +// Any narrow string argument (const char*, char[N], std::string, std::string_view) must be widened +// first: C++20/23 explicitly disables formatter and friends ([format.formatter.spec]), +// and libc++ 20+ ships them as deleted specializations. Converting here, at the single point where +// arguments enter the wide format context, lets callers keep passing narrow strings without any +// non-conformant formatter specialization and without per-call-site conversions. Non-string and +// already-wide arguments are forwarded unchanged (and the widening branches are compiled out on +// non-Windows builds, where the format context is already narrow). +// +template +inline decltype(auto) WideFormatArg([[maybe_unused]] T&& value) +{ +#ifdef WIN32 + using Decayed = std::decay_t; + if constexpr (std::is_same_v, std::string> || std::is_same_v, std::string_view>) + { + return MultiByteToWide(std::string{value}); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + return MultiByteToWide(value); + } + else + { + return std::forward(value); + } +#else + return std::forward(value); +#endif +} + inline std::string WideToMultiByte(const wchar_t* string) { @@ -927,71 +964,12 @@ struct std::formatter template auto format(const std::source_location& location, TCtx& ctx) const { - return std::format_to(ctx.out(), L"{}[{}:{}]", location.function_name(), location.file_name(), location.line()); - } -}; - -template <> -struct std::formatter -{ - template - static constexpr auto parse(TCtx& ctx) - { - return ctx.begin(); - } - - template - auto format(const char* str, TCtx& ctx) const - { - return std::format_to(ctx.out(), "{}", wsl::shared::string::MultiByteToWide(str)); - } -}; - -template <> -struct std::formatter -{ - template - static constexpr auto parse(TCtx& ctx) - { - return ctx.begin(); - } - - template - auto format(const char* str, TCtx& ctx) const - { - return std::format_to(ctx.out(), "{}", wsl::shared::string::MultiByteToWide(str)); - } -}; - -template -struct std::formatter -{ - template - static constexpr auto parse(TCtx& ctx) - { - return ctx.begin(); - } - - template - auto format(const char str[N], TCtx& ctx) const - { - return std::format_to(ctx.out(), "{}", wsl::shared::string::MultiByteToWide(str)); - } -}; - -template -struct std::formatter, wchar_t> -{ - template - static constexpr auto parse(TCtx& ctx) - { - return ctx.begin(); - } - - template - auto format(const std::basic_string& str, TCtx& ctx) const - { - return std::format_to(ctx.out(), "{}", wsl::shared::string::MultiByteToWide(str)); + return std::format_to( + ctx.out(), + L"{}[{}:{}]", + wsl::shared::string::MultiByteToWide(location.function_name()), + wsl::shared::string::MultiByteToWide(location.file_name()), + location.line()); } }; diff --git a/src/windows/common/ConsommeNetworking.cpp b/src/windows/common/ConsommeNetworking.cpp index 67e4f49da9..f2c2124d8d 100644 --- a/src/windows/common/ConsommeNetworking.cpp +++ b/src/windows/common/ConsommeNetworking.cpp @@ -164,7 +164,8 @@ uint16_t ConsommeNetworking::ModifyOpenPorts( const auto hostAddressStr = wsl::windows::common::string::SockAddrInetToString(hostAddress); - std::wstring portString = std::format(L"tag={};guest_port={};listen_addr={}", tag, GuestPort, hostAddressStr.c_str()); + std::wstring portString = + std::format(L"tag={};guest_port={};listen_addr={}", tag, GuestPort, wsl::shared::string::MultiByteToWide(hostAddressStr)); if (HostPort != WSLC_EPHEMERAL_PORT) { diff --git a/src/windows/common/ExecutionContext.h b/src/windows/common/ExecutionContext.h index 5bbd33f4c2..7b0a154228 100644 --- a/src/windows/common/ExecutionContext.h +++ b/src/windows/common/ExecutionContext.h @@ -6,10 +6,22 @@ namespace wsl::windows::common { +// User-facing error/warning messages may be produced as wide strings (Localization on Windows) +// or narrow strings (e.g. std::string from lower layers). Normalize to wide for reporting. +inline std::wstring ToUserErrorMessage(std::wstring_view message) +{ + return std::wstring{message}; +} + +inline std::wstring ToUserErrorMessage(std::string_view message) +{ + return wsl::shared::string::MultiByteToWide(std::string{message}); +} + #define THROW_HR_WITH_USER_ERROR(Result, Message) \ do \ { \ - auto _messageWide = std::format(L"{}", Message); \ + auto _messageWide = wsl::windows::common::ToUserErrorMessage(Message); \ if (wsl::windows::common::ExecutionContext::ShouldCollectErrorMessage()) \ { \ ::wsl::windows::common::SetErrorMessage(std::wstring(_messageWide)); \ @@ -20,7 +32,7 @@ namespace wsl::windows::common { #define THROW_HR_WITH_USER_ERROR_MSG(Result, Message, Format, ...) \ do \ { \ - auto _messageWide = std::format(L"{}", Message); \ + auto _messageWide = wsl::windows::common::ToUserErrorMessage(Message); \ if (wsl::windows::common::ExecutionContext::ShouldCollectErrorMessage()) \ { \ ::wsl::windows::common::SetErrorMessage(std::wstring(_messageWide)); \ diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index 5cad5ef5e2..12fec0cd5f 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -1293,13 +1293,22 @@ int Version() const auto windowsVersion = wsl::windows::common::helpers::GetWindowsVersionString(); wsl::windows::common::wslutil::PrintMessage( Localization::MessagePackageVersions( - WSL_PACKAGE_VERSION, KERNEL_VERSION, WSLG_VERSION, MSRDC_VERSION, DIRECT3D_VERSION, DXCORE_VERSION, windowsVersion), + STRING_TO_WIDE_STRING(WSL_PACKAGE_VERSION), + STRING_TO_WIDE_STRING(KERNEL_VERSION), + STRING_TO_WIDE_STRING(WSLG_VERSION), + STRING_TO_WIDE_STRING(MSRDC_VERSION), + STRING_TO_WIDE_STRING(DIRECT3D_VERSION), + STRING_TO_WIDE_STRING(DXCORE_VERSION), + windowsVersion), stdout); if constexpr (!wsl::shared::OfficialBuild) { // Print additional information if running a debug build. - wsl::windows::common::wslutil::PrintMessage(Localization::MessageBuildInfo(_MSC_VER, COMMIT_HASH, __TIME__ " " __DATE__), stdout); + wsl::windows::common::wslutil::PrintMessage( + Localization::MessageBuildInfo( + _MSC_VER, STRING_TO_WIDE_STRING(COMMIT_HASH), STRING_TO_WIDE_STRING(__TIME__) L" " STRING_TO_WIDE_STRING(__DATE__)), + stdout); } return 0; diff --git a/src/windows/wslc/commands/VersionCommand.cpp b/src/windows/wslc/commands/VersionCommand.cpp index 63f99c106e..1cd9fc7330 100644 --- a/src/windows/wslc/commands/VersionCommand.cpp +++ b/src/windows/wslc/commands/VersionCommand.cpp @@ -29,7 +29,7 @@ std::wstring VersionCommand::LongDescription() const void VersionCommand::PrintVersion() { - wsl::windows::common::wslutil::PrintMessage(std::format(L"{} {}", s_ExecutableName, WSL_PACKAGE_VERSION)); + wsl::windows::common::wslutil::PrintMessage(std::format(L"{} {}", s_ExecutableName, STRING_TO_WIDE_STRING(WSL_PACKAGE_VERSION))); } void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const diff --git a/src/windows/wslc/services/ImageProgressCallback.cpp b/src/windows/wslc/services/ImageProgressCallback.cpp index d84a8e4fd9..1285fefd99 100644 --- a/src/windows/wslc/services/ImageProgressCallback.cpp +++ b/src/windows/wslc/services/ImageProgressCallback.cpp @@ -53,7 +53,7 @@ HRESULT ImageProgressCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG cu if (id == nullptr || *id == '\0') // Print all 'global' statuses on their own line { - WriteTerminal(std::format(L"{}\n", status)); + WriteTerminal(std::format(L"{}\n", wsl::shared::string::MultiByteToWide(status))); m_currentLine++; return S_OK; } @@ -121,15 +121,19 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, progress += std::format(L"/{}", wsl::shared::string::FormatBytes(total)); } - line = std::format(L"{}: {} [{}] {}", id, status, bar, progress); + line = std::format(L"{}: {} [{}] {}", wsl::shared::string::MultiByteToWide(id), wsl::shared::string::MultiByteToWide(status), bar, progress); } else if (current != 0) { - line = std::format(L"{}: {} {}", id, status, wsl::shared::string::FormatBytes(current)); + line = std::format( + L"{}: {} {}", + wsl::shared::string::MultiByteToWide(id), + wsl::shared::string::MultiByteToWide(status), + wsl::shared::string::FormatBytes(current)); } else { - line = std::format(L"{}: {}", id, status); + line = std::format(L"{}: {}", wsl::shared::string::MultiByteToWide(id), wsl::shared::string::MultiByteToWide(status)); } // Use the visible window width (not the buffer width) to prevent wrapping. diff --git a/src/windows/wslc/tasks/ImageTasks.cpp b/src/windows/wslc/tasks/ImageTasks.cpp index e017c445aa..5d31ec49cc 100644 --- a/src/windows/wslc/tasks/ImageTasks.cpp +++ b/src/windows/wslc/tasks/ImageTasks.cpp @@ -162,7 +162,7 @@ void ListImages(CLIExecutionContext& context) bool trunc = !context.Args.Contains(ArgType::NoTrunc); for (const auto& image : images) { - context.Reporter.Output(L"{}\n", trunc ? TruncateId(image.Id, true) : image.Id); + context.Reporter.Output(L"{}\n", MultiByteToWide(trunc ? TruncateId(image.Id, true) : image.Id)); } return; diff --git a/test/windows/Common.cpp b/test/windows/Common.cpp index 8549ee6170..359aee0245 100644 --- a/test/windows/Common.cpp +++ b/test/windows/Common.cpp @@ -2748,7 +2748,8 @@ void VerifyPatternMatch(const std::string& Content, const std::string& Pattern) { if (!PathMatchSpecA(Content.c_str(), Pattern.c_str())) { - std::wstring message = std::format(L"Output: '{}' didn't match pattern: '{}'", Content, Pattern); + std::wstring message = std::format( + L"Output: '{}' didn't match pattern: '{}'", wsl::shared::string::MultiByteToWide(Content), wsl::shared::string::MultiByteToWide(Pattern)); VERIFY_FAIL(message.c_str()); } } diff --git a/test/windows/Common.h b/test/windows/Common.h index 40348f9999..d8a5b66d94 100644 --- a/test/windows/Common.h +++ b/test/windows/Common.h @@ -679,7 +679,8 @@ void VerifyAreEqualUnordered(const std::vector& expected, const std::vector& expected, const std::vector& expected, const std::vectorc_str())) { - std::wstring message = std::format(L"Output: '{}' didn't match pattern: '{}'", output, options.value()); + std::wstring message = std::format( + L"Output: '{}' didn't match pattern: '{}'", + wsl::shared::string::MultiByteToWide(output), + wsl::shared::string::MultiByteToWide(options.value())); VERIFY_FAIL(message.c_str()); } } @@ -6526,7 +6529,7 @@ class WSLCTests auto id = container.Id(); VERIFY_ARE_EQUAL(container.Get().Stop(WSLCSignalSIGKILL, 0), WSLC_E_CONTAINER_NOT_RUNNING); - ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id)); + ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", wsl::shared::string::MultiByteToWide(id))); // Verify that the container is in running state. VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr)); @@ -6648,7 +6651,7 @@ class WSLCTests // Validate that a created container cannot be killed. auto id = container.Id(); VERIFY_ARE_EQUAL(container.Get().Kill(WSLCSignalNone), WSLC_E_CONTAINER_NOT_RUNNING); - ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id)); + ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", wsl::shared::string::MultiByteToWide(id))); VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr)); VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning); @@ -6659,7 +6662,7 @@ class WSLCTests // Validate that killing a non-running container fails (unlike Stop()) VERIFY_ARE_EQUAL(container.Get().Kill(WSLCSignalNone), WSLC_E_CONTAINER_NOT_RUNNING); - ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id)); + ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", wsl::shared::string::MultiByteToWide(id))); // Verify that deleting a container stopped via Kill() works. VERIFY_SUCCEEDED(container.Get().Delete(WSLCDeleteFlagsNone)); @@ -6705,7 +6708,7 @@ class WSLCTests auto id = container.Id(); VERIFY_ARE_EQUAL(container.Get().Delete(WSLCDeleteFlagsNone), WSLC_E_CONTAINER_IS_RUNNING); ValidateCOMErrorMessage( - std::format(L"Container '{}' is running and cannot be removed. Either stop the container before removing or use forced remove (-f).", id)); + std::format(L"Container '{}' is running and cannot be removed. Either stop the container before removing or use forced remove (-f).", wsl::shared::string::MultiByteToWide(id))); // Kill the container. auto initProcess = container.GetInitProcess(); @@ -6758,7 +6761,7 @@ class WSLCTests // Verify that Start() can't be called again on a running container. auto id = container->Id(); VERIFY_ARE_EQUAL(container->Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr), WSLC_E_CONTAINER_IS_RUNNING); - ValidateCOMErrorMessage(std::format(L"Container '{}' is running.", id)); + ValidateCOMErrorMessage(std::format(L"Container '{}' is running.", wsl::shared::string::MultiByteToWide(id))); VERIFY_ARE_EQUAL(container->State(), WslcContainerStateRunning); @@ -6815,7 +6818,7 @@ class WSLCTests auto id = container.Id(); VERIFY_ARE_EQUAL(container.Get().Delete(WSLCDeleteFlagsNone), WSLC_E_CONTAINER_IS_RUNNING); ValidateCOMErrorMessage( - std::format(L"Container '{}' is running and cannot be removed. Either stop the container before removing or use forced remove (-f).", id)); + std::format(L"Container '{}' is running and cannot be removed. Either stop the container before removing or use forced remove (-f).", wsl::shared::string::MultiByteToWide(id))); // Validate that invalid flags are rejected. VERIFY_ARE_EQUAL(container.Get().Delete(static_cast(0x4)), E_INVALIDARG); @@ -7797,7 +7800,7 @@ class WSLCTests auto retVal = launcher.LaunchNoThrow(*m_defaultSession); VERIFY_ARE_EQUAL(WSLC_E_CONTAINER_NOT_FOUND, retVal.first); - ValidateCOMErrorMessage(std::format(L"Target container '{}' not found.", targetName)); + ValidateCOMErrorMessage(std::format(L"Target container '{}' not found.", wsl::shared::string::MultiByteToWide(targetName))); } WSLC_TEST_METHOD(ContainerNetworkModePortsRejectedTest) @@ -8181,7 +8184,7 @@ class WSLCTests auto [result, _] = WSLCProcessLauncher({}, {"/bin/cat"}).LaunchNoThrow(container.Get()); VERIFY_ARE_EQUAL(result, WSLC_E_CONTAINER_NOT_RUNNING); - ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id)); + ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", wsl::shared::string::MultiByteToWide(id))); } // Validate that invalid tty sizes are rejected. @@ -8642,7 +8645,7 @@ class WSLCTests auto container = createTcpContainer({{WSLC_EPHEMERAL_PORT, 8000, AF_INET, IPPROTO_TCP, "127.0.0.1"}}); auto hostPort = validateInspectPortBinding(container, 8000, IPPROTO_TCP, "127.0.0.1", std::nullopt); - ExpectHttpResponse(std::format(L"http://127.0.0.1:{}", hostPort).c_str(), 200); + ExpectHttpResponse(std::format(L"http://127.0.0.1:{}", wsl::shared::string::MultiByteToWide(hostPort)).c_str(), 200); } // Anonymous bind on host ip (ephemeral host port). @@ -8652,7 +8655,8 @@ class WSLCTests auto container = createTcpContainer({{WSLC_EPHEMERAL_PORT, 8000, AF_INET, IPPROTO_TCP, hostIpNarrow.value()}}); auto hostPort = validateInspectPortBinding(container, 8000, IPPROTO_TCP, hostIpNarrow.value(), std::nullopt); - ExpectHttpResponse(std::format(L"http://{}:{}", hostIp.value(), hostPort).c_str(), 200); + ExpectHttpResponse( + std::format(L"http://{}:{}", hostIp.value(), wsl::shared::string::MultiByteToWide(hostPort)).c_str(), 200); } else { @@ -10380,7 +10384,7 @@ class WSLCTests COMOutputHandle stderrHandle{}; auto id = container->Id(); VERIFY_ARE_EQUAL(container->Get().Attach(nullptr, &stdinHandle, &stdoutHandle, &stderrHandle), WSLC_E_CONTAINER_NOT_RUNNING); - ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id)); + ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", wsl::shared::string::MultiByteToWide(id))); // Start the container. VERIFY_SUCCEEDED(container->Get().Start(WSLCContainerStartFlagsAttach, nullptr, nullptr)); @@ -10435,7 +10439,7 @@ class WSLCTests stdoutHandle.Reset(); stderrHandle.Reset(); VERIFY_ARE_EQUAL(container->Get().Attach(nullptr, &stdinHandle, &stdoutHandle, &stderrHandle), WSLC_E_CONTAINER_NOT_RUNNING); - ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id)); + ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", wsl::shared::string::MultiByteToWide(id))); // Validate that attaching to a deleted container fails. VERIFY_SUCCEEDED(container->Get().Delete(WSLCDeleteFlagsNone)); @@ -10696,7 +10700,7 @@ class WSLCTests VERIFY_ARE_EQUAL(m_defaultSession->OpenContainer(name.c_str(), &container), E_INVALIDARG); VERIFY_IS_NULL(container.get()); - ValidateCOMErrorMessage(std::format(L"Invalid name: '{}'", name)); + ValidateCOMErrorMessage(std::format(L"Invalid name: '{}'", wsl::shared::string::MultiByteToWide(name))); }; expectInvalidArg("container with spaces"); @@ -10715,7 +10719,7 @@ class WSLCTests auto comError = wsl::windows::common::wslutil::GetCOMErrorInfo(); VERIFY_IS_TRUE(comError.has_value()); - VERIFY_ARE_EQUAL(comError->Message.get(), std::format(L"Invalid image: '{}'", name)); + VERIFY_ARE_EQUAL(comError->Message.get(), std::format(L"Invalid image: '{}'", wsl::shared::string::MultiByteToWide(name))); }; expectInvalidPull("?foo&bar/url\n:name"); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerAttachTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerAttachTests.cpp index 350113a162..5af4860d62 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerAttachTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerAttachTests.cpp @@ -53,8 +53,12 @@ class WSLCE2EContainerAttachTests VerifyContainerIsNotListed(WslcContainerName); const auto& prompt = ">"; - auto result = RunWslc(std::format( - L"container run -itd -e PS1={} --name {} {} bash --norc", prompt, WslcContainerName, DebianImage.NameAndTag())); + auto result = RunWslc( + std::format( + L"container run -itd -e PS1={} --name {} {} bash --norc", + wsl::shared::string::MultiByteToWide(prompt), + WslcContainerName, + DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = 0}); auto containerId = result.GetStdoutOneLine(); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index 39fd029997..a6e2f4bef8 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -91,7 +91,7 @@ class WSLCE2EContainerCreateTests auto session = OpenDefaultElevatedSession(); auto [registryContainer, registryAddress] = StartLocalRegistry(*session, "", "", 5000); - auto reference = std::format(L"{}/invalid-image:latest", registryAddress); + auto reference = std::format(L"{}/invalid-image:latest", wsl::shared::string::MultiByteToWide(registryAddress)); auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, reference)); @@ -521,8 +521,12 @@ class WSLCE2EContainerCreateTests VerifyContainerIsNotListed(WslcContainerName); const auto& prompt = ">"; - auto result = RunWslc(std::format( - L"container create -it -e PS1={} --name {} {} bash --norc", prompt, WslcContainerName, DebianImage.NameAndTag())); + auto result = RunWslc( + std::format( + L"container create -it -e PS1={} --name {} {} bash --norc", + wsl::shared::string::MultiByteToWide(prompt), + WslcContainerName, + DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = 0}); auto containerId = result.GetStdoutOneLine(); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerExecTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerExecTests.cpp index 0a16883100..c09374d511 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerExecTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerExecTests.cpp @@ -106,8 +106,12 @@ class WSLCE2EContainerExecTests VerifyContainerIsNotListed(WslcContainerName); const auto& prompt = ">"; - auto result = - RunWslc(std::format(L"container run -itd -e PS1={} --name {} {}", prompt, WslcContainerName, DebianImage.NameAndTag())); + auto result = RunWslc( + std::format( + L"container run -itd -e PS1={} --name {} {}", + wsl::shared::string::MultiByteToWide(prompt), + WslcContainerName, + DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = 0}); auto containerId = result.GetStdoutOneLine(); @@ -425,7 +429,9 @@ class WSLCE2EContainerExecTests auto inspect = InspectContainer(WslcContainerName); result = RunWslc(std::format(L"container exec {} echo should-fail", WslcContainerName)); - auto errorMessage = std::format(L"Container '{}' is not running.\r\nError code: WSLC_E_CONTAINER_NOT_RUNNING\r\n", inspect.Id); + auto errorMessage = std::format( + L"Container '{}' is not running.\r\nError code: WSLC_E_CONTAINER_NOT_RUNNING\r\n", + wsl::shared::string::MultiByteToWide(inspect.Id)); result.Verify({.Stderr = errorMessage, .ExitCode = 1}); } diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 74ac853a64..451a053e3a 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -675,7 +675,11 @@ class WSLCE2EContainerRunTests const auto& prompt = ">"; auto session = RunWslcInteractive( - std::format(L"container run -it -e PS1={} --name {} {} bash --norc", prompt, WslcContainerName, DebianImage.NameAndTag())); + std::format( + L"container run -it -e PS1={} --name {} {} bash --norc", + wsl::shared::string::MultiByteToWide(prompt), + WslcContainerName, + DebianImage.NameAndTag())); VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); // Ignore resize-repaint messages. Those are emitted when the the tty initial size is set, which can happen before or after we start running commands. diff --git a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp index a63969029d..c938b15bf4 100644 --- a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp @@ -575,7 +575,7 @@ class WSLCE2EGlobalTests private: std::wstring GetVersionMessage() const { - return std::format(L"wslc {}\r\n", WSL_PACKAGE_VERSION); + return std::format(L"wslc {}\r\n", STRING_TO_WIDE_STRING(WSL_PACKAGE_VERSION)); } }; } // namespace WSLCE2ETests diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp index d460aaa6fc..8af48b2520 100644 --- a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp +++ b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp @@ -97,7 +97,12 @@ namespace { std::filesystem::remove_all(storagePath, error); if (error) { - Log::Error(std::format(L"Failed to cleanup storage path {}: {}", storagePath.wstring(), error.message()).c_str()); + Log::Error( + std::format( + L"Failed to cleanup storage path {}: {}", + storagePath.wstring(), + wsl::shared::string::MultiByteToWide(error.message())) + .c_str()); } } } @@ -367,8 +372,8 @@ void EnsureImageContainersAreDeleted(const TestImage& image) auto nameAndTag = wsl::shared::string::WideToMultiByte(image.NameAndTag()); if (container.Image.find(nameAndTag) != std::string::npos) { - auto result = RunWslc(std::format(L"container remove --force {}", container.Id)); - result.Verify({.Stdout = std::format(L"{}\r\n", container.Id), .Stderr = L"", .ExitCode = 0}); + auto result = RunWslc(std::format(L"container remove --force {}", wsl::shared::string::MultiByteToWide(container.Id))); + result.Verify({.Stdout = std::format(L"{}\r\n", wsl::shared::string::MultiByteToWide(container.Id)), .Stderr = L"", .ExitCode = 0}); } } } diff --git a/test/windows/wslc/e2e/WSLCE2EImageDeleteTests.cpp b/test/windows/wslc/e2e/WSLCE2EImageDeleteTests.cpp index be49f238ba..166399ef03 100644 --- a/test/windows/wslc/e2e/WSLCE2EImageDeleteTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EImageDeleteTests.cpp @@ -102,8 +102,8 @@ class WSLCE2EImageDeleteTests L"conflict: unable to remove repository reference \"{}\" (must force) - container {} is using its referenced image " L"{}\r\nError code: ERROR_SHARING_VIOLATION\r\n", DebianImage.Name, - containerId, - imageId); + wsl::shared::string::MultiByteToWide(containerId), + wsl::shared::string::MultiByteToWide(imageId)); result.Verify({.Stdout = L"", .Stderr = errorMessage, .ExitCode = 1}); } diff --git a/tools/generateLocalizationHeader.ps1 b/tools/generateLocalizationHeader.ps1 index afb21b532f..8864617cec 100644 --- a/tools/generateLocalizationHeader.ps1 +++ b/tools/generateLocalizationHeader.ps1 @@ -108,7 +108,10 @@ function generateEntry { $ArgumentTypes = [string]::Join(', ', ((1..$ArgumentsCount) |% {"typename T$_"})) $ArgumentHeaders = [string]::Join(', ', ((1..$ArgumentsCount) |% {"const T$_& arg$_"})) - $Arguments = [string]::Join(', ', ((1..$ArgumentsCount) |% {"arg$_"})) + # Widen narrow arguments once into lifetime-extended locals so make_[w]format_args (which + # requires lvalues) can bind to them. LocalizationArg is identity on non-narrow types. + $LocalDecls = [string]::Join("`r`n ", ((1..$ArgumentsCount) |% {"auto&& _formatArg$_ = ::wsl::shared::string::WideFormatArg(arg$_);"})) + $Arguments = [string]::Join(', ', ((1..$ArgumentsCount) |% {"_formatArg$_"})) return ' /* Message: {4}*/ @@ -118,9 +121,10 @@ function generateEntry {5}; auto message = LookupString(strings, options); + {6} return std::vformat(message, ARGS({3})); }} - ' -f $Name, $ArgumentTypes, $ArgumentHeaders, $Arguments, $Content.split("`n")[0], $map + ' -f $Name, $ArgumentTypes, $ArgumentHeaders, $Arguments, $Content.split("`n")[0], $map, $LocalDecls } }