-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathtsan-inout.swift
313 lines (258 loc) · 13.9 KB
/
tsan-inout.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// RUN: %empty-directory(%t)
// RUN: cd %t
// RUN: %target-build-swift %S/Inputs/tsan-uninstrumented.swift -module-name TSanUninstrumented -emit-module -emit-module-path %t/TSanUninstrumented.swiftmodule -parse-as-library
// RUN: %target-build-swift %S/Inputs/tsan-uninstrumented.swift -c -module-name TSanUninstrumented -parse-as-library -o %t/TSanUninstrumented.o
// RUN: %target-swiftc_driver %s %t/TSanUninstrumented.o -I%t -L%t -g -sanitize=thread %import-libdispatch -o %t/tsan-binary
// RUN: not env %env-TSAN_OPTIONS=abort_on_error=0 %target-run %t/tsan-binary 2>&1 | %FileCheck %s
// REQUIRES: executable_test
// REQUIRES: stress_test
// REQUIRES: tsan_runtime
// Test ThreadSanitizer execution end-to-end when calling
// an uninstrumented module with inout parameters
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#else
#error("Unsupported platform")
#endif
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 () -> ()) {
#if canImport(Darwin)
var thread1: pthread_t?
var thread2: pthread_t?
#else
var thread1: pthread_t = 0
var thread2: pthread_t = 0
var t : pthread_t = 0
#endif
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)
#if canImport(Darwin)
_ = pthread_join(thread1!, nil)
_ = pthread_join(thread2!, nil)
#else
_ = pthread_join(thread1, nil)
_ = pthread_join(thread2, nil)
#endif
// 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.
testRace(name: "ReadAndWriteToStoredGlobalInUninstrumentedModule",
thread: { storedGlobalInUninstrumentedModule2 = 7 },
thread: { uninstrumentedBlackHole(storedGlobalInUninstrumentedModule2) } )
// CHECK-LABEL: Running ReadAndWriteToStoredGlobalInUninstrumentedModule
// CHECK: ThreadSanitizer: data race
// CHECK: 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()