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

GC.malloc resets WinError.value on MinGW-w64 #15496

Open
HertzDevil opened this issue Feb 21, 2025 · 0 comments
Open

GC.malloc resets WinError.value on MinGW-w64 #15496

HertzDevil opened this issue Feb 21, 2025 · 0 comments
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. platform:windows-gnu Windows support based on the MinGW-w64 toolchain + MSYS2 topic:stdlib:runtime

Comments

@HertzDevil
Copy link
Contributor

HertzDevil commented Feb 21, 2025

The following seemingly innocuous code:

WinError.value = WinError::ERROR_INVALID_PARAMETER
GC.malloc(1)
p WinError.value

prints WinError::ERROR_INVALID_PARAMETER for MSVC, but WinError::ERROR_SUCCESS for MinGW-w64. The reason is rather subtle:

  • Boehm GC on Windows uses different ways to access thread-local storage depending on whether a GNU compiler is used; it defines USE_WIN32_SPECIFIC or USE_WIN32_COMPILER_TLS for GNU and MSVC respectively.
  • For MSVC it uses __declspec(thread), and for GNU it uses the internal GC_getspecific, which is a macro for the Win32 TlsGetValue.
  • TlsGetValue is one of the few Win32 functions that always set a thread's last error, even if it succeeded.
  • This line is where GC_malloc or GC_malloc_atomic ultimately accesses TLS.

Few allocations look as straightforward as the code above. Here is one:

private def system_bind(addr, addrstr, &)
unless LibC.bind(fd, addr, addr.size) == 0
yield ::Socket::BindError.from_wsa_error("Could not bind to '#{addrstr}'")
end
end

WSAGetLastError and GetLastError are identical, except for their return types. Since this string interpolation allocates memory before the body of SystemError::ClassMethods#from_wsa_error calls WinError.wsa_value, the error message will always be "The operation completed successfully.".

One might argue that this is sensible behavior, in that Errno.value shares the same caveat with respect to C functions. But while functions in POSIX or the C standard library, including malloc, may write positive integers to errno regardless of whether a failure occurred, they never write 0. Indeed, if the top snippet prints neither ERROR_INVALID_PARAMETER nor ERROR_SUCCESS, then a genuine error might have happened somewhere else. ERROR_SUCCESS falls out of this natural expectation.

We could work around this by defining an appropriate Socket::Error.build_message, but I wonder if there is a better solution for the general case, other than preserving WinError.value on every single dynamic allocation.

@HertzDevil HertzDevil added kind:bug A bug in the code. Does not apply to documentation, specs, etc. platform:windows-gnu Windows support based on the MinGW-w64 toolchain + MSYS2 topic:stdlib:runtime labels Feb 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. platform:windows-gnu Windows support based on the MinGW-w64 toolchain + MSYS2 topic:stdlib:runtime
Projects
None yet
Development

No branches or pull requests

1 participant