Consider this:
Having a struct:
struct Foo {
let value: Int = 0
mutating func bar1() {}
mutating func bar2() {}
}
As we can see, there is no way any instance of Foo can be mutated, since these mutating functions don't actually mutate it.
Now, lets create a UnitTest and rund it with Thread Sanitizer enabled:
func testDataRace() {
let expect1 = self.expectation(description: "complete1")
let expect2 = self.expectation(description: "complete2")
var foo = Foo()
DispatchQueue.global().async {
foo.bar1()
expect1.fulfill()
}
DispatchQueue.global().async {
foo.bar2()
expect2.fulfill()
}
self.waitForExpectations(timeout: 0.1, handler: nil)
}
This will actually rise the following "Data Race" detection in Xcode 9.3 (see below). It reports that the starting address where the value `foo` is located has a data race. It does not say specifically which instance variable of the struct `foo`. It seems, it just makes guesses that the struct instance as a whole has been modified.
Furthermore, it seems, Thread Sanitizer is deducing a race condition solely on the `mutating` modifier of the function. If those will be called on different threads without synchronization (before they are called) it "detects" a race.
In all honesty, this is by far too insensitive: consider, we have a "real" struct where mutating functions actually do mutate the state of the instance, but the struct itself has defined a synchronization primitive (for example a pthread_mutex) which actually prevents a data race when any member function will be called. Then, Thread Sanitzer would issue false poitives, as above. This is not useful.
IMHO, Thread Sanitzer should perform a more sophisticated analysis - which perhaps selects candidates of functions based in their mutating modifier, but goes a step further where it actually checks which variable will be modified without synchronization.
So, in order to alleviate the poblem, are there any measurements to configure Thread Sanitizer so that it does not issue these kind of false positives?
WARNING: ThreadSanitizer: Swift access race (pid=28676)
Modifying access of Swift variable at 0x7b080000e070 by thread T3:
#0 closure #2 in FutureBaseTests.testDataRace() FutureBaseTests.swift:38 (RXFutureTests:x86_64+0x36d70)
#1 partial apply for closure #2 in FutureBaseTests.testDataRace() FutureBaseTests.swift (RXFutureTests:x86_64+0x36e5b)
#2 _T0Ieg_IeyB_TR FutureBasicContinuationsTests.swift (RXFutureTests:x86_64+0x1fd0)
#3 __tsan::invoke_and_release_block(void*) <null>:2136560 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x650ab)
#4 _dispatch_client_callout <null>:2136560 (libdispatch.dylib:x86_64+0x1e07)
Previous modifying access of Swift variable at 0x7b080000e070 by thread T5:
#0 closure #1 in FutureBaseTests.testDataRace() FutureBaseTests.swift:34 (RXFutureTests:x86_64+0x36b50)
#1 partial apply for closure #1 in FutureBaseTests.testDataRace() FutureBaseTests.swift (RXFutureTests:x86_64+0x36c3b)
#2 _T0Ieg_IeyB_TR FutureBasicContinuationsTests.swift (RXFutureTests:x86_64+0x1fd0)
#3 __tsan::invoke_and_release_block(void*) <null>:2136560 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x650ab)
#4 _dispatch_client_callout <null>:2136560 (libdispatch.dylib:x86_64+0x1e07)
Location is heap block of size 24 at 0x7b080000e060 allocated by main thread:
#0 malloc <null>:2136592 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x4998a)
#1 swift_slowAlloc <null>:2136592 (libswiftCore.dylib:x86_64+0x2d9928)
#2 FutureBaseTests.testDataRace() FutureBaseTests.swift (RXFutureTests:x86_64+0x3638d)
#3 @objc FutureBaseTests.testDataRace() FutureBaseTests.swift (RXFutureTests:x86_64+0x370b0)
#4 __invoking___ <null>:2136592 (CoreFoundation:x86_64h+0x82c3b)
Thread T3 (tid=1828145, running) is a GCD worker thread
Thread T5 (tid=1828158, running) is a GCD worker thread
SUMMARY: ThreadSanitizer: Swift access race FutureBaseTests.swift:38 in closure #2 in FutureBaseTests.testDataRace()
==================