// RUN: %empty-directory(%t) // RUN: %target-build-swift -swift-version 4 %s -o %t/a.out -enforce-exclusivity=checked -Onone // // RUN: %target-codesign %t/a.out // RUN: %target-run %t/a.out // REQUIRES: executable_test // REQUIRES: thread_safe_runtime // UNSUPPORTED: use_os_stdlib // Tests for traps at run time when enforcing exclusive access. import StdlibUnittest import SwiftPrivateThreadExtras struct X { var i = 7 } /// Calling this function will begin a read access to the variable referred to /// in the first parameter that lasts for the duration of the call. Any /// accesses in the closure will therefore be nested inside the outer read. func readAndPerform(_ _: UnsafePointer, closure: () ->()) { closure() } /// Begin a modify access to the first parameter and call the closure inside it. func modifyAndPerform(_ _: UnsafeMutablePointer, closure: () ->()) { closure() } /// Begin a modify access to the first parameter and call the escaping closure inside it. func modifyAndPerformEscaping(_ _: UnsafeMutablePointer, closure: () ->()) { closure() } var globalX = X() var ExclusiveAccessTestSuite = TestSuite("ExclusiveAccess") ExclusiveAccessTestSuite.test("Read") { let l = globalX // no-trap _blackHole(l) } // It is safe for a read access to overlap with a read. ExclusiveAccessTestSuite.test("ReadInsideRead") { readAndPerform(&globalX) { let l = globalX // no-trap _blackHole(l) } } ExclusiveAccessTestSuite.test("ModifyInsideRead") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a read) started at") .crashOutputMatches("Current access (a modification) started at") .code { readAndPerform(&globalX) { expectCrashLater() globalX = X() } } ExclusiveAccessTestSuite.test("ReadInsideModify") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a read) started at") .code { modifyAndPerform(&globalX) { expectCrashLater() let l = globalX _blackHole(l) } } ExclusiveAccessTestSuite.test("ModifyInsideModify") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a modification) started at") .code { modifyAndPerform(&globalX) { expectCrashLater() globalX.i = 12 } } var globalOtherX = X() // It is safe for two modifications of different variables // to overlap. ExclusiveAccessTestSuite.test("ModifyInsideModifyOfOther") { modifyAndPerform(&globalOtherX) { globalX.i = 12 // no-trap } } // The access durations for these two modifications do not overlap ExclusiveAccessTestSuite.test("ModifyFollowedByModify") { globalX = X() _blackHole(()) globalX = X() // no-trap } ExclusiveAccessTestSuite.test("ClosureCaptureReadRead") { var x = X() readAndPerform(&x) { _blackHole(x.i) // no-trap } } // Test for per-thread enforcement. Don't trap when two different threads // have overlapping accesses ExclusiveAccessTestSuite.test("PerThreadEnforcement") { modifyAndPerform(&globalX) { let (_, otherThread) = _stdlib_thread_create_block({ (_ : Void) -> () in globalX.i = 12 // no-trap return () }, ()) _ = _stdlib_thread_join(otherThread!, Void.self) } } // Helpers func doOne(_ f: () -> ()) { f() } func doTwo(_ f1: ()->(), _ f2: ()->()) { f1(); f2() } // No crash. ExclusiveAccessTestSuite.test("WriteNoescapeWrite") { var x = 3 let c = { x = 7 } // Inside may-escape closure `c`: [read] [dynamic] // Inside never-escape closure: [modify] [dynamic] doTwo(c, { x = 42 }) _blackHole(x) } // No crash. ExclusiveAccessTestSuite.test("InoutReadEscapeRead") { var x = 3 let c = { let y = x; _blackHole(y) } readAndPerform(&x, closure: c) _blackHole(x) } ExclusiveAccessTestSuite.test("InoutReadEscapeWrite") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a read) started at") .crashOutputMatches("Current access (a modification) started at") .code { var x = 3 let c = { x = 42 } expectCrashLater() readAndPerform(&x, closure: c) _blackHole(x) } ExclusiveAccessTestSuite.test("InoutWriteEscapeRead") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a read) started at") .code { var x = 3 let c = { let y = x; _blackHole(y) } expectCrashLater() modifyAndPerform(&x, closure: c) _blackHole(x) } ExclusiveAccessTestSuite.test("InoutWriteEscapeWrite") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a modification) started at") .code { var x = 3 let c = { x = 42 } expectCrashLater() modifyAndPerform(&x, closure: c) _blackHole(x) } // No crash. ExclusiveAccessTestSuite.test("InoutReadNoescapeRead") { var x = 3 let c = { let y = x; _blackHole(y) } doOne { readAndPerform(&x, closure: c) } } ExclusiveAccessTestSuite.test("InoutReadNoescapeWrite") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a read) started at") .crashOutputMatches("Current access (a modification) started at") .code { var x = 3 let c = { x = 7 } expectCrashLater() doOne { readAndPerform(&x, closure: c) } } ExclusiveAccessTestSuite.test("InoutWriteEscapeReadClosure") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a read) started at") .code { var x = 3 let c = { let y = x; _blackHole(y) } expectCrashLater() doOne { modifyAndPerform(&x, closure: c) } } ExclusiveAccessTestSuite.test("InoutWriteEscapeWriteClosure") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a modification) started at") .code { var x = 3 let c = { x = 7 } expectCrashLater() doOne { modifyAndPerform(&x, closure: c) } } class ClassWithStoredProperty { final var f = 7 } ExclusiveAccessTestSuite.test("KeyPathInoutDirectWriteClassStoredProp") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a modification) started at") .code { let getF = \ClassWithStoredProperty.f let c = ClassWithStoredProperty() expectCrashLater() modifyAndPerform(&c[keyPath: getF]) { c.f = 12 } } ExclusiveAccessTestSuite.test("KeyPathInoutDirectReadClassStoredProp") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a read) started at") .code { let getF = \ClassWithStoredProperty.f let c = ClassWithStoredProperty() expectCrashLater() modifyAndPerform(&c[keyPath: getF]) { let x = c.f _blackHole(x) } } // Unlike inout accesses, read-only inout-to-pointer conversions on key paths for // final stored-properties do not perform a long-term read access. Instead, they // materialize a location on the stack, perform an instantaneous read // from the storage indicated by the key path and write the read value to the // stack location. The stack location is then passed as the pointer for the // inout-to-pointer conversion. // // This means that there is no conflict between a read-only inout-to-pointer // conversion of the key path location for a call and an access to the // the same location within the call. ExclusiveAccessTestSuite.test("KeyPathReadOnlyInoutToPtrDirectWriteClassStoredProp") { let getF = \ClassWithStoredProperty.f let c = ClassWithStoredProperty() // This performs an instantaneous read readAndPerform(&c[keyPath: getF]) { c.f = 12 // no-trap } } ExclusiveAccessTestSuite.test("SequentialKeyPathWritesDontOverlap") { let getF = \ClassWithStoredProperty.f let c = ClassWithStoredProperty() c[keyPath: getF] = 7 c[keyPath: getF] = 8 // no-trap c[keyPath: getF] += c[keyPath: getF] // no-trap } ExclusiveAccessTestSuite.test("KeyPathInoutKeyPathWriteClassStoredProp") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a modification) started at") .code { let getF = \ClassWithStoredProperty.f let c = ClassWithStoredProperty() expectCrashLater() modifyAndPerform(&c[keyPath: getF]) { c[keyPath: getF] = 12 } } ExclusiveAccessTestSuite.test("KeyPathInoutKeyPathReadClassStoredProp") .skip(.custom( { _isFastAssertConfiguration() }, reason: "this trap is not guaranteed to happen in -Ounchecked")) .crashOutputMatches("Previous access (a modification) started at") .crashOutputMatches("Current access (a read) started at") .code { let getF = \ClassWithStoredProperty.f let c = ClassWithStoredProperty() expectCrashLater() modifyAndPerform(&c[keyPath: getF]) { let y = c[keyPath: getF] _blackHole(y) } } // [Exclusivity] improve diagnostics for // withoutActuallyEscaping. ExclusiveAccessTestSuite.test("withoutActuallyEscapingConflict") { var localVal = 0 let nestedModify = { localVal = 3 } withoutActuallyEscaping(nestedModify) { expectCrashLater() modifyAndPerform(&localVal, closure: $0) } } ExclusiveAccessTestSuite.test("directlyAppliedConflict") { var localVal = 0 let nestedModify = { localVal = 3 } expectCrashLater() _ = { modifyAndPerform(&localVal, closure: nestedModify) }() } // [Exclusivity] failure to diagnose // escaping closures called within directly applied noescape closures. ExclusiveAccessTestSuite.test("directlyAppliedEscapingConflict") { var localVal = 0 let nestedModify = { localVal = 3 } expectCrashLater() _ = { modifyAndPerformEscaping(&localVal, closure: nestedModify) }() } // rdar://102056143 - recursive locals inside a mutating method // effectively capture self as inout even if they don't modify // self. This should not trigger a runtime trap. struct RecursiveLocalDuringMutation { var flag: Bool = false mutating func update() { func local1(_ done: Bool) -> Bool { if !done { return local2() } return flag } func local2() -> Bool { return local1(true) } flag = !local1(false) } } ExclusiveAccessTestSuite.test("recursiveLocalDuringMutation") { var v = RecursiveLocalDuringMutation() v.update() _blackHole(v.flag) } // rdar://102056143 - negative test. Make sure we still catch true // violations on recursive captures. struct RecursiveCaptureViolation { func testBadness() -> Bool { var flag = false func local1(_ x: inout Bool) { if !flag { x = true local2() } } func local2() { local1(&flag) } local2() return flag } } ExclusiveAccessTestSuite.test("recursiveCaptureViolation") { var s = RecursiveCaptureViolation() expectCrashLater() s.testBadness() _blackHole(s) } runAllTests()