| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // This is a utility program for testing that Dart binary correctly handles |
| // situations when stdout and stderr handles are the same. |
| // |
| // This can happen in certain terminal emulators, e.g. one used by GitBash |
| // see https://github.com/dart-lang/sdk/issues/61981 for an example. |
| |
| #include "platform/globals.h" |
| #if defined(DART_HOST_OS_WINDOWS) |
| |
| #include <cstdio> |
| #include <source_location> |
| #include <sstream> |
| #include <vector> |
| |
| struct StdioHandles { |
| HANDLE in; |
| HANDLE out; |
| HANDLE err; |
| }; |
| |
| static void ReportErrorAndAbort( |
| std::source_location location = std::source_location::current()) { |
| const auto code = GetLastError(); |
| |
| wchar_t buffer[512]; |
| auto message_size = |
| FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
| nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| buffer, ARRAY_SIZE(buffer), nullptr); |
| if (message_size == 0) { |
| _snwprintf(buffer, ARRAY_SIZE(buffer), L"OS Error %d", code); |
| } |
| fprintf(stderr, "error at %s:%d: %ls", location.file_name(), location.line(), |
| buffer); |
| abort(); |
| } |
| |
| static void LaunchProcessWith(wchar_t* command_line, |
| const StdioHandles& stdio_handles) { |
| fprintf(stderr, "LAUNCHING %ls\n", command_line); |
| |
| // Setup info |
| STARTUPINFOEXW startup_info; |
| ZeroMemory(&startup_info, sizeof(startup_info)); |
| startup_info.StartupInfo.cb = sizeof(startup_info); |
| |
| // Setup the handles to inherit. We only want to inherit the three |
| // handles for stdin, stdout and stderr. |
| startup_info.StartupInfo.hStdInput = stdio_handles.in; |
| startup_info.StartupInfo.hStdOutput = stdio_handles.out; |
| startup_info.StartupInfo.hStdError = stdio_handles.err; |
| startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES; |
| SIZE_T size = 0; |
| // The call to determine the size of an attribute list always fails with |
| // ERROR_INSUFFICIENT_BUFFER and that error should be ignored. |
| if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &size) && |
| (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { |
| return ReportErrorAndAbort(); |
| } |
| auto attribute_list = |
| reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(size)); |
| ZeroMemory(attribute_list, size); |
| if (!InitializeProcThreadAttributeList(attribute_list, 1, 0, &size)) { |
| return ReportErrorAndAbort(); |
| } |
| std::vector<HANDLE> inherited_handles = {stdio_handles.in}; |
| if (stdio_handles.out != stdio_handles.in) { |
| inherited_handles.push_back(stdio_handles.out); |
| } |
| if (stdio_handles.err != stdio_handles.out && |
| stdio_handles.err != stdio_handles.in) { |
| inherited_handles.push_back(stdio_handles.err); |
| } |
| if (!UpdateProcThreadAttribute( |
| attribute_list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, |
| inherited_handles.data(), inherited_handles.size() * sizeof(HANDLE), |
| nullptr, nullptr)) { |
| return ReportErrorAndAbort(); |
| } |
| startup_info.lpAttributeList = attribute_list; |
| |
| PROCESS_INFORMATION process_info; |
| ZeroMemory(&process_info, sizeof(process_info)); |
| |
| // Create process. |
| BOOL result = CreateProcessW( |
| /*lpApplicationName=*/nullptr, command_line, |
| /*lpProcessAttributes=*/nullptr, |
| /*lpThreadAttributes=*/nullptr, |
| /*bInheritHandles=*/TRUE, |
| /*dwCreationFlags=*/EXTENDED_STARTUPINFO_PRESENT, |
| /*lpEnvironment=*/nullptr, |
| /*lpCurrentDirectory=*/nullptr, |
| reinterpret_cast<STARTUPINFOW*>(&startup_info), &process_info); |
| |
| if (result == 0) { |
| return ReportErrorAndAbort(); |
| } |
| |
| WaitForSingleObject(process_info.hProcess, INFINITE); |
| CloseHandle(process_info.hProcess); |
| CloseHandle(process_info.hThread); |
| } |
| |
| int main(int argc, char* argv[]) { |
| if (argc <= 1) { |
| fprintf(stderr, "Usage: %s <executable> <arg0> ... <argN>\n", argv[0]); |
| return -1; |
| } |
| |
| // Generate command line. Assume that it does not contain any white-space |
| // in the arguments. |
| std::wstringstream wstr; |
| for (int i = 1; i < argc; i++) { |
| wstr << (i > 1 ? " " : "") << argv[i]; |
| } |
| |
| HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE); |
| HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); |
| LaunchProcessWith( |
| wstr.str().data(), |
| {.in = stdin_handle, .out = stdout_handle, .err = stdout_handle}); |
| CloseHandle(stdin_handle); |
| CloseHandle(stdout_handle); |
| return 0; |
| } |
| |
| #else |
| |
| int main() { |
| return -1; |
| } |
| |
| #endif // defined(DART_HOST_OS_WINDOWS) |