util: Clarify the assertion message in Assert, Assume and CHECK_NONFATAL #35461

pull optout21 wants to merge 1 commits into bitcoin:master from optout21:2606-assert-msg changing 4 files +26 −6
  1. optout21 commented at 11:19 AM on June 4, 2026: contributor

    Motivation: This was discussed in #34844 (review)

    The error message generated by Assert, Assume, and CHECK_NONFATAL contains the failing assertion condition text, but it's unclear whether that's the failed expectation or the actual case.

    For example, "Internal bug detected: pindex != nullptr" in fact means that what happened was pindex == nullptr.

    Clarify the situation, by inserting "Failed: '%s'".

    The above example is now: "Internal bug detected: Failed: 'pindex != nullptr'" which is more unambiguous.

    Note: non-assert type errors, such as NONFATAL_UNREACHABLE, are not affected.

  2. DrahtBot commented at 11:19 AM on June 4, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/35461.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK l0rinc

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. optout21 force-pushed on Jun 4, 2026
  4. DrahtBot added the label CI failed on Jun 4, 2026
  5. In Assert clarify the failing condition in the message
    The error message generated by Assert, Assume, and CHECK_NONFATAL
    contained the failing assertion condition text, but it was
    unclear whether that's the failed expectation or the
    actual case. E.g. "Internal bug detected: pindex != nullptr"
    meant that pindex == nullptr.
    
    Clarify the situation, by inserting "Failed: '%s'".
    The above example is now:
    "Internal bug detected: Failed: 'pindex != nullptr'".
    d46f1fae63
  6. optout21 force-pushed on Jun 4, 2026
  7. optout21 commented at 12:33 PM on June 4, 2026: contributor

    Added a test update (following CI failures), condensed util_check test.

  8. DrahtBot removed the label CI failed on Jun 4, 2026
  9. optout21 marked this as ready for review on Jun 4, 2026
  10. in src/test/util_check_tests.cpp:25 in d46f1fae63
      21 | @@ -22,12 +22,25 @@ BOOST_AUTO_TEST_CASE(check_fail)
      22 |      test_only_CheckFailuresAreExceptionsNotAborts mock_checks{};
      23 |  
      24 |      if constexpr (G_ABORT_ON_FAILED_ASSUME) {
      25 | -        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{"Internal bug detected: false"});
      26 | +        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'false'"});
    


    l0rinc commented at 7:36 PM on June 4, 2026:

    I don't particularly like the double colon and starting with capital letters in the middle, please consider avoiding the two stacked labels by adding the text after the failure, e.g:

    diff --git a/src/test/util_check_tests.cpp b/src/test/util_check_tests.cpp
    index 49954dff9c..0d944427f8 100644
    --- a/src/test/util_check_tests.cpp
    +++ b/src/test/util_check_tests.cpp
    @@ -22,16 +22,16 @@ BOOST_AUTO_TEST_CASE(check_fail)
         test_only_CheckFailuresAreExceptionsNotAborts mock_checks{};
     
         if constexpr (G_ABORT_ON_FAILED_ASSUME) {
    -        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'false'"});
    +        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{"Internal bug detected: `false` check failed"});
         } else {
             BOOST_CHECK_NO_THROW(Assume(false));
         }
    -    BOOST_CHECK_EXCEPTION(Assert(false), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'false'"});
    -    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'false'"});
    +    BOOST_CHECK_EXCEPTION(Assert(false), NonFatalCheckError, HasReason{"Internal bug detected: `false` check failed"});
    +    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{"Internal bug detected: `false` check failed"});
     
         // Repeat with a more realistic assert condition
         void* this_should_be_nonnull{nullptr};
    -    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'this_should_be_nonnull != nullptr'"});
    +    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{"Internal bug detected: `this_should_be_nonnull != nullptr` check failed"});
     }
     
     BOOST_AUTO_TEST_SUITE_END()
    diff --git a/src/util/check.cpp b/src/util/check.cpp
    index e26f3d7b41..b62520b1b4 100644
    --- a/src/util/check.cpp
    +++ b/src/util/check.cpp
    @@ -17,7 +17,7 @@
     
     std::string StrFormatFailedCheck(std::string_view assertion)
     {
    -    return strprintf("Failed: '%s'", assertion);
    +    return strprintf("`%s` check failed", assertion);
     }
     
     std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc)
    diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py
    index 412c9455ce..7fd104a815 100755
    --- a/test/functional/rpc_misc.py
    +++ b/test/functional/rpc_misc.py
    @@ -27,6 +27,7 @@ class RpcMiscTest(BitcoinTestFramework):
     
             self.log.info("test CHECK_NONFATAL")
             msg_internal_bug = 'request.params[9].get_str() != "trigger_internal_bug"'
    +        msg_failed_check = f"`{msg_internal_bug}` check failed"
             self.restart_node(0)  # Required to flush the chainstate
             try:
                 node.echo(arg9="trigger_internal_bug")
    @@ -41,7 +42,7 @@ class RpcMiscTest(BitcoinTestFramework):
                 self.start_node(0)
             except JSONRPCException as e:
                 assert_equal(e.error["code"], -1)
    -            assert f"Internal bug detected: Failed: '{msg_internal_bug}'" in e.error["message"]
    +            assert f"Internal bug detected: {msg_failed_check}" in e.error["message"]
     
             self.log.info("test max arg size")
             ARG_SZ_COMMON = 131071  # Common limit, used previously in the test framework, serves as a regression test
    
  11. in src/util/check.cpp:32 in d46f1fae63
      28 | @@ -29,12 +29,17 @@ NonFatalCheckError::NonFatalCheckError(std::string_view msg, const std::source_l
      29 |  {
      30 |  }
      31 |  
      32 | +NonFatalCheckError NonFatalCheckError::CreateFailedAssert(std::string_view assertion, const std::source_location& loc)
    


    l0rinc commented at 7:38 PM on June 4, 2026:

    Could we avoid adding a public NonFatalCheckError factory? The new formatting is only used by Assert, Assume, and CHECK_NONFATAL, so a helper for the formatted string keeps the generic exception constructor as the only public error API:

    diff --git a/src/test/util_check_tests.cpp b/src/test/util_check_tests.cpp
    index 3a47ecb3c2..49954dff9c 100644
    --- a/src/test/util_check_tests.cpp
    +++ b/src/test/util_check_tests.cpp
    @@ -34,13 +34,4 @@ BOOST_AUTO_TEST_CASE(check_fail)
         BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'this_should_be_nonnull != nullptr'"});
     }
     
    -BOOST_AUTO_TEST_CASE(check_exception_constructor)
    -{
    -    const auto assert_text{"pointer != nullptr"};
    -    const auto location{std::source_location::current()};
    -
    -    BOOST_CHECK(std::string_view{NonFatalCheckError("some_message", location).what()}.find("Internal bug detected: some_message\n") != std::string_view::npos);
    -    BOOST_CHECK(std::string_view{NonFatalCheckError::CreateFailedAssert(assert_text, location).what()}.find("Internal bug detected: Failed: 'pointer != nullptr'\n") != std::string_view::npos);
    -}
    -
     BOOST_AUTO_TEST_SUITE_END()
    diff --git a/src/util/check.cpp b/src/util/check.cpp
    index 36885d6db1..e26f3d7b41 100644
    --- a/src/util/check.cpp
    +++ b/src/util/check.cpp
    @@ -15,6 +15,11 @@
     #include <string>
     #include <string_view>
     
    +std::string StrFormatFailedCheck(std::string_view assertion)
    +{
    +    return strprintf("Failed: '%s'", assertion);
    +}
    +
     std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc)
     {
         return strprintf("Internal bug detected: %s\n%s:%d (%s)\n"
    @@ -29,17 +34,12 @@ NonFatalCheckError::NonFatalCheckError(std::string_view msg, const std::source_l
     {
     }
     
    -NonFatalCheckError NonFatalCheckError::CreateFailedAssert(std::string_view assertion, const std::source_location& loc)
    -{
    -    return NonFatalCheckError(strprintf("Failed: '%s'", assertion), loc);
    -}
    -
     bool g_detail_test_only_CheckFailuresAreExceptionsNotAborts{false};
     
     void assertion_fail(const std::source_location& loc, std::string_view assertion)
     {
         if (g_detail_test_only_CheckFailuresAreExceptionsNotAborts) {
    -        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);
    +        throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};
         }
         auto str = strprintf("%s:%s %s: Assertion `%s' failed.\n", loc.file_name(), loc.line(), loc.function_name(), assertion);
         fwrite(str.data(), 1, str.size(), stderr);
    diff --git a/src/util/check.h b/src/util/check.h
    index 4a7a52b6a8..3ffe8af282 100644
    --- a/src/util/check.h
    +++ b/src/util/check.h
    @@ -57,13 +57,12 @@ struct test_only_CheckFailuresAreExceptionsNotAborts {
     };
     
     std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc);
    +std::string StrFormatFailedCheck(std::string_view assertion);
     
     class NonFatalCheckError : public std::runtime_error
     {
     public:
         NonFatalCheckError(std::string_view msg, const std::source_location& loc);
    -    /// Create an instance in case of a failed assert condition, message is constructed accordingly
    -    static NonFatalCheckError CreateFailedAssert(std::string_view assertion, const std::source_location& loc);
     };
     
     /** Internal helper */
    @@ -77,7 +76,7 @@ T&& inline_check_non_fatal(LIFETIMEBOUND T&& val, const std::source_location& lo
             if constexpr (G_ABORT_ON_FAILED_ASSUME) {
                 assertion_fail(loc, assertion);
             }
    -        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);
    +        throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};
         }
         return std::forward<T>(val);
     }
    
  12. in src/util/check.cpp:44 in d46f1fae63
      40 |  {
      41 |      if (g_detail_test_only_CheckFailuresAreExceptionsNotAborts) {
      42 | -        throw NonFatalCheckError{assertion, loc};
      43 | +        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);
      44 |      }
      45 |      auto str = strprintf("%s:%s %s: Assertion `%s' failed.\n", loc.file_name(), loc.line(), loc.function_name(), assertion);
    


    l0rinc commented at 7:39 PM on June 4, 2026:

    Should the aborting assertion path use the same clarified wording? Without this, a normal fatal Assert still prints the old Assertion 'condition' failed shape while the exception path says the check failed.

    diff --git a/src/util/check.cpp b/src/util/check.cpp
    --- a/src/util/check.cpp	(revision e8c1542b132f855eecb2cf621eb280cd35d63cf0)
    +++ b/src/util/check.cpp	(revision 4ffb91e3bbf0a06166b2a7bbd7f543d2f5c6b36d)
    @@ -41,7 +41,7 @@
         if (g_detail_test_only_CheckFailuresAreExceptionsNotAborts) {
             throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};
         }
    -    auto str = strprintf("%s:%s %s: Assertion `%s' failed.\n", loc.file_name(), loc.line(), loc.function_name(), assertion);
    +    auto str = strprintf("%s:%s %s: Assertion failed: %s.\n", loc.file_name(), loc.line(), loc.function_name(), StrFormatFailedCheck(assertion));
         fwrite(str.data(), 1, str.size(), stderr);
         std::abort();
     }
    
  13. in src/test/util_check_tests.cpp:35 in d46f1fae63
      33 | +    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'false'"});
      34 | +
      35 | +    // Repeat with a more realistic assert condition
      36 | +    void* this_should_be_nonnull{nullptr};
      37 | +    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'this_should_be_nonnull != nullptr'"});
      38 | +}
    


    l0rinc commented at 7:41 PM on June 4, 2026:

    Could this test the note about non-assert errors directly? Checking NONFATAL_UNREACHABLE keeps the coverage tied to the behavior reviewers need to preserve, instead of only exercising the constructor plumbing.

    diff --git a/src/test/util_check_tests.cpp b/src/test/util_check_tests.cpp
    --- a/src/test/util_check_tests.cpp	(revision 4ffb91e3bbf0a06166b2a7bbd7f543d2f5c6b36d)
    +++ b/src/test/util_check_tests.cpp	(revision 014fae1a7202498a6e9022a1f4215b0acf23cc9c)
    @@ -9,6 +9,13 @@
     
     BOOST_AUTO_TEST_SUITE(util_check_tests)
     
    +namespace {
    +void TriggerNonFatalUnreachable()
    +{
    +    NONFATAL_UNREACHABLE();
    +}
    +} // namespace
    +
     BOOST_AUTO_TEST_CASE(check_pass)
     {
         Assume(true);
    @@ -34,4 +41,9 @@
         BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{"Internal bug detected: `this_should_be_nonnull != nullptr` check failed"});
     }
     
    +BOOST_AUTO_TEST_CASE(unreachable_diagnostic_unchanged)
    +{
    +    BOOST_CHECK_EXCEPTION(TriggerNonFatalUnreachable(), NonFatalCheckError, HasReason{"Internal bug detected: Unreachable code reached (non-fatal)"});
    +}
    +
     BOOST_AUTO_TEST_SUITE_END()
    
  14. l0rinc approved
  15. l0rinc commented at 7:45 PM on June 4, 2026: contributor

    Concept ACK, it's a tiny difference that we don't often expect to encounter, but when we do, it better tell us a realistic story.

    Left a few nits about slightly clearer phrasing and leaving the new formatter private.

    <details><summary>Local patch</summary>

    diff --git a/src/test/util_check_tests.cpp b/src/test/util_check_tests.cpp
    index 3a47ecb3c2..8a92376608 100644
    --- a/src/test/util_check_tests.cpp
    +++ b/src/test/util_check_tests.cpp
    @@ -9,6 +9,13 @@
     
     BOOST_AUTO_TEST_SUITE(util_check_tests)
     
    +namespace {
    +void TriggerNonFatalUnreachable()
    +{
    +    NONFATAL_UNREACHABLE();
    +}
    +} // namespace
    +
     BOOST_AUTO_TEST_CASE(check_pass)
     {
         Assume(true);
    @@ -22,25 +29,21 @@ BOOST_AUTO_TEST_CASE(check_fail)
         test_only_CheckFailuresAreExceptionsNotAborts mock_checks{};
     
         if constexpr (G_ABORT_ON_FAILED_ASSUME) {
    -        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'false'"});
    +        BOOST_CHECK_EXCEPTION(Assume(false), NonFatalCheckError, HasReason{"Internal bug detected: `false` check failed"});
         } else {
             BOOST_CHECK_NO_THROW(Assume(false));
         }
    -    BOOST_CHECK_EXCEPTION(Assert(false), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'false'"});
    -    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'false'"});
    +    BOOST_CHECK_EXCEPTION(Assert(false), NonFatalCheckError, HasReason{"Internal bug detected: `false` check failed"});
    +    BOOST_CHECK_EXCEPTION(CHECK_NONFATAL(false), NonFatalCheckError, HasReason{"Internal bug detected: `false` check failed"});
     
         // Repeat with a more realistic assert condition
         void* this_should_be_nonnull{nullptr};
    -    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{"Internal bug detected: Failed: 'this_should_be_nonnull != nullptr'"});
    +    BOOST_CHECK_EXCEPTION(Assert(this_should_be_nonnull != nullptr), NonFatalCheckError, HasReason{"Internal bug detected: `this_should_be_nonnull != nullptr` check failed"});
     }
     
    -BOOST_AUTO_TEST_CASE(check_exception_constructor)
    +BOOST_AUTO_TEST_CASE(unreachable_diagnostic_unchanged)
     {
    -    const auto assert_text{"pointer != nullptr"};
    -    const auto location{std::source_location::current()};
    -
    -    BOOST_CHECK(std::string_view{NonFatalCheckError("some_message", location).what()}.find("Internal bug detected: some_message\n") != std::string_view::npos);
    -    BOOST_CHECK(std::string_view{NonFatalCheckError::CreateFailedAssert(assert_text, location).what()}.find("Internal bug detected: Failed: 'pointer != nullptr'\n") != std::string_view::npos);
    +    BOOST_CHECK_EXCEPTION(TriggerNonFatalUnreachable(), NonFatalCheckError, HasReason{"Internal bug detected: Unreachable code reached (non-fatal)"});
     }
     
     BOOST_AUTO_TEST_SUITE_END()
    diff --git a/src/util/check.cpp b/src/util/check.cpp
    index 36885d6db1..f67fb5374f 100644
    --- a/src/util/check.cpp
    +++ b/src/util/check.cpp
    @@ -15,6 +15,11 @@
     #include <string>
     #include <string_view>
     
    +std::string StrFormatFailedCheck(std::string_view assertion)
    +{
    +    return strprintf("`%s` check failed", assertion);
    +}
    +
     std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc)
     {
         return strprintf("Internal bug detected: %s\n%s:%d (%s)\n"
    @@ -29,19 +34,14 @@ NonFatalCheckError::NonFatalCheckError(std::string_view msg, const std::source_l
     {
     }
     
    -NonFatalCheckError NonFatalCheckError::CreateFailedAssert(std::string_view assertion, const std::source_location& loc)
    -{
    -    return NonFatalCheckError(strprintf("Failed: '%s'", assertion), loc);
    -}
    -
     bool g_detail_test_only_CheckFailuresAreExceptionsNotAborts{false};
     
     void assertion_fail(const std::source_location& loc, std::string_view assertion)
     {
         if (g_detail_test_only_CheckFailuresAreExceptionsNotAborts) {
    -        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);
    +        throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};
         }
    -    auto str = strprintf("%s:%s %s: Assertion `%s' failed.\n", loc.file_name(), loc.line(), loc.function_name(), assertion);
    +    auto str = strprintf("%s:%s %s: Assertion failed: %s.\n", loc.file_name(), loc.line(), loc.function_name(), StrFormatFailedCheck(assertion));
         fwrite(str.data(), 1, str.size(), stderr);
         std::abort();
     }
    diff --git a/src/util/check.h b/src/util/check.h
    index 4a7a52b6a8..3ffe8af282 100644
    --- a/src/util/check.h
    +++ b/src/util/check.h
    @@ -57,13 +57,12 @@ struct test_only_CheckFailuresAreExceptionsNotAborts {
     };
     
     std::string StrFormatInternalBug(std::string_view msg, const std::source_location& loc);
    +std::string StrFormatFailedCheck(std::string_view assertion);
     
     class NonFatalCheckError : public std::runtime_error
     {
     public:
         NonFatalCheckError(std::string_view msg, const std::source_location& loc);
    -    /// Create an instance in case of a failed assert condition, message is constructed accordingly
    -    static NonFatalCheckError CreateFailedAssert(std::string_view assertion, const std::source_location& loc);
     };
     
     /** Internal helper */
    @@ -77,7 +76,7 @@ T&& inline_check_non_fatal(LIFETIMEBOUND T&& val, const std::source_location& lo
             if constexpr (G_ABORT_ON_FAILED_ASSUME) {
                 assertion_fail(loc, assertion);
             }
    -        throw NonFatalCheckError::CreateFailedAssert(assertion, loc);
    +        throw NonFatalCheckError{StrFormatFailedCheck(assertion), loc};
         }
         return std::forward<T>(val);
     }
    diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py
    index 412c9455ce..7fd104a815 100755
    --- a/test/functional/rpc_misc.py
    +++ b/test/functional/rpc_misc.py
    @@ -27,6 +27,7 @@ class RpcMiscTest(BitcoinTestFramework):
     
             self.log.info("test CHECK_NONFATAL")
             msg_internal_bug = 'request.params[9].get_str() != "trigger_internal_bug"'
    +        msg_failed_check = f"`{msg_internal_bug}` check failed"
             self.restart_node(0)  # Required to flush the chainstate
             try:
                 node.echo(arg9="trigger_internal_bug")
    @@ -41,7 +42,7 @@ class RpcMiscTest(BitcoinTestFramework):
                 self.start_node(0)
             except JSONRPCException as e:
                 assert_equal(e.error["code"], -1)
    -            assert f"Internal bug detected: Failed: '{msg_internal_bug}'" in e.error["message"]
    +            assert f"Internal bug detected: {msg_failed_check}" in e.error["message"]
     
             self.log.info("test max arg size")
             ARG_SZ_COMMON = 131071  # Common limit, used previously in the test framework, serves as a regression test
    

    </details>

  16. optout21 renamed this:
    Clarify the assertion message in Assert, Assume and CHECK_NONFATAL
    util: Clarify the assertion message in Assert, Assume and CHECK_NONFATAL
    on Jun 4, 2026
  17. DrahtBot added the label Utils/log/libs on Jun 4, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-06-11 10:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me