From bd85943b62ea09f2b41dd54775f4eda288cef2a2 Mon Sep 17 00:00:00 2001 From: 1ndahous3 <9205230+1ndahous3@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:57:55 +0300 Subject: [PATCH] Default absent CreateProcess startup attributes (#9267) * Default missing CreateProcess startup attributes Assisted-by: Codex:GPT-6 * Use a single platform guard for winapi tests Assisted-by: Codex:GPT-6 --- crates/vm/src/stdlib/_winapi.rs | 33 +++++--- extra_tests/snippets/stdlib_winapi.py | 110 +++++++++++++++++++------- 2 files changed, 102 insertions(+), 41 deletions(-) diff --git a/crates/vm/src/stdlib/_winapi.rs b/crates/vm/src/stdlib/_winapi.rs index 6f495299a..5462ba7ca 100644 --- a/crates/vm/src/stdlib/_winapi.rs +++ b/crates/vm/src/stdlib/_winapi.rs @@ -246,20 +246,25 @@ mod _winapi { args: CreateProcessArgs, vm: &VirtualMachine, ) -> PyResult<(WinHandle, WinHandle, u32, u32)> { + // Default failed startup attribute lookups as CPython does. Keep value + // conversion errors separate from lookup failures. macro_rules! si_attr { ($attr:ident, $t:ty) => {{ - >::try_from_object( - vm, - args.startup_info.get_attr(stringify!($attr), vm)?, - )? - .unwrap_or(0) as _ + args.startup_info + .get_attr(stringify!($attr), vm) + .ok() + .map(|obj| >::try_from_object(vm, obj)) + .transpose()? + .flatten() + .unwrap_or(0) as _ }}; ($attr:ident) => {{ - >::try_from_object( - vm, - args.startup_info.get_attr(stringify!($attr), vm)?, - )? - .unwrap_or(0) + args.startup_info + .get_attr(stringify!($attr), vm) + .ok() + .map(|obj| TryFromObject::try_from_object(vm, obj)) + .transpose()? + .unwrap_or(0) }}; } let startup_info = host_winapi::StartupInfoData { @@ -275,7 +280,13 @@ mod _winapi { .map(|m| getenvironment(m, vm)) .transpose()?; - let handle_list = get_handle_list(args.startup_info.get_attr("lpAttributeList", vm)?, vm)?; + let handle_list = args + .startup_info + .get_attr("lpAttributeList", vm) + .ok() + .map(|obj| get_handle_list(obj, vm)) + .transpose()? + .flatten(); // Validate no embedded null bytes in command name and command line // before handing the strings off; to_wide_cstring rejects interior NULs. diff --git a/extra_tests/snippets/stdlib_winapi.py b/extra_tests/snippets/stdlib_winapi.py index ef1cad44c..31aca0762 100644 --- a/extra_tests/snippets/stdlib_winapi.py +++ b/extra_tests/snippets/stdlib_winapi.py @@ -4,36 +4,86 @@ import sys from testutils import assert_raises -if sys.platform == "win32": - import _winapi +if sys.platform != "win32": + sys.exit(0) - # Every call fails before a host operation. ReportEvent overflow cases - # also retain an embedded NUL as an independent safety barrier. - assert _winapi.EVENTLOG_SUCCESS == 0 - assert _winapi.EVENTLOG_ERROR_TYPE == 1 - assert _winapi.EVENTLOG_WARNING_TYPE == 2 - assert _winapi.EVENTLOG_INFORMATION_TYPE == 4 - assert _winapi.EVENTLOG_AUDIT_SUCCESS == 8 - assert _winapi.EVENTLOG_AUDIT_FAILURE == 16 +import _winapi - assert_raises(TypeError, _winapi.RegisterEventSource, 1, "source\0") - assert_raises(TypeError, _winapi.RegisterEventSource, None, 1) - assert_raises(TypeError, _winapi.RegisterEventSource, None, None) - assert_raises(ValueError, _winapi.RegisterEventSource, None, "source\0") - assert_raises(ValueError, _winapi.RegisterEventSource, "server\0", "source\0") - assert_raises(ValueError, _winapi.RegisterEventSource, "", "source\0") - assert_raises(TypeError, _winapi.DeregisterEventSource, "invalid handle") - assert_raises(TypeError, _winapi.DeregisterEventSource, None) - assert_raises( - TypeError, _winapi.ReportEvent, "invalid handle", 0, 0, 0, "message\0" - ) - assert_raises(ValueError, _winapi.ReportEvent, 0, 0, 0, 0, "message\0") - assert_raises(TypeError, _winapi.ReportEvent, 0, 0, 0, 0, None) - for index, bits in ((1, 16), (2, 16), (3, 32)): - for invalid, exception in ((-1, ValueError), (1 << bits, OverflowError)): - arguments = [0, 0, 0, 0, "message\0"] - arguments[index] = invalid - assert_raises(exception, _winapi.ReportEvent, *arguments) +# Every call fails before a host operation. ReportEvent overflow cases +# also retain an embedded NUL as an independent safety barrier. +assert _winapi.EVENTLOG_SUCCESS == 0 +assert _winapi.EVENTLOG_ERROR_TYPE == 1 +assert _winapi.EVENTLOG_WARNING_TYPE == 2 +assert _winapi.EVENTLOG_INFORMATION_TYPE == 4 +assert _winapi.EVENTLOG_AUDIT_SUCCESS == 8 +assert _winapi.EVENTLOG_AUDIT_FAILURE == 16 + +assert_raises(TypeError, _winapi.RegisterEventSource, 1, "source\0") +assert_raises(TypeError, _winapi.RegisterEventSource, None, 1) +assert_raises(TypeError, _winapi.RegisterEventSource, None, None) +assert_raises(ValueError, _winapi.RegisterEventSource, None, "source\0") +assert_raises(ValueError, _winapi.RegisterEventSource, "server\0", "source\0") +assert_raises(ValueError, _winapi.RegisterEventSource, "", "source\0") +assert_raises(TypeError, _winapi.DeregisterEventSource, "invalid handle") +assert_raises(TypeError, _winapi.DeregisterEventSource, None) +assert_raises(TypeError, _winapi.ReportEvent, "invalid handle", 0, 0, 0, "message\0") +assert_raises(ValueError, _winapi.ReportEvent, 0, 0, 0, 0, "message\0") +assert_raises(TypeError, _winapi.ReportEvent, 0, 0, 0, 0, None) +for index, bits in ((1, 16), (2, 16), (3, 32)): + for invalid, exception in ((-1, ValueError), (1 << bits, OverflowError)): arguments = [0, 0, 0, 0, "message\0"] - arguments[index] = "invalid integer" - assert_raises(TypeError, _winapi.ReportEvent, *arguments) + arguments[index] = invalid + assert_raises(exception, _winapi.ReportEvent, *arguments) + arguments = [0, 0, 0, 0, "message\0"] + arguments[index] = "invalid integer" + assert_raises(TypeError, _winapi.ReportEvent, *arguments) + + +import subprocess + + +class PartialStartupInfo: + dwFlags = 0 + + def __getattr__(self, name): + raise ValueError(name) + + +for startup in (None, PartialStartupInfo()): + process, thread, _, _ = _winapi.CreateProcess( + sys.executable, + subprocess.list2cmdline([sys.executable, "-c", "pass"]), + None, + None, + False, + _winapi.CREATE_NO_WINDOW, + None, + None, + startup, + ) + try: + status = _winapi.WaitForSingleObject(process, 10000) + if status != _winapi.WAIT_OBJECT_0: + _winapi.TerminateProcess(process, 1) + _winapi.WaitForSingleObject(process, 10000) + assert status == _winapi.WAIT_OBJECT_0 + assert _winapi.GetExitCodeProcess(process) == 0 + finally: + _winapi.CloseHandle(thread) + _winapi.CloseHandle(process) + +startup = subprocess.STARTUPINFO() +startup.dwFlags = "invalid" +assert_raises( + TypeError, + _winapi.CreateProcess, + None, + "invalid\0", + None, + None, + False, + 0, + None, + None, + startup, +)