From 5b31183968c8e8b3566832aa79ecbb70a1fa1ee3 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 18 Jul 2019 16:52:00 +0200 Subject: [PATCH] stdlib: Print the error message in precondition() and preconditionFailure() even in release builds. This is important for frameworks, like Foundation, because frameworks are always shipped and used as release builds. This change results in a little code size overhead, but it's quite small: Instead of a trap instruction it basically ends up in loading a few registers and calling the fatalError runtime function. I also added an overload for precondition/Failure without a message (instead of a default parameter). This overload just traps, because without a message calling the fatalError function does not add any benefit. --- stdlib/public/core/Assert.swift | 100 ++++++++++++++++-- ...izationOptions-without-stdlib-checks.swift | 8 +- .../specialize_checked_cast_branch.swift | 38 +++---- 3 files changed, 115 insertions(+), 31 deletions(-) diff --git a/stdlib/public/core/Assert.swift b/stdlib/public/core/Assert.swift index d88d5b0580002..5da93bbc40759 100644 --- a/stdlib/public/core/Assert.swift +++ b/stdlib/public/core/Assert.swift @@ -59,10 +59,12 @@ public func assert( /// /// * In playgrounds and `-Onone` builds (the default for Xcode's Debug /// configuration): If `condition` evaluates to `false`, stop program -/// execution in a debuggable state after printing `message`. +/// execution in a debuggable state after printing `message` +/// along with the file and line information.. /// /// * In `-O` builds (the default for Xcode's Release configuration): If -/// `condition` evaluates to `false`, stop program execution. +/// `condition` evaluates to `false`, stop program execution state after +/// printing `message`. /// /// * In `-Ounchecked` builds, `condition` is not evaluated, but the optimizer /// may assume that it *always* evaluates to `true`. Failure to satisfy that @@ -82,7 +84,7 @@ public func assert( @_transparent public func precondition( _ condition: @autoclosure () -> Bool, - _ message: @autoclosure () -> String = String(), + _ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line ) { // Only check in debug and release mode. In release mode just trap. @@ -91,10 +93,55 @@ public func precondition( _assertionFailure("Precondition failed", message(), file: file, line: line, flags: _fatalErrorFlags()) } + } else if _isReleaseAssertConfiguration() { + if !_fastPath(condition()) { + _assertionFailure("Precondition failed", message(), + flags: _fatalErrorFlags()) + } + } +} + +/// Checks a necessary condition for making forward progress. +/// +/// Use this function to detect conditions that must prevent the program from +/// proceeding, even in shipping code, but without a failure message. +/// +/// * In playgrounds and `-Onone` builds (the default for Xcode's Debug +/// configuration): If `condition` evaluates to `false`, stop program +/// execution in a debuggable state after printing "Precondition failed", +/// along with the file and line information. +/// +/// * In `-O` builds (the default for Xcode's Release configuration): If +/// `condition` evaluates to `false`, stop program execution. +/// +/// * In `-Ounchecked` builds, `condition` is not evaluated, but the optimizer +/// may assume that it *always* evaluates to `true`. Failure to satisfy that +/// assumption is a serious programming error. +/// +/// - Parameters: +/// - condition: The condition to test. `condition` is not evaluated in +/// `-Ounchecked` builds. +/// - file: The file name to print with `message` if the precondition fails. +/// The default is the file where `precondition(_:_:file:line:)` is +/// called. +/// - line: The line number to print along with `message` if the assertion +/// fails. The default is the line number where +/// `precondition(_:_:file:line:)` is called. +@_transparent +public func precondition( + _ condition: @autoclosure () -> Bool, + file: StaticString = #file, line: UInt = #line +) { + // Only check in debug and release mode. In release mode just trap. + if _isDebugAssertConfiguration() { + if !_fastPath(condition()) { + _assertionFailure("Precondition failed", String(), file: file, line: line, + flags: _fatalErrorFlags()) + } } else if _isReleaseAssertConfiguration() { let error = !condition() Builtin.condfail_message(error._value, - StaticString("precondition failure").unsafeRawPointer) + StaticString("Precondition failed").unsafeRawPointer) } } @@ -146,10 +193,10 @@ public func assertionFailure( /// /// * In playgrounds and `-Onone` builds (the default for Xcode's Debug /// configuration), stops program execution in a debuggable state after -/// printing `message`. +/// printing `message` along with the file and line information. /// /// * In `-O` builds (the default for Xcode's Release configuration), stops -/// program execution. +/// program execution after printing `message`. /// /// * In `-Ounchecked` builds, the optimizer may assume that this function is /// never called. Failure to satisfy that assumption is a serious @@ -164,16 +211,53 @@ public func assertionFailure( /// line number where `preconditionFailure(_:file:line:)` is called. @_transparent public func preconditionFailure( - _ message: @autoclosure () -> String = String(), + _ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line ) -> Never { // Only check in debug and release mode. In release mode just trap. if _isDebugAssertConfiguration() { _assertionFailure("Fatal error", message(), file: file, line: line, flags: _fatalErrorFlags()) + } else if _isReleaseAssertConfiguration() { + _assertionFailure("Precondition failed", message(), + flags: _fatalErrorFlags()) + } + _conditionallyUnreachable() +} + +/// Indicates that a precondition was violated, without a message. +/// +/// Use this function to stop the program when control flow can only reach the +/// call if your API was improperly used. This function's effects vary +/// depending on the build flag used: +/// +/// * In playgrounds and `-Onone` builds (the default for Xcode's Debug +/// configuration), stops program execution in a debuggable state after +/// printing "Fatal error". +/// +/// * In `-O` builds (the default for Xcode's Release configuration), stops +/// program execution. +/// +/// * In `-Ounchecked` builds, the optimizer may assume that this function is +/// never called. Failure to satisfy that assumption is a serious +/// programming error. +/// +/// - Parameters: +/// - file: The file name to print with `message`. The default is the file +/// where `preconditionFailure(file:line:)` is called. +/// - line: The line number to print. The default is the +/// line number where `preconditionFailure(file:line:)` is called. +@_transparent +public func preconditionFailure( + file: StaticString = #file, line: UInt = #line +) -> Never { + // Only check in debug and release mode. In release mode just trap. + if _isDebugAssertConfiguration() { + _assertionFailure("Fatal error", String(), file: file, line: line, + flags: _fatalErrorFlags()) } else if _isReleaseAssertConfiguration() { Builtin.condfail_message(true._value, - StaticString("precondition failure").unsafeRawPointer) + StaticString("Precondition failed").unsafeRawPointer) } _conditionallyUnreachable() } diff --git a/test/Frontend/OptimizationOptions-without-stdlib-checks.swift b/test/Frontend/OptimizationOptions-without-stdlib-checks.swift index c51ab8a7834c4..2451819566e9f 100644 --- a/test/Frontend/OptimizationOptions-without-stdlib-checks.swift +++ b/test/Frontend/OptimizationOptions-without-stdlib-checks.swift @@ -100,10 +100,10 @@ func test_partial_safety_check(x: Int, y: Int) -> Int { // In release mode keep succinct library precondition checks (trap). // RELEASE-LABEL: sil hidden @$s19OptimizationOptions22testprecondition_check1x1yS2i_SitF : $@convention(thin) (Int, Int) -> Int { -// RELEASE-NOT: "Fatal error" -// RELEASE: %[[V2:.+]] = builtin "xor_Int1"(%{{.+}}, %{{.+}}) -// RELEASE: cond_fail %[[V2]] -// RELEASE: return +// RELEASE-DAG: "Precondition failed" +// RELEASE-DAG: %[[FATAL_ERROR:.+]] = function_ref @[[FATAL_ERROR_FUNC:.*assertionFailure.*]] +// RELEASE: apply %[[FATAL_ERROR]]({{.*}}) +// RELEASE: unreachable // In unchecked mode remove library precondition checks. // UNCHECKED-LABEL: sil hidden @$s19OptimizationOptions22testprecondition_check1x1yS2i_SitF : $@convention(thin) (Int, Int) -> Int { diff --git a/test/SILOptimizer/specialize_checked_cast_branch.swift b/test/SILOptimizer/specialize_checked_cast_branch.swift index ca3162c2d0c8d..6d4d0fa5dc001 100644 --- a/test/SILOptimizer/specialize_checked_cast_branch.swift +++ b/test/SILOptimizer/specialize_checked_cast_branch.swift @@ -22,7 +22,7 @@ public func ArchetypeToArchetypeCast(t1 : T1, t2 : T2) -> T2 { if let x = t1 as? T2 { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch011ArchetypeToE4Cast2t12t2q_x_q_tr0_lFAA1CC_AA1DCTg5 : $@convention(thin) (@guaranteed C, @guaranteed D) -> @owned D @@ -34,7 +34,7 @@ public func ArchetypeToArchetypeCast(t1 : T1, t2 : T2) -> T2 { // CHECK: return [[T0]] // // CHECK: bb2 -// CHECK: cond_fail {{%.*}}, "precondition failure" +// CHECK: cond_fail {{%.*}}, "Precondition failed" // CHECK: unreachable // CHECK: } // end sil function '$s30specialize_checked_cast_branch011ArchetypeToE4Cast2t12t2q_x_q_tr0_lFAA1CC_AA1DCTg5' _ = ArchetypeToArchetypeCast(t1: c, t2: d) @@ -58,7 +58,7 @@ _ = ArchetypeToArchetypeCast(t1: b, t2: f) // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch011ArchetypeToE4Cast2t12t2q_x_q_tr0_lFAA8NotUInt8V_AA1CCTg5 : $@convention(thin) (NotUInt8, @guaranteed C) -> @owned C { // CHECK: bb0 // CHECK-NOT: bb1 -// CHECK: cond_fail {{%.*}}, "precondition failure" +// CHECK: cond_fail {{%.*}}, "Precondition failed" // CHECK: unreachable // CHECK: } // end sil function '$s30specialize_checked_cast_branch011ArchetypeToE4Cast2t12t2q_x_q_tr0_lFAA8NotUInt8V_AA1CCTg5' _ = ArchetypeToArchetypeCast(t1: b, t2: c) @@ -67,7 +67,7 @@ _ = ArchetypeToArchetypeCast(t1: b, t2: c) // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch011ArchetypeToE4Cast2t12t2q_x_q_tr0_lFAA1CC_AA8NotUInt8VTg5 : $@convention(thin) (@guaranteed C, NotUInt8) -> NotUInt8 { // CHECK: bb0 // CHECK-NOT: bb1 -// CHECK: cond_fail {{%.*}}, "precondition failure" +// CHECK: cond_fail {{%.*}}, "Precondition failed" // CHECK: unreachable // CHECK: } // end sil function '$s30specialize_checked_cast_branch011ArchetypeToE4Cast2t12t2q_x_q_tr0_lFAA1CC_AA8NotUInt8VTg5' _ = ArchetypeToArchetypeCast(t1: c, t2: b) @@ -84,7 +84,7 @@ _ = ArchetypeToArchetypeCast(t1: d, t2: c) // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch011ArchetypeToE4Cast2t12t2q_x_q_tr0_lFAA1CC_AA1ECTg5 : $@convention(thin) (@guaranteed C, @guaranteed E) -> @owned E { // CHECK: bb0 // CHECK-NOT: bb1 -// CHECK: cond_fail {{%.*}}, "precondition failure" +// CHECK: cond_fail {{%.*}}, "Precondition failed" // CHECK: unreachable // CHECK: } // end sil function '$s30specialize_checked_cast_branch011ArchetypeToE4Cast2t12t2q_x_q_tr0_lFAA1CC_AA1ECTg5' _ = ArchetypeToArchetypeCast(t1: c, t2: e) @@ -97,28 +97,28 @@ func ArchetypeToConcreteCastUInt8(t : T) -> NotUInt8 { if let x = t as? NotUInt8 { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } func ArchetypeToConcreteCastC(t : T) -> C { if let x = t as? C { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } func ArchetypeToConcreteCastD(t : T) -> D { if let x = t as? D { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } func ArchetypeToConcreteCastE(t : T) -> E { if let x = t as? E { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } // uint8 -> uint8 @@ -135,7 +135,7 @@ _ = ArchetypeToConcreteCastUInt8(t: c) // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch28ArchetypeToConcreteCastUInt81tAA03NotI0Vx_tlFAA0J6UInt64V_Tg5 : $@convention(thin) (NotUInt64) -> NotUInt8 { // CHECK: bb0 // CHECK-NOT: checked_cast_br -// CHECK: cond_fail {{%.*}}, "precondition failure" +// CHECK: cond_fail {{%.*}}, "Precondition failed" // CHECK: unreachable _ = ArchetypeToConcreteCastUInt8(t: f) @@ -167,7 +167,7 @@ _ = ArchetypeToConcreteCastC(t: d) // E -> C // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch24ArchetypeToConcreteCastC1tAA1CCx_tlFAA1EC_Tg5 : $@convention(thin) (@guaranteed E) -> @owned C { // CHECK: bb0 -// CHECK: cond_fail {{%.*}}, "precondition failure" +// CHECK: cond_fail {{%.*}}, "Precondition failed" // CHECK: unreachable _ = ArchetypeToConcreteCastC(t: e) @@ -182,7 +182,7 @@ _ = ArchetypeToConcreteCastC(t: e) // CHECK: return [[T0]] : $D // // CHECK: [[FAIL_BB]]: -// CHECK: cond_fail {{%.*}}, "precondition failure" +// CHECK: cond_fail {{%.*}}, "Precondition failed" // CHECK: unreachable // CHECK: } // end sil function '$s30specialize_checked_cast_branch24ArchetypeToConcreteCastD1tAA1DCx_tlFAA1CC_Tg5' _ = ArchetypeToConcreteCastD(t: c) @@ -190,7 +190,7 @@ _ = ArchetypeToConcreteCastD(t: c) // C -> E // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch24ArchetypeToConcreteCastE1tAA1ECx_tlFAA1CC_Tg5 : $@convention(thin) (@guaranteed C) -> @owned E { // CHECK: bb0 -// CHECK: cond_fail {{%.*}}, "precondition failure" +// CHECK: cond_fail {{%.*}}, "Precondition failed" // CHECK: unreachable _ = ArchetypeToConcreteCastE(t: c) @@ -202,19 +202,19 @@ func ConcreteToArchetypeCastUInt8(t: NotUInt8, t2: T) -> T { if let x = t as? T { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } func ConcreteToArchetypeCastC(t: C, t2: T) -> T { if let x = t as? T { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } func ConcreteToArchetypeCastD(t: D, t2: T) -> T { if let x = t as? T { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } // x -> x where x is not a class. @@ -273,14 +273,14 @@ func SuperToArchetypeCastC(c : C, t : T) -> T { if let x = c as? T { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } func SuperToArchetypeCastD(d : D, t : T) -> T { if let x = d as? T { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch21SuperToArchetypeCastC1c1txAA1CC_xtlFAF_Tg5 : $@convention(thin) (@guaranteed C, @guaranteed C) -> @owned C @@ -318,7 +318,7 @@ func ExistentialToArchetypeCast(o : AnyObject, t : T) -> T { if let x = o as? T { return x } - preconditionFailure("??? Profit?") + preconditionFailure() } // CHECK-LABEL: sil shared @$s30specialize_checked_cast_branch26ExistentialToArchetypeCast1o1txyXl_xtlFAA1CC_Tg5 : $@convention(thin) (@guaranteed AnyObject, @guaranteed C) -> @owned C