Files
swift-mirror/test/SILGen/tsan_instrumentation.swift
Devin Coughlin 38503da505 [TSan] Don't instrument inout accesses on known-empty types
Empty types (such as structs without stored properties) have a
meaningless value for their address. To avoid crashes in the Thread
Sanitizer runtime, rather than passing this unspecified value as
the address of the inout access, skip emission of the runtime call.

The bug allowing unspecified behavior here has been present since we
first added TSan support for checking Swift access races -- but codegen
changes on arm64 have recently made crashes due to the bug much more
likely.

rdar://problem/47686212
2021-02-02 21:22:04 -08:00

117 lines
4.8 KiB
Swift

// REQUIRES: tsan_runtime
// RUN: %target-swift-emit-silgen -sanitize=thread %s | %FileCheck %s
// RUN: %target-swift-frontend -sanitize=thread -emit-ir -primary-file %s | %FileCheck --check-prefix=CHECK-LLVM-IR %s
// TSan is only supported on 64 bit.
// REQUIRES: PTRSIZE=64
func takesInout(_ p: inout Int) { }
func takesInout(_ p: inout MyStruct) { }
struct MyStruct {
var storedProperty: Int = 77
}
class MyClass {
var storedProperty: Int = 22
}
var gStruct = MyStruct()
var gClass = MyClass()
// CHECK-LABEL: sil hidden [ossa] @$s20tsan_instrumentation17inoutGlobalStructyyF : $@convention(thin) () -> () {
// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s20tsan_instrumentation7gStructAA02MyC0Vvp : $*MyStruct
// CHECK: [[WRITE:%.*]] = begin_access [modify] [dynamic] [[GLOBAL_ADDR]] : $*MyStruct
// CHECK: {{%.*}} = builtin "tsanInoutAccess"([[WRITE]] : $*MyStruct) : $()
// CHECK: [[TAKES_INOUT_FUNC:%.*]] = function_ref @$s20tsan_instrumentation10takesInoutyyAA8MyStructVzF : $@convention(thin) (@inout MyStruct) -> ()
// CHECK: {{%.*}} = apply [[TAKES_INOUT_FUNC]]([[WRITE]]) : $@convention(thin) (@inout MyStruct) -> ()
func inoutGlobalStruct() {
takesInout(&gStruct)
}
// CHECK-LABEL: sil hidden [ossa] @$s20tsan_instrumentation31inoutGlobalStructStoredPropertyyyF : $@convention(thin) () -> () {
// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s20tsan_instrumentation7gStructAA02MyC0Vvp : $*MyStruct
// CHECK: [[WRITE:%.*]] = begin_access [modify] [dynamic] [[GLOBAL_ADDR]] : $*MyStruct
// CHECK: {{%.*}} = builtin "tsanInoutAccess"([[WRITE]] : $*MyStruct) : $()
// CHECK: [[ELEMENT_ADDR:%.*]] = struct_element_addr [[WRITE]] : $*MyStruct, #MyStruct.storedProperty
// CHECK: {{%.*}} = builtin "tsanInoutAccess"([[ELEMENT_ADDR]] : $*Int) : $()
// CHECK: [[TAKES_INOUT_FUNC:%.*]] = function_ref @$s20tsan_instrumentation10takesInoutyySizF : $@convention(thin) (@inout Int) -> ()
// CHECK: {{%.*}} = apply [[TAKES_INOUT_FUNC]]([[ELEMENT_ADDR]]) : $@convention(thin) (@inout Int) -> ()
func inoutGlobalStructStoredProperty() {
// This should generate two TSan inout instrumentations; one for the address
// of the global and one for the address of the struct stored property.
takesInout(&gStruct.storedProperty)
}
// CHECK-LABEL: sil hidden [ossa] @$s20tsan_instrumentation30inoutGlobalClassStoredPropertyyyF : $@convention(thin) () -> () {
// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s20tsan_instrumentation6gClassAA02MyC0Cvp : $*MyClass
// CHECK: [[READ:%.*]] = begin_access [read] [dynamic] [[GLOBAL_ADDR]] : $*MyClass
// CHECK: [[LOADED_CLASS:%.*]] = load [copy] [[READ]] : $*MyClass
// CHECK: end_access [[READ]]
// CHECK: [[BORROWED_CLASS:%.*]] = begin_borrow [[LOADED_CLASS]]
// CHECK: [[MODIFY:%.*]] = class_method [[BORROWED_CLASS]] : $MyClass, #MyClass.storedProperty!modify :
// CHECK: ([[BUFFER_ADDRESS:%.*]], [[TOKEN:%.*]]) = begin_apply [[MODIFY]]([[BORROWED_CLASS]]) : $@yield_once @convention(method) (@guaranteed MyClass) -> @yields @inout Int
// CHECK: {{%.*}} = builtin "tsanInoutAccess"([[BUFFER_ADDRESS]] : $*Int) : $()
// CHECK: [[TAKES_INOUT_FUNC:%.*]] = function_ref @$s20tsan_instrumentation10takesInoutyySizF : $@convention(thin) (@inout Int) -> ()
// CHECK: {{%.*}} apply [[TAKES_INOUT_FUNC]]([[BUFFER_ADDRESS]]) : $@convention(thin) (@inout Int) -> ()
// CHECK: end_apply [[TOKEN]]
// CHECK: destroy_value [[LOADED_CLASS]]
func inoutGlobalClassStoredProperty() {
// This generates two TSan inout instrumentations. One for the value
// buffer that is passed inout to materializeForSet and one for the
// temporary buffer passed to takesInout().
takesInout(&gClass.storedProperty)
}
// Known-empty types don't have storage, so there is no address
// to pass to the TSan runtime to check for data races on inout accesses.
// In this case, the instrumentation should skip the call to
// __tsan_external_write()
struct ZeroSizedStruct {
mutating
func mutate() { }
}
struct NonEmptyStruct {
var f: Int = 5
mutating
func mutate() { }
}
// CHECK-LLVM-IR-LABEL: testNoInstrumentZeroSizedStruct
func testNoInstrumentZeroSizedStruct() {
var s = ZeroSizedStruct()
// CHECK-LLVM-IR-NOT: tsan_external_write
s.mutate()
}
func takesInout<T>(_ p: inout T) { }
// CHECK-LLVM-IR-LABEL: testNoInstrumentEmptyTuple
func testNoInstrumentEmptyTuple() {
var t: Void = ()
// CHECK-LLVM-IR-NOT: tsan_external_write
takesInout(&t)
}
// CHECK-LLVM-IR-LABEL: testNoInstrumentMutateInoutZeroSizedStruct
func testNoInstrumentMutateInoutZeroSizedStruct(p: inout ZeroSizedStruct) {
// CHECK-LLVM-IR-NOT: tsan_external_write
p.mutate()
}
// CHECK-LLVM-IR-LABEL: testInstrumentNonEmptyStruct
func testInstrumentNonEmptyStruct() {
// Make sure we actually instrument accesses to non-empty structs.
var s = NonEmptyStruct()
// CHECK-LLVM-IR: tsan_external_write
s.mutate()
}