SILGen: Add experimental TSan instrumentation for inout accesses. (#7736)

Add SILGen instrumentation to treat inout accesses as Thread Sanitizer writes.
The goal is to catch races on inout accesses even when there is a not an
llvm-level read/write to a particular address. Ultimately
this will enable TSan to, for example, report racy writes to distinct
stored properties of a common struct as a data race.

This instrumentation is off by default. It can be enabled with the
'enable-experimental-tsan-inout-instrumentation' frontend flag.

The high-level approach is to add a SIL-level builtin that represents a call
to a TSan routine in compiler-rt. Then, when emitting an address for an LValue
as part of an inout expression, we call this builtin for each path component
that represents an LValue. I've added an 'isRValue()' method to PathComponent
that tracks whether a component represents an RValue or an LValue. Right the
only PathComponent that sometimes returns 'true' is ValueComponent().

For now, we're instrumenting only InoutExprs, but in the future it probably
makes sense to instrument all LValue accesses. In this patch I've
added a 'TSanKind' parameter to SILGenFunction::emitAddressOfLValue() and
its helpers to limit instrumentation to inout accesses. I envision that this
parameter will eventually go away.
This commit is contained in:
Devin Coughlin
2017-03-16 17:08:48 -07:00
committed by GitHub
parent a8e4e72270
commit 52d5178a3e
11 changed files with 521 additions and 18 deletions

View File

@@ -162,6 +162,10 @@ namespace swift {
/// \brief Enable experimental property behavior feature. /// \brief Enable experimental property behavior feature.
bool EnableExperimentalPropertyBehaviors = false; bool EnableExperimentalPropertyBehaviors = false;
/// \brief Staging flag for treating inout parameters as Thread Sanitizer
/// accesses.
bool EnableTSANInoutInstrumentation = false;
/// \brief Staging flag for class resilience, which we do not want to enable /// \brief Staging flag for class resilience, which we do not want to enable
/// fully until more code is in place, to allow the standard library to be /// fully until more code is in place, to allow the standard library to be
/// tested with value type resilience only. /// tested with value type resilience only.

View File

@@ -267,6 +267,10 @@ def disable_availability_checking : Flag<["-"],
"disable-availability-checking">, "disable-availability-checking">,
HelpText<"Disable checking for potentially unavailable APIs">; HelpText<"Disable checking for potentially unavailable APIs">;
def enable_experimental_tsan_inout_instrumentation : Flag<["-"],
"enable-experimental-tsan-inout-instrumentation">,
HelpText<"Enable treatment of inout parameters as Thread Sanitizer accesses">;
def enable_infer_import_as_member : def enable_infer_import_as_member :
Flag<["-"], "enable-infer-import-as-member">, Flag<["-"], "enable-infer-import-as-member">,
HelpText<"Infer when a global could be imported as a member">; HelpText<"Infer when a global could be imported as a member">;

View File

@@ -847,6 +847,10 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
Opts.DisableAvailabilityChecking |= Opts.DisableAvailabilityChecking |=
Args.hasArg(OPT_disable_availability_checking); Args.hasArg(OPT_disable_availability_checking);
Opts.EnableTSANInoutInstrumentation |=
Args.hasArg(OPT_enable_experimental_tsan_inout_instrumentation);
if (FrontendOpts.InputKind == InputFileKind::IFK_SIL) if (FrontendOpts.InputKind == InputFileKind::IFK_SIL)
Opts.DisableAvailabilityChecking = true; Opts.DisableAvailabilityChecking = true;

View File

@@ -156,7 +156,9 @@ public:
/// base value. /// base value.
virtual AccessKind getBaseAccessKind(SILGenFunction &SGF, virtual AccessKind getBaseAccessKind(SILGenFunction &SGF,
AccessKind accessKind) const = 0; AccessKind accessKind) const = 0;
virtual bool isRValue() const { return false; }
/// Returns the logical type-as-rvalue of the value addressed by the /// Returns the logical type-as-rvalue of the value addressed by the
/// component. This is always an object type, never an address. /// component. This is always an object type, never an address.
SILType getTypeOfRValue() const { return TypeData.TypeOfRValue; } SILType getTypeOfRValue() const { return TypeData.TypeOfRValue; }

View File

@@ -1987,7 +1987,8 @@ static void beginInOutFormalAccesses(SILGenFunction &SGF,
LValue &inoutArg = inoutNext->first; LValue &inoutArg = inoutNext->first;
SILLocation loc = inoutNext->second; SILLocation loc = inoutNext->second;
ManagedValue address = SGF.emitAddressOfLValue(loc, std::move(inoutArg), ManagedValue address = SGF.emitAddressOfLValue(loc, std::move(inoutArg),
AccessKind::ReadWrite); AccessKind::ReadWrite,
TSanKind::InoutAccess);
siteArg = address; siteArg = address;
emittedInoutArgs.push_back({address.getValue(), loc}); emittedInoutArgs.push_back({address.getValue(), loc});

View File

@@ -201,6 +201,16 @@ enum class CaptureEmission {
PartialApplication, PartialApplication,
}; };
/// Parameter to \c SILGenFunction::emitAddressOfLValue that indicates
/// what kind of instrumentation should be emitted when compiling under
/// Thread Sanitizer.
enum class TSanKind : bool {
None = 0,
/// Instrument the LValue access as an inout access.
InoutAccess
};
/// Represents an LValue opened for mutating access. /// Represents an LValue opened for mutating access.
/// ///
/// This is used by LogicalPathComponent::getMaterialized() and /// This is used by LogicalPathComponent::getMaterialized() and
@@ -1238,7 +1248,9 @@ public:
void emitCopyLValueInto(SILLocation loc, LValue &&src, void emitCopyLValueInto(SILLocation loc, LValue &&src,
Initialization *dest); Initialization *dest);
ManagedValue emitAddressOfLValue(SILLocation loc, LValue &&src, ManagedValue emitAddressOfLValue(SILLocation loc, LValue &&src,
AccessKind accessKind); AccessKind accessKind,
TSanKind tsanKind = TSanKind::None);
RValue emitLoadOfLValue(SILLocation loc, LValue &&src, SGFContext C, RValue emitLoadOfLValue(SILLocation loc, LValue &&src, SGFContext C,
bool isGuaranteedValid = false); bool isGuaranteedValid = false);

View File

@@ -454,10 +454,14 @@ namespace {
/// A physical path component which returns a literal address. /// A physical path component which returns a literal address.
class ValueComponent : public PhysicalPathComponent { class ValueComponent : public PhysicalPathComponent {
ManagedValue Value; ManagedValue Value;
bool IsRValue;
public: public:
ValueComponent(ManagedValue value, LValueTypeData typeData) : ValueComponent(ManagedValue value, LValueTypeData typeData,
bool isRValue = false) :
PhysicalPathComponent(typeData, ValueKind), PhysicalPathComponent(typeData, ValueKind),
Value(value) { Value(value),
IsRValue(isRValue) {
assert(IsRValue || value.getType().isAddress());
} }
ManagedValue offset(SILGenFunction &SGF, SILLocation loc, ManagedValue base, ManagedValue offset(SILGenFunction &SGF, SILLocation loc, ManagedValue base,
@@ -466,6 +470,10 @@ namespace {
return Value; return Value;
} }
bool isRValue() const override {
return IsRValue;
}
void print(raw_ostream &OS) const override { void print(raw_ostream &OS) const override {
OS << "ValueComponent()\n"; OS << "ValueComponent()\n";
} }
@@ -1351,7 +1359,7 @@ LValue LValue::forValue(ManagedValue value,
value.getValue()); value.getValue());
LValue lv; LValue lv;
lv.add<ValueComponent>(value, typeData); lv.add<ValueComponent>(value, typeData, /*isRValue=*/true);
return lv; return lv;
} }
@@ -1514,7 +1522,7 @@ LValue SILGenLValue::visitRec(Expr *e, AccessKind accessKind) {
CanType formalType = getSubstFormalRValueType(e); CanType formalType = getSubstFormalRValueType(e);
auto typeData = getValueTypeData(formalType, rv.getValue()); auto typeData = getValueTypeData(formalType, rv.getValue());
LValue lv; LValue lv;
lv.add<ValueComponent>(rv, typeData); lv.add<ValueComponent>(rv, typeData, /*isRValue=*/true);
return lv; return lv;
} }
@@ -1974,7 +1982,7 @@ LValue SILGenFunction::emitPropertyLValue(SILLocation loc, ManagedValue base,
base.getValue()); base.getValue());
// Refer to 'self' as the base of the lvalue. // Refer to 'self' as the base of the lvalue.
lv.add<ValueComponent>(base, baseTypeData); lv.add<ValueComponent>(base, baseTypeData, /*isRValue=*/!base.isLValue());
auto substFormalType = ivar->getInterfaceType().subst(subMap) auto substFormalType = ivar->getInterfaceType().subst(subMap)
->getCanonicalType(); ->getCanonicalType();
@@ -2396,13 +2404,23 @@ SILValue SILGenFunction::emitConversionFromSemanticValue(SILLocation loc,
llvm_unreachable("unexpected storage type that differs from type-of-rvalue"); llvm_unreachable("unexpected storage type that differs from type-of-rvalue");
} }
static void emitTsanInoutAccess(SILGenFunction &SGF, SILLocation loc,
ManagedValue address) {
assert(address.getType().isAddress());
SILValue accessFnArgs[] = {address.getValue()};
SGF.B.createBuiltin(loc, SGF.getASTContext().getIdentifier("tsanInoutAccess"),
SGF.SGM.Types.getEmptyTupleType(), {}, accessFnArgs);
}
/// Produce a physical address that corresponds to the given l-value /// Produce a physical address that corresponds to the given l-value
/// component. /// component.
static ManagedValue drillIntoComponent(SILGenFunction &SGF, static ManagedValue drillIntoComponent(SILGenFunction &SGF,
SILLocation loc, SILLocation loc,
PathComponent &&component, PathComponent &&component,
ManagedValue base, ManagedValue base,
AccessKind accessKind) { AccessKind accessKind,
TSanKind tsanKind) {
ManagedValue addr; ManagedValue addr;
if (component.isPhysical()) { if (component.isPhysical()) {
addr = std::move(component.asPhysical()).offset(SGF, loc, base, accessKind); addr = std::move(component.asPhysical()).offset(SGF, loc, base, accessKind);
@@ -2411,16 +2429,23 @@ static ManagedValue drillIntoComponent(SILGenFunction &SGF,
addr = std::move(lcomponent).getMaterialized(SGF, loc, base, accessKind); addr = std::move(lcomponent).getMaterialized(SGF, loc, base, accessKind);
} }
if (SGF.getASTContext().LangOpts.EnableTSANInoutInstrumentation &&
tsanKind == TSanKind::InoutAccess && !component.isRValue()) {
emitTsanInoutAccess(SGF, loc, addr);
}
return addr; return addr;
} }
/// Find the last component of the given lvalue and derive a base /// Find the last component of the given lvalue and derive a base
/// location for it. /// location for it.
static PathComponent &&drillToLastComponent(SILGenFunction &SGF, static PathComponent &&
SILLocation loc, drillToLastComponent(SILGenFunction &SGF,
LValue &&lv, SILLocation loc,
ManagedValue &addr, LValue &&lv,
AccessKind accessKind) { ManagedValue &addr,
AccessKind accessKind,
TSanKind tsanKind = TSanKind::None) {
assert(lv.begin() != lv.end() && assert(lv.begin() != lv.end() &&
"lvalue must have at least one component"); "lvalue must have at least one component");
@@ -2432,7 +2457,8 @@ static PathComponent &&drillToLastComponent(SILGenFunction &SGF,
} }
for (auto i = lv.begin(), e = lv.end() - 1; i != e; ++i) { for (auto i = lv.begin(), e = lv.end() - 1; i != e; ++i) {
addr = drillIntoComponent(SGF, loc, std::move(**i), addr, accessKind); addr = drillIntoComponent(SGF, loc, std::move(**i), addr, accessKind,
tsanKind);
accessKind = pathAccessKinds.pop_back_val(); accessKind = pathAccessKinds.pop_back_val();
} }
@@ -2464,11 +2490,15 @@ RValue SILGenFunction::emitLoadOfLValue(SILLocation loc, LValue &&src,
ManagedValue SILGenFunction::emitAddressOfLValue(SILLocation loc, ManagedValue SILGenFunction::emitAddressOfLValue(SILLocation loc,
LValue &&src, LValue &&src,
AccessKind accessKind) { AccessKind accessKind,
TSanKind tsanKind) {
ManagedValue addr; ManagedValue addr;
PathComponent &&component = PathComponent &&component =
drillToLastComponent(*this, loc, std::move(src), addr, accessKind); drillToLastComponent(*this, loc, std::move(src), addr, accessKind,
addr = drillIntoComponent(*this, loc, std::move(component), addr, accessKind); tsanKind);
addr = drillIntoComponent(*this, loc, std::move(component), addr, accessKind,
tsanKind);
assert(addr.getType().isAddress() && assert(addr.getType().isAddress() &&
"resolving lvalue did not give an address"); "resolving lvalue did not give an address");
return ManagedValue::forLValue(addr.getValue()); return ManagedValue::forLValue(addr.getValue());

View File

@@ -594,6 +594,21 @@ void ElementUseCollector::collectContainerUses(AllocBoxInst *ABI) {
} }
} }
/// Returns true when the instruction represents added instrumentation for
/// run-time sanitizers.
static bool isSanitizerInstrumentation(SILInstruction *Instruction,
ASTContext &Ctx) {
auto *BI = dyn_cast<BuiltinInst>(Instruction);
if (!BI)
return false;
Identifier Name = BI->getName();
if (Name == Ctx.getIdentifier("tsanInoutAccess"))
return true;
return false;
}
void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) { void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) {
assert(Pointer->getType().isAddress() && assert(Pointer->getType().isAddress() &&
"Walked through the pointer to the value?"); "Walked through the pointer to the value?");
@@ -832,6 +847,11 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) {
continue; continue;
} }
// Sanitizer instrumentation is not user visible, so it should not
// count as a use and must not affect compile-time diagnostics.
if (isSanitizerInstrumentation(User, Module.getASTContext()))
continue;
// Otherwise, the use is something complicated, it escapes. // Otherwise, the use is something complicated, it escapes.
addElementUses(BaseEltNo, PointeeType, User, DIUseKind::Escape); addElementUses(BaseEltNo, PointeeType, User, DIUseKind::Escape);
} }

View File

@@ -0,0 +1,66 @@
// RUN: %target-swift-frontend -Xllvm -new-mangling-for-tests -sanitize=thread -enable-experimental-tsan-inout-instrumentation -emit-silgen %s | %FileCheck %s
// REQUIRES: tsan_runtime
// XFAIL: linux
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 @_T020tsan_instrumentation17inoutGlobalStructyyF : $@convention(thin) () -> () {
// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @_T020tsan_instrumentation7gStructAA02MyC0Vv : $*MyStruct
// CHECK: [[TAKES_INOUT_FUNC:%.*]] = function_ref @_T020tsan_instrumentation10takesInoutyAA8MyStructVzF : $@convention(thin) (@inout MyStruct) -> ()
// CHECK: {{%.*}} = builtin "tsanInoutAccess"([[GLOBAL_ADDR]] : $*MyStruct) : $()
// CHECK: {{%.*}} = apply [[TAKES_INOUT_FUNC]]([[GLOBAL_ADDR]]) : $@convention(thin) (@inout MyStruct) -> ()
func inoutGlobalStruct() {
takesInout(&gStruct)
}
// CHECK-LABEL: sil hidden @_T020tsan_instrumentation31inoutGlobalStructStoredPropertyyyF : $@convention(thin) () -> () {
// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @_T020tsan_instrumentation7gStructAA02MyC0Vv : $*MyStruct
// CHECK: [[TAKES_INOUT_FUNC:%.*]] = function_ref @_T020tsan_instrumentation10takesInoutySizF : $@convention(thin) (@inout Int) -> ()
// CHECK: {{%.*}} = builtin "tsanInoutAccess"([[GLOBAL_ADDR]] : $*MyStruct) : $()
// CHECK: [[ELEMENT_ADDR:%.*]] = struct_element_addr [[GLOBAL_ADDR]] : $*MyStruct, #MyStruct.storedProperty
// CHECK: {{%.*}} = builtin "tsanInoutAccess"([[ELEMENT_ADDR]] : $*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 @_T020tsan_instrumentation30inoutGlobalClassStoredPropertyyyF : $@convention(thin) () -> () {
// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @_T020tsan_instrumentation6gClassAA02MyC0Cv : $*MyClass
// CHECK: [[TAKES_INOUT_FUNC:%.*]] = function_ref @_T020tsan_instrumentation10takesInoutySizF : $@convention(thin) (@inout Int) -> ()
// CHECK: [[LOADED_CLASS:%.*]] = load [copy] [[GLOBAL_ADDR]] : $*MyClass
// CHECK: [[VALUE_BUFFER:%.*]] = alloc_stack $Builtin.UnsafeValueBuffer
// CHECK: [[TEMPORARY:%.*]] = alloc_stack $Int
// CHECK: [[BORROWED_CLASS:%.*]] = begin_borrow [[LOADED_CLASS]] : $MyClass
// CHECK: [[TEMPORARY_RAW:%.*]] = address_to_pointer [[TEMPORARY]] : $*Int to $Builtin.RawPointer
// CHECK: [[MATERIALIZE_FOR_SET:%.*]] = class_method [[BORROWED_CLASS]] : $MyClass, #MyClass.storedProperty!materializeForSet.1 : (MyClass) -> (Builtin.RawPointer, inout Builtin.UnsafeValueBuffer) -> (Builtin.RawPointer, Builtin.RawPointer?), $@convention(method) (Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @guaranteed MyClass) -> (Builtin.RawPointer, Optional<Builtin.RawPointer>)
// CHECK: {{%.*}} = builtin "tsanInoutAccess"([[VALUE_BUFFER]] : $*Builtin.UnsafeValueBuffer) : $()
// CHECK: [[MATERIALIZE_FOR_SET_TUPLE:%.*]] = apply [[MATERIALIZE_FOR_SET]]([[TEMPORARY_RAW]], [[VALUE_BUFFER]], [[BORROWED_CLASS]]) : $@convention(method) (Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @guaranteed MyClass) -> (Builtin.RawPointer, Optional<Builtin.RawPointer>)
// CHECK: [[TEMPORARY_BUFFER:%.*]] = tuple_extract [[MATERIALIZE_FOR_SET_TUPLE]] : $(Builtin.RawPointer, Optional<Builtin.RawPointer>), 0
// CHECK: [[OPTIONAL_CALLBACK:%.*]] = tuple_extract [[MATERIALIZE_FOR_SET_TUPLE]] : $(Builtin.RawPointer, Optional<Builtin.RawPointer>), 1
// CHECK: [[BUFFER_ADDRESS:%.*]] = pointer_to_address [[TEMPORARY_BUFFER]] : $Builtin.RawPointer to [strict] $*Int
// CHECK: [[BUFFER_ADDRESS_DEPENDENCE:%.*]] = mark_dependence [[BUFFER_ADDRESS]] : $*Int on [[LOADED_CLASS]] : $MyClass
// CHECK: end_borrow [[BORROWED_CLASS]] from [[LOADED_CLASS]] : $MyClass, $MyClass
// CHECK: {{%.*}} builtin "tsanInoutAccess"([[BUFFER_ADDRESS_DEPENDENCE]] : $*Int) : $()
// CHECK: {{%.*}} apply [[TAKES_INOUT_FUNC]]([[BUFFER_ADDRESS_DEPENDENCE]]) : $@convention(thin) (@inout Int) -> ()
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)
}

View File

@@ -0,0 +1,62 @@
// This is uninstrumented code used for testing calls into uninstrumented
// modules.
public struct UninstrumentedStruct {
public init() { }
public func read() -> Int {
return 0
}
public mutating func mutate() { }
public var storedProperty1: Int = 7
public var storedProperty2: Int = 22
public subscript(index: Int) -> Int {
get { return 0 }
set(newValue) { }
}
public var storedClass: UninstrumentedClass? = nil
}
public class UninstrumentedClass {
public init() { }
public func read() -> Int {
return 0
}
public func mutate() { }
public var storedProperty1: Int = 7
public var storedProperty2: Int = 22
public subscript(index: Int) -> Int {
get { return 0 }
set(newValue) { }
}
public var storedStructProperty: UninstrumentedStruct = UninstrumentedStruct()
public var computedStructProperty: UninstrumentedStruct {
get { return UninstrumentedStruct() }
set { }
}
}
public func uninstrumentedTakesInout(_ i: inout Int) { }
public var storedGlobalInUninstrumentedModule1: Int = 7
public var storedGlobalInUninstrumentedModule2: Int = 88
public var computedGlobalInUninstrumentedModule1: Int {
get { return 0 }
set { }
}
public var computedGlobalInUninstrumentedModule2: Int {
get { return 0 }
set { }
}

View File

@@ -0,0 +1,298 @@
// 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 -Xfrontend -enable-experimental-tsan-inout-instrumentation %s %T/TSanUninstrumented.o -I%T -L%T -g -sanitize=thread -o %t_tsan-binary
// RUN: not env TSAN_OPTIONS=abort_on_error=0 %target-run %t_tsan-binary 2>&1 | %FileCheck %s
// REQUIRES: executable_test
// REQUIRES: objc_interop
// REQUIRES: CPU=x86_64
// REQUIRES: tsan_runtime
// XFAIL: linux
// 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 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?
print("Running \(name)")
fflush(stdout)
// 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
fflush(stderr)
print("Done \(name)")
}
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: data 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: data 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: data race
var globalForGlobalStructSubscriptDifferentIndexesInout = UninstrumentedStruct()
testRace(name: "GlobalStructSubscriptDifferentIndexesInout",
thread: { uninstrumentedTakesInout(&globalForGlobalStructSubscriptDifferentIndexesInout[0]) },
thread: { uninstrumentedTakesInout(&globalForGlobalStructSubscriptDifferentIndexesInout[1]) } )
// CHECK-LABEL: Running GlobalStructSubscriptDifferentIndexes
// CHECK: ThreadSanitizer: data race
// CHECK: Location is global
var globalForGlobalStructSubscriptDifferentIndexesGetSet = UninstrumentedStruct()
testRace(name: "GlobalStructSubscriptDifferentIndexesGetSet",
thread: { _ = globalForGlobalStructSubscriptDifferentIndexesGetSet[0] },
thread: { globalForGlobalStructSubscriptDifferentIndexesGetSet[1] = 12 } )
// CHECK-LABEL: Running GlobalStructSubscriptDifferentIndexesGetSet
// CHECK: ThreadSanitizer: data race
// CHECK: Location is global
var globalForGlobalClassGeneralMethods = UninstrumentedClass()
testRace(name: "GlobalClassGeneralMethods",
thread: { _ = globalForGlobalClassGeneralMethods.read() },
thread: { globalForGlobalClassGeneralMethods.mutate() } )
// CHECK-LABEL: Running GlobalClassGeneralMethods
// CHECK-NOT: ThreadSanitizer: data race
var globalForGlobalClassDifferentStoredPropertiesInout = UninstrumentedClass()
testRace(name: "GlobalClassDifferentStoredPropertiesInout",
thread: { uninstrumentedTakesInout(&globalForGlobalClassDifferentStoredPropertiesInout.storedProperty1) },
thread: { uninstrumentedTakesInout(&globalForGlobalClassDifferentStoredPropertiesInout.storedProperty2) } )
// CHECK-LABEL: Running GlobalClassDifferentStoredPropertiesInout
// CHECK-NOT: ThreadSanitizer: data race
var globalForGlobalClassSubscriptDifferentIndexesInout = UninstrumentedClass()
testRace(name: "GlobalClassSubscriptDifferentIndexesInout",
thread: { uninstrumentedTakesInout(&globalForGlobalClassSubscriptDifferentIndexesInout[0]) },
thread: { uninstrumentedTakesInout(&globalForGlobalClassSubscriptDifferentIndexesInout[1]) } )
// CHECK-LABEL: Running GlobalClassSubscriptDifferentIndexesInout
// CHECK-NOT: ThreadSanitizer: data race
var globalForGlobalClassSameStoredPropertyInout = UninstrumentedClass()
testRace(name: "GlobalClassSameStoredPropertyInout",
thread: { uninstrumentedTakesInout(&globalForGlobalClassSameStoredPropertyInout.storedProperty1) },
thread: { uninstrumentedTakesInout(&globalForGlobalClassSameStoredPropertyInout.storedProperty1) } )
// CHECK-LABEL: Running GlobalClassSameStoredPropertyInout
// CHECK: ThreadSanitizer: data 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: data race
// CHECK: Location is global
// These access a global declared in the TSanUninstrumented module
testRace(name: "ReadAndWriteToStoredGlobalInUninstrumentedModule",
thread: { storedGlobalInUninstrumentedModule2 = 7 },
thread: { _ = 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: data 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: data 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: data 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: data 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-NO: ThreadSanitizer: data 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: data 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: data 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: data 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: data race
func runCapturedLocalDifferentStoredPropertiesInout() {
let l = UninstrumentedClass()
testRace(name: "CapturedLocalClassDifferentStoredPropertiesInout",
thread: { uninstrumentedTakesInout(&l.storedProperty1) },
thread: { uninstrumentedTakesInout(&l.storedProperty2) } )
}
// CHECK-LABEL: Running CapturedLocalClassDifferentStoredPropertiesInout
// CHECK-NOT: ThreadSanitizer: data race
func runCapturedLocalSameStoredPropertyInout() {
let l = UninstrumentedClass()
testRace(name: "CapturedLocalClassSameStoredPropertyInout",
thread: { uninstrumentedTakesInout(&l.storedProperty1) },
thread: { uninstrumentedTakesInout(&l.storedProperty1) } )
}
// CHECK-LABEL: Running CapturedLocalClassSameStoredPropertyInout
// CHECK: ThreadSanitizer: data race
// CHECK: Location is heap block
runLocalTests()