[swift-evolution] [Pitch] Make `errno`-setting functions more usable from Swift
Zach Waldowski
zach at waldowski.me
Wed Nov 2 09:47:39 CDT 2016
I could see the "swift_error" Clang attribute being extended with an
"errno" case (in the same vein as "nonzero_result" and "zero_result"),
which is then reflected into Swift via the importer. Although, having an
attribute for methods set errno seems like it would be useful for the
Clang Analyzer overall.
Cheers!
Zach
On Wed, Nov 2, 2016, at 09:12 AM, Johannes Weiß via swift-evolution
wrote:
> Hey swift-evolution,
>
> First of all apologies, this is not a full proposal yet, it's meant to
> kick off a discussion on how to resolve the issue.
>
> # Make `errno`-setting functions more usable from Swift
>
> ## Introduction
>
> This is a pitch to make [`errno`][1]-setting functions properly usable,
> as in having a guarantee to get the correct `errno` value on failure of a
> [system call][2]. Currently, functions which set `errno` are just
> exported in the Darwin/Glibc modules with (as far as I understand) no
> guaranteed correct way of handling errors as the correct `errno` value
> can't be retrieved.
> This means that much of the Swift code which uses Darwin/Glibc out there
> relies on behaviour that isn't guaranteed.
>
>
> ## Motivation
>
> In many Swift libraries that use the Darwin/Glibc modules there is code
> similar to:
>
> ```
> /* import Darwin/Glibc */
>
> let rv = some_system_call(some, parameters)
> if rv < 0 {
> throw SomeError(errorCode: errno) /* <-- errno use */
> }
> ```
>
> That looks very innocent but please note that `errno` is used here. And
> `errno` is an interesting one as it's a thread-local variable which is
> written to by many functions. A thread-local variable is like a global
> variable except that setting it in one thread does not affect its value
> in any other thread. Pretty much all system calls and many library
> functions set `errno` if something went wrong.
>
> The problem is that as far as I see (and Swift developers have
> confirmed), there is no guarantee that in between the call of
> `some_system_call` and the reading of `errno`, `errno` hasn't been
> overwritten by some other system call that has been call on the same
> thread.
>
> To illustrate this further, let's consider this example
>
> ```
> /* import Darwin/Glibc */
> public class SomeClass {
> public let someValue: Int = 1
> deinit {
> /* call some failing syscall, for example */
> write(-1, nil, 0) /* should set errno to EBADF */
> }
> }
>
> public func foo() {
> let x = SomeClass()
> let rv = write(x.someValue, nil, 0)
> let errnoSave = errno
> if rv != 0 {
> throw SomeError(errorCode: errnoSave)
> }
> }
> ```
>
> as you see in function `foo`, the instance `x` of `SomeClass` isn't
> needed anymore as soon as `write` has been called. So (as far as I
> understand) there's no guarantee that ARC doesn't turn the above code
> into
>
> ```
> let x = SomeClass()
> let rv = write(x.someValue, nil, 42) /* should set errno to EFAULT */
> /* ARC generated */ x.release()
> let errnoSave = errno /* wrong errno value :( */
> if rv != 0 {
> throw SomeError(errorCode: errnoSave)
> }
> ```
>
> And the ARC generated `x.release()` will cause `x` to be deallocated
> which will call the failing `write` in the `deinit` of `SomeClass`. So
> `errnoSave` might be `EBADF` instead of `EFAULT` depending on where ARC
> put the `x.release()` call.
>
> What `errno` value we read will depend on the optimisation settings and
> the Swift compiler version. That's IMHO a big issue as it might make the
> lowest layers unstable with hard-to-debug issues.
>
>
> ## Proposed solution
>
> I don't have a full story on how to actually resolve the issue but I see
> a few options:
>
> ### Option 1: always return errno
>
> clang importer could be changed to make all `errno`-setting functions
> return a tuple of the actual return value and the `errno` value.
>
> For example, currently write(2) is imported as:
>
> ```
> public func write(_ __fd: Int32, _ __buf: UnsafeRawPointer!, _ __nbyte:
> Int) -> Int
> ```
>
> which could be changed to
>
> ```
> public func write(_ __fd: Int32, _ __buf: UnsafeRawPointer!, _ __nbyte:
> Int) -> (Int, Int32 /* for errno */)
> ```
>
> Correct code to use write would then look like this:
>
> ```
> let (bytesWritten, writeErrno) = write(fd, buf, len)
> if bytesWritten >= 0 {
> /* everything's fine */
> } else {
> throw POSIXError(code: writeErrno)
> }
> ```
>
>
> ### Option 2: make them throw
>
> The second option is to teach clang importer to make the functions
> throwing. So write(2) would be imported as
>
> ```
> public func write(_ __fd: Int32, _ __buf: UnsafeRawPointer!, _ __nbyte:
> Int) throws /* POSIXError */ -> Int
> ```
>
> That would make these functions quite easy to use and would feel natural
> in Swift:
>
> ```
> do {
> let bytesWritten = write(fd, buf, len)
> } catch let e as POSIXError {
> /* handle error */
> } catch {
> ...
> }
> ```
>
>
> ### Discussion
>
> The beauty of option 1 is simplicity. Clang importer would not need to
> know what exact values a system call returns on failure. Also very little
> additional code needs to be emitted for calling a system call. That seems
> to be the [way Go is going][3].
>
> The downside of option 1 is that the API doesn't feel like idiomatic
> Swift. The returned `errno` value is only useful if the system call
> failed and is arbitrary in the case when it worked. (There is no
> guarantee that `errno` is set to `0` when a system call succeeds.)
> Also there is a slight overhead in reading `errno` which would be paid
> for every `errno`-setting function, even if successful. Hence, option 2
> looks nice as it brings these functions more in like with other Swift
> functions. However, as mentioned before, clang importer would need to
> learn what values are returned on success/failure for _every_
> `errno`-setting function (and there's _many_ of them).
>
>
> ## Proposed Approach
>
> Let's discuss what is a good solution and then I will volunteer to put
> together a full proposal.
>
>
> ## Source compatibility
>
> it depends.
>
> To retain source compatibility the Darwin/Glibc modules could also be
> left as is. The safe `errno` handling could then be implemented only in a
> new, unified module for Darwin/Glibc. There's already ongoing
> discussions/proposals about that on the list anyway. That new module
> could then be implemented in the spirit of options 1, 2, or some other
> solution. The benefits are guaranteed source compatibility for legacy
> applications and `errno` safety plus easier imports for new applications
> - win/win 🙂.
>
>
> ## Effect on ABI stability
>
> Will most likely be additive, so probably none.
>
>
> ## Effect on API resilience
>
> see source compatibility.
>
>
> ## Alternatives considered
>
> Do nothing and workaround `errno` capturing being very hard. I discussed
> this previously elsewhere and Joe Groff came up with the code below which
> should convince the optimiser not to insert any release calls at the
> wrong place or inline the function:
>
> ```
> @inline(never)
> func callWithErrno(_ fn: () -> Int) -> (result: Int, errno: Int) {
> var result: Int
> var savedErrno: Int
> withExtendedLifetime(fn) {
> result = fn()
> savedErrno = errno
> }
> return (result, savedErrno)
> }
> ```
>
> An example use of that is
>
> ```
> let (rv, writeErrno) = callWithErrno {
> write(-1, nil, 0)
> }
>
> if rv < 0 {
> throw SomeError(errorCode: writeErrno)
> }
> ```
>
> This makes it possible to retrieve the correct `errno` value in Swift but
> I think there remain too many ways to do it wrongly. First and foremost
> that the compiler doesn't complain if the programmer forgets to use
> `callWithErrno`.
>
> ===
>
> Let me know what you think!
>
> [1]: https://en.wikipedia.org/wiki/Errno.h
> [2]: https://en.wikipedia.org/wiki/System_call
> [3]: https://golang.org/pkg/syscall/#Write
>
> Many thanks,
> Johannes
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
More information about the swift-evolution
mailing list