Skip to content

Eliminate a race condition in NSError bridging #3975

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions stdlib/public/runtime/ErrorObject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,10 @@ - (NSInteger)code {
- (NSDictionary*)userInfo {
auto error = (const SwiftError*)self;
auto userInfo = error->userInfo.load(SWIFT_MEMORY_ORDER_CONSUME);

if (userInfo) {
// Don't need to .retain.autorelease since it's immutable.
return (NSDictionary*)userInfo;
} else {
// -[NSError userInfo] never returns nil on OS X 10.8 or later.
NSDictionary *emptyDict = SWIFT_LAZY_CONSTANT(@{});
return emptyDict;
}
assert(userInfo
&& "Error box used as NSError before initialization");
// Don't need to .retain.autorelease since it's immutable.
return (NSDictionary*)userInfo;
}

- (id)copyWithZone:(NSZone *)zone {
Expand Down Expand Up @@ -319,8 +314,11 @@ typedef SWIFT_CC(swift)
static id _swift_bridgeErrorToNSError_(SwiftError *errorObject) {
auto ns = reinterpret_cast<NSError *>(errorObject);

// If we already have a domain set, then we've already initialized.
if (errorObject->domain.load(SWIFT_MEMORY_ORDER_CONSUME))
// If we already have a domain and userInfo set, then we've already
// initialized.
// FIXME: This might be overly strict; can we look only at the domain?
if (errorObject->domain.load(std::memory_order_acquire) &&
errorObject->userInfo.load(std::memory_order_acquire))
return ns;

// Otherwise, calculate the domain and code (TODO: and user info), and
Expand All @@ -334,6 +332,10 @@ static id _swift_bridgeErrorToNSError_(SwiftError *errorObject) {
NSDictionary *userInfo =
swift_stdlib_getErrorUserInfoNSDictionary(value, type, witness);

// Never produce an empty userInfo dictionary.
if (!userInfo)
userInfo = SWIFT_LAZY_CONSTANT(@{});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we already do this somewhere else? We might want to factor this out into a static helper so there's only one global initializer for all empty dictionaries.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I eliminated the other place I know about, but I do want to revisit this because on modern platforms we don't need the dispatch_once at all for empty collection literals.


// The error code shouldn't change, so we can store it blindly, even if
// somebody beat us to it. The store can be relaxed, since we'll do a
// store(release) of the domain last thing to publish the initialized
Expand Down
1 change: 0 additions & 1 deletion validation-test/stdlib/ErrorProtocol.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// RUN: %target-run-simple-swift
// REQUIRES: executable_test
// REQUIRES: rdar27541751
// REQUIRES: objc_interop

import SwiftPrivate
Expand Down