mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
I also removed the -verify-sil-ownership flag in favor of a disable flag -disable-sil-ownership-verifier. I used this on only two tests that still need work to get them to pass with ownership, but whose problems are well understood, small corner cases. I am going to fix them in follow on commits. I detail them below: 1. SILOptimizer/definite_init_inout_super_init.swift. This is a test case where DI is supposed to error. The only problem is that we crash before we error since the code emitting by SILGen to trigger this error does not pass ownership invariants. I have spoken with JoeG about this and he suggested that I fix this earlier in the compiler. Since we do not run the ownership verifier without asserts enabled, this should not affect compiler users. Given that it has triggered DI errors previously I think it is safe to disable ownership here. 2. PrintAsObjC/extensions.swift. In this case, the signature generated by type lowering for one of the thunks here uses an unsafe +0 return value instead of doing an autorelease return. The ownership checker rightly flags this leak. This is going to require either an AST level change or a change to TypeLowering. I think it is safe to turn this off since it is such a corner case that it was found by a test that has nothing to do with it. rdar://43398898
387 lines
11 KiB
Swift
387 lines
11 KiB
Swift
// 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 explicit swift-version 4.
|
|
// UNSUPPORTED: swift_test_mode_optimize_none_with_implicit_dynamic
|
|
|
|
// 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<T>(_ _: UnsafePointer<T>, closure: () ->()) {
|
|
closure()
|
|
}
|
|
|
|
/// Begin a modify access to the first parameter and call the closure inside it.
|
|
func modifyAndPerform<T>(_ _: UnsafeMutablePointer<T>, closure: () ->()) {
|
|
closure()
|
|
}
|
|
|
|
/// Begin a modify access to the first parameter and call the escaping closure inside it.
|
|
func modifyAndPerformEscaping<T>(_ _: UnsafeMutablePointer<T>, 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)
|
|
}
|
|
}
|
|
|
|
// <rdar://problem/43076947> [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)
|
|
}()
|
|
}
|
|
|
|
// <rdar://problem/43122715> [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)
|
|
}()
|
|
}
|
|
|
|
runAllTests()
|