Skip to content

Commit 5b7bdc3

Browse files
committed
Fixup Usage of pthread Mutexes on macOS
Did you know that pthread_mutex_init can _fail_? On macOS, the value of PTHREAD_MUTEX_INITIALIZER is not just zeroes - there's optional signature bits in there. POSIX says that you're allowed to check for those signature bits in the API and issue EINVAL if they don't match. By default, pthread_mutex_t() in swift will zero-fill through those signature bits, which results in an invalid mutex variable - even WRT pthread_mutex_init. Once that fails, all subsequent calls to lock and unlock will fail too and leave all of your critical sections completely unguarded. Thank you POSIX On Linuxes this is (often) not the case, and they tend to just use zeroes here, don't check the signature, or both. This allows compilers to allocate lock variables in .bss, which is kinda neat. So, on macOS, we need to install those signature bits, BUT Swift cannot import PTHREAD_MUTEX_INITIALIZER since it's a non-trivial macro that uses brace initialization. Really what we care about is just the signature bits, so we'll install those by hand.
1 parent 90aa7b5 commit 5b7bdc3

File tree

4 files changed

+39
-22
lines changed

4 files changed

+39
-22
lines changed

Sources/SwiftParser/Parser.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ extension Parser {
122122
/// tokens as needed to disambiguate a parse. However, because lookahead
123123
/// operates on a copy of the lexical stream, no input tokens are lost..
124124
public struct Parser: TokenConsumer {
125-
let arena: SyntaxArena
125+
@_spi(RawSyntax)
126+
public var arena: SyntaxArena
126127
/// A view of the sequence of lexemes in the input.
127128
var lexemes: Lexer.LexemeSequence
128129
/// The current token. If there was no input, this token will have a kind of `.eof`.

Sources/SwiftSyntax/PlatformMutex.swift

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#endif
2020

2121
/// A protocol that platform-specific mutual exclusion primitives should conform to.
22-
struct PlatformMutex {
22+
final class PlatformMutex {
2323
// FIXME: Use os_unfair_lock when we bump to macOS 12.0 on Darwin
2424
#if canImport(Darwin) || canImport(Glibc)
2525
typealias Primitive = pthread_mutex_t
@@ -36,12 +36,8 @@ struct PlatformMutex {
3636
init(allocator: BumpPtrAllocator) {
3737
let storage = allocator.allocate(Primitive.self, count: 1).baseAddress!
3838
storage.initialize(to: Primitive())
39-
#if canImport(Darwin) || canImport(Glibc)
40-
pthread_mutex_init(storage, nil)
41-
#elseif canImport(WinSDK)
42-
InitializeSRWLock(storage)
43-
#endif
4439
self.lock = storage
40+
Self.initialize(self.lock)
4541
}
4642

4743
/// Deinitialize the memory associated with the mutex.
@@ -64,17 +60,39 @@ struct PlatformMutex {
6460
}
6561

6662
extension PlatformMutex {
63+
private static func initialize(_ platformLock: PlatformLock) {
64+
#if canImport(Darwin)
65+
// HACK: On Darwin, the value of PTHREAD_MUTEX_INITIALIZER installs
66+
// signature bits into the mutex that are later checked by other aspects
67+
// of the mutex API. This is a completely optional POSIX-ism that most
68+
// Linuxes don't implement - often so that (global) lock variables can be
69+
// stuck in .bss. Swift doesn't know how to import
70+
// PTHREAD_MUTEX_INITIALIZER, so we'll replicate its signature-installing
71+
// magic with the bit it can import.
72+
platformLock.pointee.__sig = Int(_PTHREAD_MUTEX_SIG_init)
73+
let result = pthread_mutex_init(platformLock, nil)
74+
precondition(result == 0)
75+
#elseif canImport(Glibc)
76+
let result = pthread_mutex_init(platformLock, nil)
77+
precondition(result == 0)
78+
#elseif canImport(WinSDK)
79+
InitializeSRWLock(platformLock)
80+
#endif
81+
}
82+
6783
private static func lock(_ platformLock: PlatformLock) {
6884
#if canImport(Darwin) || canImport(Glibc)
69-
pthread_mutex_lock(platformLock)
85+
let result = pthread_mutex_lock(platformLock)
86+
assert(result == 0)
7087
#elseif canImport(WinSDK)
7188
AcquireSRWLockExclusive(platformLock)
7289
#endif
7390
}
7491

7592
private static func unlock(_ platformLock: PlatformLock) {
7693
#if canImport(Darwin) || canImport(Glibc)
77-
pthread_mutex_unlock(platformLock)
94+
let result = pthread_mutex_unlock(platformLock)
95+
assert(result == 0)
7896
#elseif canImport(WinSDK)
7997
ReleaseSRWLockExclusive(platformLock)
8098
#endif

Sources/SwiftSyntax/SyntaxArena.swift

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ public class SyntaxArena {
1515
@_spi(RawSyntax)
1616
public typealias ParseTriviaFunction = (_ source: SyntaxText, _ position: TriviaPosition) -> [RawTriviaPiece]
1717

18-
/// Thread safe guard.
19-
private let lock: PlatformMutex
20-
private var singleThreadMode: Bool
21-
2218
/// Bump-pointer allocator for all "intern" methods.
2319
private let allocator: BumpPtrAllocator
2420
/// Source file buffer the Syntax tree represents.
@@ -32,15 +28,18 @@ public class SyntaxArena {
3228
private var hasParent: Bool
3329
private var parseTriviaFunction: ParseTriviaFunction
3430

31+
/// Thread safe guard.
32+
private let lock: PlatformMutex
33+
private var singleThreadMode: Bool
34+
3535
@_spi(RawSyntax)
3636
public init(parseTriviaFunction: @escaping ParseTriviaFunction) {
37-
let allocator = BumpPtrAllocator()
38-
self.lock = PlatformMutex(allocator: allocator)
37+
self.allocator = BumpPtrAllocator()
38+
self.lock = PlatformMutex(allocator: self.allocator)
3939
self.singleThreadMode = false
40-
self.allocator = allocator
41-
children = []
42-
sourceBuffer = .init(start: nil, count: 0)
43-
hasParent = false
40+
self.children = []
41+
self.sourceBuffer = .init(start: nil, count: 0)
42+
self.hasParent = false
4443
self.parseTriviaFunction = parseTriviaFunction
4544
}
4645

Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
@_spi(RawSyntax)
2-
import SwiftSyntax
3-
import SwiftParser
1+
@_spi(RawSyntax) import SwiftSyntax
2+
@_spi(RawSyntax) import SwiftParser
43

54
/// An individual interpolated syntax node.
65
struct InterpolatedSyntaxNode {

0 commit comments

Comments
 (0)