Skip to content

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

Closed
@HertzDevil

Description

@HertzDevil

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind:bugA bug in the code. Does not apply to documentation, specs, etc.platform:windows-gnuWindows support based on the MinGW-w64 toolchain + MSYS2topic:stdlib:runtime

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions