Skip to content

[stdlib] Add build option to enable _debugPrecondition in Release mode #41445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,10 @@ option(SWIFT_ENABLE_STDLIBCORE_EXCLUSIVITY_CHECKING
"Build stdlibCore with exclusivity checking enabled"
FALSE)

option(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
"Enable _debugPrecondition checks in the stdlib in Release configurations"
FALSE)

option(SWIFT_ENABLE_EXPERIMENTAL_DIFFERENTIABLE_PROGRAMMING
"Enable experimental Swift differentiable programming features"
FALSE)
Expand Down
8 changes: 8 additions & 0 deletions stdlib/public/core/Assert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -261,24 +261,32 @@ internal func _debugPrecondition(
_ condition: @autoclosure () -> Bool, _ message: StaticString = StaticString(),
file: StaticString = #file, line: UInt = #line
) {
#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
_precondition(condition(), message, file: file, line: line)
#else
// Only check in debug mode.
if _slowPath(_isDebugAssertConfiguration()) {
if !_fastPath(condition()) {
_fatalErrorMessage("Fatal error", message, file: file, line: line,
flags: _fatalErrorFlags())
}
}
#endif
}

@usableFromInline @_transparent
internal func _debugPreconditionFailure(
_ message: StaticString = StaticString(),
file: StaticString = #file, line: UInt = #line
) -> Never {
#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
_preconditionFailure(message, file: file, line: line)
#else
if _slowPath(_isDebugAssertConfiguration()) {
_precondition(false, message, file: file, line: line)
}
_conditionallyUnreachable()
#endif
}

/// Internal checks.
Expand Down
11 changes: 11 additions & 0 deletions stdlib/public/core/AssertCommon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ func _isStdlibInternalChecksEnabled() -> Bool {
#endif
}

@_transparent
@_alwaysEmitIntoClient // Introduced in 5.7
public // @testable
func _isStdlibDebugChecksEnabled() -> Bool {
#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
return !_isFastAssertConfiguration()
#else
return _isDebugAssertConfiguration()
#endif
}

@usableFromInline @_transparent
internal func _fatalErrorFlags() -> UInt32 {
// The current flags are:
Expand Down
3 changes: 3 additions & 0 deletions stdlib/public/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ endif()
if(SWIFT_STDLIB_ENABLE_STDLIBCORE_EXCLUSIVITY_CHECKING)
list(APPEND swift_stdlib_compile_flags "-enforce-exclusivity=checked")
endif()
if(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE)
list(APPEND swift_stdlib_compile_flags "-DSWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE")
endif()
set(compile_flags_for_final_build)
if(SWIFT_ENABLE_ARRAY_COW_CHECKS)
list(APPEND compile_flags_for_final_build "-DCOW_CHECKS_ENABLED")
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ normalize_boolean_spelling(SWIFT_OPTIMIZED)
normalize_boolean_spelling(SWIFT_STDLIB_SINGLE_THREADED_RUNTIME)
normalize_boolean_spelling(SWIFT_ENABLE_REFLECTION)
normalize_boolean_spelling(SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS)
normalize_boolean_spelling(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE)
normalize_boolean_spelling(SWIFT_HAVE_LIBXML2)
normalize_boolean_spelling(SWIFT_INCLUDE_TOOLS)
normalize_boolean_spelling(SWIFT_STDLIB_STATIC_PRINT)
Expand Down
2 changes: 2 additions & 0 deletions test/lit.site.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ if "@SWIFT_STDLIB_ENABLE_UNICODE_DATA" == "TRUE":
config.available_features.add('stdlib_unicode_data')
if "@SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING@" == "TRUE":
config.available_features.add('string_processing')
if "@SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE@" == "TRUE":
config.available_features.add('swift_stdlib_debug_preconditions_in_release')

config.swift_freestanding_is_darwin = "@SWIFT_FREESTANDING_IS_DARWIN@" == "TRUE"
config.swift_enable_dispatch = "@SWIFT_ENABLE_DISPATCH@" == "TRUE"
Expand Down
4 changes: 2 additions & 2 deletions test/stdlib/RangeTraps.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ if #available(SwiftStdlib 5.5, *) {
// Debug check was introduced in https://github.com/apple/swift/pull/34961
RangeTraps.test("UncheckedHalfOpen")
.xfail(.custom(
{ !_isDebugAssertConfiguration() },
{ !_isStdlibDebugChecksEnabled() },
reason: "assertions are disabled in Release and Unchecked mode"))
.code {
expectCrashLater()
Expand All @@ -138,7 +138,7 @@ if #available(SwiftStdlib 5.5, *) {

RangeTraps.test("UncheckedClosed")
.xfail(.custom(
{ !_isDebugAssertConfiguration() },
{ !_isStdlibDebugChecksEnabled() },
reason: "assertions are disabled in Release and Unchecked mode"))
.code {
expectCrashLater()
Expand Down
2 changes: 2 additions & 0 deletions validation-test/lit.site.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ if "@SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED@" == "TRUE":
config.available_features.add('distributed')
if "@SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING@" == "TRUE":
config.available_features.add('string_processing')
if "@SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE@" == "TRUE":
config.available_features.add('swift_stdlib_debug_preconditions_in_release')

config.swift_freestanding_is_darwin = "@SWIFT_FREESTANDING_IS_DARWIN@" == "TRUE"
config.swift_enable_dispatch = "@SWIFT_ENABLE_DISPATCH@" == "TRUE"
Expand Down
34 changes: 34 additions & 0 deletions validation-test/stdlib/Assert-debugPrecondition-off.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Debug -Onone
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Release -O
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Unchecked -Ounchecked
// RUN: %target-codesign %t/Assert_Debug
// RUN: %target-codesign %t/Assert_Release
// RUN: %target-codesign %t/Assert_Unchecked
// RUN: %target-run %t/Assert_Debug | %FileCheck --check-prefixes=DEBUG %s
// RUN: %target-run %t/Assert_Release | %FileCheck --check-prefixes=RELEASE %s
// RUN: %target-run %t/Assert_Unchecked | %FileCheck --check-prefixes=UNCHECKED %s

// UNSUPPORTED: swift_stdlib_debug_preconditions_in_release

// DEBUG: _isStdlibDebugChecksEnabled: true
// RELEASE: _isStdlibDebugChecksEnabled: false
// UNCHECKED: _isStdlibDebugChecksEnabled: false
print("_isStdlibDebugChecksEnabled: \(_isStdlibDebugChecksEnabled())")


func check() -> Bool {
print("Debug preconditions are active")
return true
}

// DEBUG-NEXT: Debug preconditions are active
// RELEASE-NOT: Debug preconditions are active
// UNCHECKED-NOT: Debug preconditions are active
_debugPrecondition(check()) // Note: side effects in an assert are a terrible
// idea; do not emulate this pattern in real code.

// DEBUG-NEXT: Done
// RELEASE-NEXT: Done
// UNCHECKED-NEXT: Done
print("Done")
34 changes: 34 additions & 0 deletions validation-test/stdlib/Assert-debugPrecondition-on.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Debug -Onone
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Release -O
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Unchecked -Ounchecked
// RUN: %target-codesign %t/Assert_Debug
// RUN: %target-codesign %t/Assert_Release
// RUN: %target-codesign %t/Assert_Unchecked
// RUN: %target-run %t/Assert_Debug | %FileCheck --check-prefixes=DEBUG %s
// RUN: %target-run %t/Assert_Release | %FileCheck --check-prefixes=RELEASE %s
// RUN: %target-run %t/Assert_Unchecked | %FileCheck --check-prefixes=UNCHECKED %s

// REQUIRES: swift_stdlib_debug_preconditions_in_release

// DEBUG: _isStdlibDebugChecksEnabled: true
// RELEASE: _isStdlibDebugChecksEnabled: true
// UNCHECKED: _isStdlibDebugChecksEnabled: false
print("_isStdlibDebugChecksEnabled: \(_isStdlibDebugChecksEnabled())")


func check() -> Bool {
print("Debug preconditions are active")
return true
}

// DEBUG-NEXT: Debug preconditions are active
// RELEASE-NEXT: Debug preconditions are active
// UNCHECKED-NOT: Debug preconditions are active
_debugPrecondition(check()) // Note: side effects in an assert are a terrible
// idea; do not emulate this pattern in real code.

// DEBUG-NEXT: Done
// RELEASE-NEXT: Done
// UNCHECKED-NEXT: Done
print("Done")
30 changes: 26 additions & 4 deletions validation-test/stdlib/Assert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,32 @@ Assert.test("preconditionFailure")
preconditionFailure("this should fail")
}

Assert.test("_precondition")
.xfail(.custom(
{ _isFastAssertConfiguration() },
reason: "preconditions are disabled in Unchecked mode"))
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
.code {
var x = 2
_precondition(x * 21 == 42, "should not fail")
expectCrashLater()
_precondition(x == 42, "this should fail")
}

Assert.test("_preconditionFailure")
.skip(.custom(
{ _isFastAssertConfiguration() },
reason: "optimizer assumes that the code path is unreachable"))
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
.code {
expectCrashLater()
_preconditionFailure("this should fail")
}

Assert.test("_debugPrecondition")
.xfail(.custom(
{ !_isDebugAssertConfiguration() },
reason: "debug preconditions are disabled in Release and Unchecked mode"))
{ !_isStdlibDebugChecksEnabled() },
reason: "debug preconditions are disabled"))
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
.code {
var x = 2
Expand All @@ -188,9 +210,9 @@ Assert.test("_debugPrecondition")

Assert.test("_debugPreconditionFailure")
.skip(.custom(
{ !_isDebugAssertConfiguration() },
{ !_isStdlibDebugChecksEnabled() },
reason: "optimizer assumes that the code path is unreachable"))
.crashOutputMatches("this should fail")
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
.code {
expectCrashLater()
_debugPreconditionFailure("this should fail")
Expand Down