blob: 774e641ee70a2eb10a40a13c6474de410db654f4 [file] [log] [blame]
// RUN: %empty-directory(%t)
// RUN: cd %t
// RUN: %target-build-swift %S/Inputs/tsan-uninstrumented.swift -target %sanitizers-target-triple -module-name TSanUninstrumented -emit-module -emit-module-path %t/TSanUninstrumented.swiftmodule -parse-as-library
// RUN: %target-build-swift %S/Inputs/tsan-uninstrumented.swift -target %sanitizers-target-triple -c -module-name TSanUninstrumented -parse-as-library -o %t/TSanUninstrumented.o
// RUN: %target-swiftc_driver %s %t/TSanUninstrumented.o -target %sanitizers-target-triple -I%t -L%t -g -sanitize=thread -o %t/tsan-binary
// RUN: not env %env-TSAN_OPTIONS=abort_on_error=0 %target-run %t/tsan-binary 2>&1 | %FileCheck %s
// RUN: not env %env-TSAN_OPTIONS=abort_on_error=0:ignore_interceptors_accesses=0 %target-run %t/tsan-binary 2>&1 | %FileCheck %s --check-prefix CHECK-INTERCEPTORS-ACCESSES
// REQUIRES: executable_test
// REQUIRES: stress_test
// REQUIRES: objc_interop
// REQUIRES: tsan_runtime
// Test ThreadSanitizer execution end-to-end when calling
// an uninstrumented module with inout parameters
import Darwin
import TSanUninstrumented
// Globals to allow closures passed to pthread_create() to be thin.
var gInThread1: () -> () = { }
var gInThread2: () -> () = { }
// Spawn two threads, run the two passed in closures simultaneously, and
// join them.
func testRace(name: String, thread inThread1: @escaping () -> (), thread inThread2: @escaping () -> ()) {
var thread1: pthread_t?
var thread2: pthread_t?
fputs("Running \(name)\n", stderr)
// Store these in globals so the closure passed to pthread_create
// can be turned into a C function pointer.
gInThread1 = inThread1
gInThread2 = inThread2
pthread_create(&thread1, nil, { _ in
gInThread1()
return nil
}, nil)
pthread_create(&thread2, nil, { _ in
gInThread2()
return nil
}, nil)
_ = pthread_join(thread1!, nil)
_ = pthread_join(thread2!, nil)
// TSan reports go to stderr
fputs("Done \(name)\n", stderr)
}
public class InstrumentedClass {
public init() { }
public var storedProperty1: Int = 7
public var storedProperty2: Int = 22
public var storedStructProperty: UninstrumentedStruct = UninstrumentedStruct()
private var _backingStoredProperty: Int = 7
public var computedPropertyBackedByStoredProperty: Int {
get {
return _backingStoredProperty
}
set(newVal) {
_backingStoredProperty = newVal;
}
}
}
// Tests for accesses to globals
// We use different globals for each test to avoid suppressions due
// to TSan's issue uniquing logic.
var globalForGlobalStructMutatingMethod = UninstrumentedStruct()
testRace(name: "GlobalStructMutatingMethod",
thread: { _ = globalForGlobalStructMutatingMethod.read() },
thread: { globalForGlobalStructMutatingMethod.mutate() } )
// CHECK-LABEL: Running GlobalStructMutatingMethod
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is global
var globalForGlobalStructDifferentStoredPropertiesInout = UninstrumentedStruct()
testRace(name: "GlobalStructDifferentStoredPropertiesInout",
thread: { uninstrumentedTakesInout(&globalForGlobalStructDifferentStoredPropertiesInout.storedProperty1) },
thread: { uninstrumentedTakesInout(&globalForGlobalStructDifferentStoredPropertiesInout.storedProperty2) } )
// CHECK-LABEL: Running GlobalStructDifferentStoredPropertiesInout
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is global
var globalForGlobalStructSameStoredPropertyInout = UninstrumentedStruct()
testRace(name: "GlobalStructSameStoredPropertyInout",
thread: { uninstrumentedTakesInout(&globalForGlobalStructSameStoredPropertyInout.storedProperty1) },
thread: { uninstrumentedTakesInout(&globalForGlobalStructSameStoredPropertyInout.storedProperty1) } )
// CHECK-LABEL: Running GlobalStructSameStoredPropertyInout
// CHECK: ThreadSanitizer: Swift access race
var globalForGlobalStructSubscriptDifferentIndexesInout = UninstrumentedStruct()
testRace(name: "GlobalStructSubscriptDifferentIndexesInout",
thread: { uninstrumentedTakesInout(&globalForGlobalStructSubscriptDifferentIndexesInout[0]) },
thread: { uninstrumentedTakesInout(&globalForGlobalStructSubscriptDifferentIndexesInout[1]) } )
// CHECK-LABEL: Running GlobalStructSubscriptDifferentIndexes
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is global
var globalForGlobalStructSubscriptDifferentIndexesGetSet = UninstrumentedStruct()
testRace(name: "GlobalStructSubscriptDifferentIndexesGetSet",
thread: { _ = globalForGlobalStructSubscriptDifferentIndexesGetSet[0] },
thread: { globalForGlobalStructSubscriptDifferentIndexesGetSet[1] = 12 } )
// CHECK-LABEL: Running GlobalStructSubscriptDifferentIndexesGetSet
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is global
var globalForGlobalClassGeneralMethods = UninstrumentedClass()
testRace(name: "GlobalClassGeneralMethods",
thread: { _ = globalForGlobalClassGeneralMethods.read() },
thread: { globalForGlobalClassGeneralMethods.mutate() } )
// CHECK-LABEL: Running GlobalClassGeneralMethods
// CHECK-NOT: ThreadSanitizer: {{.*}} race
var globalForGlobalClassDifferentStoredPropertiesInout = UninstrumentedClass()
testRace(name: "GlobalClassDifferentStoredPropertiesInout",
thread: { uninstrumentedTakesInout(&globalForGlobalClassDifferentStoredPropertiesInout.storedProperty1) },
thread: { uninstrumentedTakesInout(&globalForGlobalClassDifferentStoredPropertiesInout.storedProperty2) } )
// CHECK-LABEL: Running GlobalClassDifferentStoredPropertiesInout
// CHECK-NOT: ThreadSanitizer: {{.*}} race
var globalForGlobalClassSubscriptDifferentIndexesInout = UninstrumentedClass()
testRace(name: "GlobalClassSubscriptDifferentIndexesInout",
thread: { uninstrumentedTakesInout(&globalForGlobalClassSubscriptDifferentIndexesInout[0]) },
thread: { uninstrumentedTakesInout(&globalForGlobalClassSubscriptDifferentIndexesInout[1]) } )
// CHECK-LABEL: Running GlobalClassSubscriptDifferentIndexesInout
// CHECK-NOT: ThreadSanitizer: {{.*}} race
var globalForGlobalClassSameStoredPropertyInout = UninstrumentedClass()
testRace(name: "GlobalClassSameStoredPropertyInout",
thread: { uninstrumentedTakesInout(&globalForGlobalClassSameStoredPropertyInout.storedProperty1) },
thread: { uninstrumentedTakesInout(&globalForGlobalClassSameStoredPropertyInout.storedProperty1) } )
// CHECK-LABEL: Running GlobalClassSameStoredPropertyInout
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is heap block
// These access a global declared in the TSanUninstrumented module
testRace(name: "InoutAccessToStoredGlobalInUninstrumentedModule",
thread: { uninstrumentedTakesInout(&storedGlobalInUninstrumentedModule1) },
thread: { uninstrumentedTakesInout(&storedGlobalInUninstrumentedModule1) } )
// CHECK-LABEL: Running InoutAccessToStoredGlobalInUninstrumentedModule
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is global
// These access a global declared in the TSanUninstrumented module.
// This test requires ignore_interceptors_accesses=0 because
// the read from the global is IRGen'd to a memcpy().
testRace(name: "ReadAndWriteToStoredGlobalInUninstrumentedModule",
thread: { storedGlobalInUninstrumentedModule2 = 7 },
thread: { uninstrumentedBlackHole(storedGlobalInUninstrumentedModule2) } )
// CHECK-INTERCEPTORS-ACCESSES-LABEL: Running ReadAndWriteToStoredGlobalInUninstrumentedModule
// CHECK-INTERCEPTORS-ACCESSES: ThreadSanitizer: data race
// CHECK-INTERCEPTORS-ACCESSES: Location is global
// These access a computed global declared in the TSanUninstrumented module
testRace(name: "InoutAccessToComputedGlobalInUninstrumentedModule",
thread: { uninstrumentedTakesInout(&computedGlobalInUninstrumentedModule1) },
thread: { uninstrumentedTakesInout(&computedGlobalInUninstrumentedModule1) } )
// CHECK-LABEL: Running InoutAccessToComputedGlobalInUninstrumentedModule
// CHECK-NOT: ThreadSanitizer: {{.*}} race
// These access a computed global declared in the TSanUninstrumented module
testRace(name: "ReadAndWriteToComputedGlobalInUninstrumentedModule",
thread: { computedGlobalInUninstrumentedModule2 = 7 },
thread: { _ = computedGlobalInUninstrumentedModule2 } )
// CHECK-LABEL: Running ReadAndWriteToComputedGlobalInUninstrumentedModule
// CHECK-NOT: ThreadSanitizer: {{.*}} race
// Tests for accesses to stored class properties
var globalForGlobalUninstrumentedClassStoredPropertyMutatingMethod = UninstrumentedClass()
testRace(name: "GlobalUninstrumentedClassStoredPropertyMutatingMethod",
thread: { _ = globalForGlobalUninstrumentedClassStoredPropertyMutatingMethod.storedStructProperty.read() },
thread: { globalForGlobalUninstrumentedClassStoredPropertyMutatingMethod.storedStructProperty.mutate() } )
// CHECK-LABEL: Running GlobalUninstrumentedClassStoredPropertyMutatingMethod
// CHECK-NOT: ThreadSanitizer: {{.*}} race
// Note: TSan doesn't see a race above because it doesn't see any load on the
// read side because the getter for the class property is not instrumented.
var globalForGlobalUninstrumentedClassStoredPropertyInout = UninstrumentedClass()
testRace(name: "GlobalUninstrumentedClassStoredPropertyInout",
thread: { uninstrumentedTakesInout(&globalForGlobalUninstrumentedClassStoredPropertyInout.storedStructProperty.storedProperty1) },
thread: { uninstrumentedTakesInout(&globalForGlobalUninstrumentedClassStoredPropertyInout.storedStructProperty.storedProperty2) } )
// CHECK-LABEL: Running GlobalUninstrumentedClassStoredPropertyInout
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is heap block
// Note: TSan sees the race above because the inout instrumentation adds an
// ''access'' at the call site to the address returned from materializeForSet
var globalForGlobalUninstrumentedClassComputedPropertyInout = UninstrumentedClass()
testRace(name: "GlobalUninstrumentedClassComputedPropertyInout",
thread: { uninstrumentedTakesInout(&globalForGlobalUninstrumentedClassComputedPropertyInout.computedStructProperty.storedProperty1) },
thread: { uninstrumentedTakesInout(&globalForGlobalUninstrumentedClassComputedPropertyInout.computedStructProperty.storedProperty1) } )
// CHECK-LABEL: Running GlobalUninstrumentedClassComputedPropertyInout
// CHECK-NOT: ThreadSanitizer: {{.*}} race
// In the above the write in instrumented code is to the value buffer allocated
// at the call site so there is no data race if the getter and setters themselves
// are synchronized/don't access shared storage. Even with synchronized accessors,
// there is still the possibility of a race condition with lost updates with
// some interleavings of the calls to the getters and setters -- but no data race.
var globalForGlobalInstrumentedClassStoredPropertyMutatingMethod = InstrumentedClass()
testRace(name: "GlobalInstrumentedClassStoredPropertyMutatingMethod",
thread: { _ = globalForGlobalInstrumentedClassStoredPropertyMutatingMethod.storedStructProperty.read() },
thread: { globalForGlobalInstrumentedClassStoredPropertyMutatingMethod.storedStructProperty.mutate() } )
// CHECK-LABEL: Running GlobalInstrumentedClassStoredPropertyMutatingMethod
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is heap block
//
// TSan does see this above race because the getter and materializeForSet is instrumented
var globalForGlobalInstrumentedComputedBackedProperty = InstrumentedClass()
testRace(name: "GlobalInstrumentedComputedBackedProperty",
thread: { _ = globalForGlobalInstrumentedComputedBackedProperty.computedPropertyBackedByStoredProperty },
thread: { globalForGlobalInstrumentedComputedBackedProperty.computedPropertyBackedByStoredProperty = 77 } )
// CHECK-LABEL: Running GlobalInstrumentedComputedBackedProperty
// CHECK: ThreadSanitizer: data race
// CHECK: Location is heap block
//
// TSan does see this above race because the getter and setter are instrumented
// and write to a shared heap location.
func runLocalTests() {
runCapturedLocalStructMutatingMethod()
runCapturedLocalStructDifferentStoredPropertiesInout()
runCapturedLocalClassGeneralMethods()
runCapturedLocalDifferentStoredPropertiesInout()
runCapturedLocalSameStoredPropertyInout()
}
func runCapturedLocalStructMutatingMethod() {
var l = UninstrumentedStruct()
testRace(name: "CapturedLocalStructMutatingMethod",
thread: { _ = l.read() },
thread: { l.mutate() } )
}
// CHECK-LABEL: Running CapturedLocalStructMutatingMethod
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is heap block
func runCapturedLocalStructDifferentStoredPropertiesInout() {
var l = UninstrumentedStruct()
testRace(name: "CapturedLocalStructDifferentStoredPropertiesInout",
thread: { uninstrumentedTakesInout(&l.storedProperty1) },
thread: { uninstrumentedTakesInout(&l.storedProperty2) } )
}
// CHECK-LABEL: Running CapturedLocalStructDifferentStoredPropertiesInout
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is heap block
func runCapturedLocalClassGeneralMethods() {
let l = UninstrumentedClass()
testRace(name: "CapturedLocalClassGeneralMethods",
thread: { _ = l.read() },
thread: { l.mutate() } )
}
// CHECK-LABEL: Running CapturedLocalClassGeneralMethods
// CHECK-NOT: ThreadSanitizer: {{.*}} race
func runCapturedLocalDifferentStoredPropertiesInout() {
let l = UninstrumentedClass()
testRace(name: "CapturedLocalClassDifferentStoredPropertiesInout",
thread: { uninstrumentedTakesInout(&l.storedProperty1) },
thread: { uninstrumentedTakesInout(&l.storedProperty2) } )
}
// CHECK-LABEL: Running CapturedLocalClassDifferentStoredPropertiesInout
// CHECK-NOT: ThreadSanitizer: {{.*}} race
func runCapturedLocalSameStoredPropertyInout() {
let l = UninstrumentedClass()
testRace(name: "CapturedLocalClassSameStoredPropertyInout",
thread: { uninstrumentedTakesInout(&l.storedProperty1) },
thread: { uninstrumentedTakesInout(&l.storedProperty1) } )
}
// CHECK-LABEL: Running CapturedLocalClassSameStoredPropertyInout
// CHECK: ThreadSanitizer: Swift access race
// CHECK: Location is heap block
runLocalTests()