From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id oa89IuJM1mqbODMAWB0awg (envelope-from ) for ; Mon, 19 Oct 2026 13:01:22 -0400 Received: by simark.ca (Postfix, from userid 112) id 873791E04E; Mon, 19 Oct 2026 13:01:22 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.3 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 4E0221E04E for ; Mon, 19 Oct 2026 13:01:19 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id D3E724BA23CB for ; Mon, 19 Oct 2026 17:01:18 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org D3E724BA23CB Received: from simark.ca (simark.ca [158.69.221.121]) by sourceware.org (Postfix) with ESMTPS id AE0024BA23CC; Mon, 19 Oct 2026 16:59:36 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org AE0024BA23CC Authentication-Results: sourceware.org; dmarc=fail (p=none dis=none) header.from=efficios.com Authentication-Results: sourceware.org; spf=fail smtp.mailfrom=efficios.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org AE0024BA23CC Authentication-Results: sourceware.org; arc=none smtp.remote-ip=158.69.221.121 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1792429176; cv=none; b=LpOlv728EyHY3DE5IBlSc/RMPMH2aUw3+8JED2HO06oHHeQvj4L0biLoXMXgMdsJ/xKx0K7CsGugi0cZvWvc2RfcyCs0HvFXvvnigpU20AdoW1JgWXLaYXXlXOS5AimenePnt8Ore3N7vdcTMKDSPyvkBFQXaAjfHtJE032Vd+o= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1792429176; c=relaxed/simple; bh=nIqfxesJioC1SapTCkK4lB2O42SY2SyHqyEKtj/Dgx8=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=OadPryJdJYMtt0/f5JU3Mg6+xyFaOo5xSqvaOI4EyDIP715lPJxoydEoZpGeiu4TwkCzDYk4QcC2xtcVMSFXqK6jEJfuBoypCh3RExnZe3x1wl5YafjdPUflhbZUKm9KwM3YzxvT7hlLrZw5/cWNmHMe4UXmuuVzmdyz2SPz4NM= ARC-Authentication-Results: i=1; sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org AE0024BA23CC Received: by simark.ca (Postfix) id EC6251E091; Mon, 19 Oct 2026 12:59:34 -0400 (EDT) From: Simon Marchi To: gcc-patches@sourceware.org, gdb-patches@sourceware.org Cc: Simon Marchi Subject: [PATCH 5/6] gdbsupport/selftest: print operand values when a SELF_CHECK fails Date: Mon, 19 Oct 2026 12:58:21 -0400 Message-ID: <20261019165929.792697-6-simon.marchi@efficios.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261019165929.792697-1-simon.marchi@efficios.com> References: <20261019165929.792697-1-simon.marchi@efficios.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org The catch2 test framework (and likely others) has a neat trick to print the operands of failing test assertions. For instance: /home/simark/src/binutils-gdb/catch2/sample.cpp:17: FAILED: CHECK( reverse_string ("hello") == "hello" ) with expansion: "olleh" == "hello" This patch implements something like that for GDB's SELF_CHECK macro. As I did not instinctively know how to do this, I used Claude to guide me through the implementation, but all the code was written by me. The general idea of the trick is that the SELF_CHECK macro: SELF_CHECK (len == 4) now expands to approximately: check (decomposer () <= len == 4, ...) The decomposer object has a templated operator<= that records a reference to whatever comes after it (in this case the `len` variable) into a lhs_holder instance: template lhs_holder operator<= (const LhsT &lhs) const { return lhs_holder {lhs}; } The lhs_holder then implements all the comparison operators, such as operator==, which does the requested comparison with the following operand: template binary_expr_holder operator== (const RhsT &rhs) const { return binary_expr_holder { this->lhs == rhs, "==", this->lhs, rhs }; } The binary_expr_holder object holds references to both operands as well as the result of the comparison. That binary_expr_holder object is passed to the check function, which is then able to print the values of the operands as needed. It's actually required to go through a separate check function (instead of doing it all in the SELF_CHECK macro directly), to ensure that any temporaries that binary_expr_holder binds to are still alive when we print them. There is a second check function that takes a lhs_holder, for expressions like: SELF_CHECK (func_that_returns_bool ()); A test failure of a binary expression now looks like: FAIL: /home/smarchi/src/binutils-gdb/gdb/unittests/common-utils-selftests.c:35: func ("%s", "test") == "test boo": "test" == "test boo" Some previously used constructs are now rejected at compile time, and require a few changes in the tests: - A top-level && or ||. Overloading these operators would lose short-circuit evaluation, and only the leftmost operand could be decomposed anyway (because comparison operators take precedence). Options to work around this are to split the expressions (in the case of &&) or to add an extra pair of parentheses around the whole expression. For &&, I would argue it's a good thing to split expressions, so you get a more fine-grained message on failure. - A lone non-bool value, like SELF_CHECK (ptr). The fix for those is to use an explicit comparison. The second check function could accept a templated lhs_holder, instead of a lhs_holder, but since our coding style requires using explicit comparisons, I don't see why it should. An odd case of this is a use of the frame_id::code_addr_p field. While it is spiritually a boolean, it is in fact a 1-bit unsigned int bitfield. So it now requires an explicit `!= 0`. The expression resulting from the macro expansion triggers a -Wparentheses warning, so disable it in the SELF_CHECK macro. Operands are printed using a value_to_string function that is a long series of "if constexpr" to print various types of operands. It ends with an always-false static_assert, such that trying to print an unsupported type will cause a build failure, and will require telling the function how to print said type. For some types like iterators, there is no sensible value to print in a generic way, so we just print "{iterator}". Add a to_string method to mem_range, which is used by value_to_string. Finally, as a way to exercise this machinery without always having to purposely make a test fail, I made it so that running the selftests with -verbose prints all passing tests, including their operands. For instance: PASS: /home/simark/src/binutils-gdb/gdb/unittests/int-array-iterator.h:75: m_cur_idx < m_size: 2 < 7 Change-Id: Ic81da3024811d8fc9dd6335c50a0adcf75755d5d --- gdb/aarch64-tdep.c | 38 +- gdb/arm-tdep.c | 4 +- gdb/disasm-selftests.c | 2 +- gdb/dwarf2/frame.c | 2 +- gdb/memrange.h | 7 + gdb/producer.c | 33 +- gdb/regcache.c | 6 +- gdb/rust-parse.c | 2 +- gdb/selftest-arch.c | 2 +- gdb/source-cache.c | 12 +- gdb/unittests/array-view-selftests.c | 5 + gdb/unittests/environ-selftests.c | 32 +- gdb/unittests/frame_info_ptr-selftests.c | 2 +- gdb/unittests/function-view-selftests.c | 4 +- gdb/unittests/scoped_fd-selftests.c | 8 +- .../scoped_ignore_signal-selftests.c | 7 +- gdb/unittests/scoped_mmap-selftests.c | 8 +- gdb/unittests/style-selftests.c | 10 +- gdb/utils.c | 4 +- gdbserver/server.cc | 19 +- gdbsupport/selftest.cc | 81 ++- gdbsupport/selftest.h | 540 +++++++++++++++++- 22 files changed, 741 insertions(+), 87 deletions(-) diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index 109fca6bf110..733ee5c5279c 100644 --- a/gdb/aarch64-tdep.c +++ b/gdb/aarch64-tdep.c @@ -692,7 +692,7 @@ aarch64_analyze_prologue_test (void) info.bfd_arch_info = bfd_scan_arch ("aarch64"); struct gdbarch *gdbarch = gdbarch_find_by_info (info); - SELF_CHECK (gdbarch != NULL); + SELF_CHECK (gdbarch != nullptr); struct aarch64_prologue_cache cache; cache.saved_regs = trad_frame_alloc_saved_regs (gdbarch); @@ -721,8 +721,10 @@ aarch64_analyze_prologue_test (void) else if (i == AARCH64_LR_REGNUM) SELF_CHECK (cache.saved_regs[i].addr () == -264); else - SELF_CHECK (cache.saved_regs[i].is_realreg () - && cache.saved_regs[i].realreg () == i); + { + SELF_CHECK (cache.saved_regs[i].is_realreg ()); + SELF_CHECK (cache.saved_regs[i].realreg () == i); + } } for (int i = 0; i < AARCH64_D_REGISTER_COUNT; i++) @@ -730,8 +732,8 @@ aarch64_analyze_prologue_test (void) int num_regs = gdbarch_num_regs (gdbarch); int regnum = i + num_regs + AARCH64_D0_REGNUM; - SELF_CHECK (cache.saved_regs[regnum].is_realreg () - && cache.saved_regs[regnum].realreg () == regnum); + SELF_CHECK (cache.saved_regs[regnum].is_realreg ()); + SELF_CHECK (cache.saved_regs[regnum].realreg () == regnum); } } @@ -763,8 +765,10 @@ aarch64_analyze_prologue_test (void) else if (i == 19) SELF_CHECK (cache.saved_regs[i].addr () == -48); else - SELF_CHECK (cache.saved_regs[i].is_realreg () - && cache.saved_regs[i].realreg () == i); + { + SELF_CHECK (cache.saved_regs[i].is_realreg ()); + SELF_CHECK (cache.saved_regs[i].realreg () == i); + } } for (int i = 0; i < AARCH64_D_REGISTER_COUNT; i++) @@ -776,8 +780,10 @@ aarch64_analyze_prologue_test (void) if (i == 0) SELF_CHECK (cache.saved_regs[regnum].addr () == -24); else - SELF_CHECK (cache.saved_regs[regnum].is_realreg () - && cache.saved_regs[regnum].realreg () == regnum); + { + SELF_CHECK (cache.saved_regs[regnum].is_realreg ()); + SELF_CHECK (cache.saved_regs[regnum].realreg () == regnum); + } } } @@ -921,8 +927,10 @@ aarch64_analyze_prologue_test (void) else if (i == AARCH64_LR_REGNUM) SELF_CHECK (cache.saved_regs[i].addr () == -40); else - SELF_CHECK (cache.saved_regs[i].is_realreg () - && cache.saved_regs[i].realreg () == i); + { + SELF_CHECK (cache.saved_regs[i].is_realreg ()); + SELF_CHECK (cache.saved_regs[i].realreg () == i); + } } if (tdep->has_pauth ()) @@ -960,8 +968,10 @@ aarch64_analyze_prologue_test (void) else if (i == AARCH64_LR_REGNUM) SELF_CHECK (cache.saved_regs[i].addr () == -40); else - SELF_CHECK (cache.saved_regs[i].is_realreg () - && cache.saved_regs[i].realreg () == i); + { + SELF_CHECK (cache.saved_regs[i].is_realreg ()); + SELF_CHECK (cache.saved_regs[i].realreg () == i); + } } } } @@ -6520,7 +6530,7 @@ aarch64_process_record_test (void) info.bfd_arch_info = bfd_scan_arch ("aarch64"); struct gdbarch *gdbarch = gdbarch_find_by_info (info); - SELF_CHECK (gdbarch != NULL); + SELF_CHECK (gdbarch != nullptr); aarch64_insn_decode_record aarch64_record; diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c index bffd5d221997..a963c8c3f803 100644 --- a/gdb/arm-tdep.c +++ b/gdb/arm-tdep.c @@ -14673,7 +14673,7 @@ arm_record_test (void) struct gdbarch *gdbarch = gdbarch_find_by_info (info); - SELF_CHECK (gdbarch != NULL); + SELF_CHECK (gdbarch != nullptr); /* 16-bit Thumb instructions. */ { @@ -14789,7 +14789,7 @@ arm_analyze_prologue_test () struct gdbarch *gdbarch = gdbarch_find_by_info (info); - SELF_CHECK (gdbarch != NULL); + SELF_CHECK (gdbarch != nullptr); /* The "sub" instruction contains an immediate value rotate count of 0, which resulted in a 32-bit shift of a 32-bit value, caught by diff --git a/gdb/disasm-selftests.c b/gdb/disasm-selftests.c index 485c1245118b..1aa399a57d4a 100644 --- a/gdb/disasm-selftests.c +++ b/gdb/disasm-selftests.c @@ -158,7 +158,7 @@ get_test_insn (struct gdbarch *gdbarch, size_t *len) } gdbarch = gdbarch_find_by_info (info); - SELF_CHECK (gdbarch != NULL); + SELF_CHECK (gdbarch != nullptr); try { diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c index 95e9f1e7c34d..eec1b77c2708 100644 --- a/gdb/dwarf2/frame.c +++ b/gdb/dwarf2/frame.c @@ -568,7 +568,7 @@ execute_cfa_program_test (struct gdbarch *gdbarch) SELF_CHECK (fs.regs.cfa_offset == 4); SELF_CHECK (fs.regs.cfa_how == CFA_REG_OFFSET); SELF_CHECK (fs.regs.cfa_exp.empty ()); - SELF_CHECK (fs.regs.prev == NULL); + SELF_CHECK (fs.regs.prev == nullptr); } } /* namespace selftests */ diff --git a/gdb/memrange.h b/gdb/memrange.h index 18d53a40024c..9b8111dccbcf 100644 --- a/gdb/memrange.h +++ b/gdb/memrange.h @@ -44,6 +44,13 @@ struct mem_range /* Returns true if ADDR is in this mem_range. */ bool contains (CORE_ADDR addr) const; + /* Return a string representation of this mem_range. */ + std::string to_string () const + { + return string_printf ("{start = %s, length = %d}", + hex_string (this->start), this->length); + } + /* Lowest address in the range. */ CORE_ADDR start; diff --git a/gdb/producer.c b/gdb/producer.c index 9e0da6f4eacd..1ece4237fa94 100644 --- a/gdb/producer.c +++ b/gdb/producer.c @@ -242,8 +242,9 @@ producer_parsing_tests () " Intel(R) 64, Version 14.0.1.074 Build 20130716"); int major = 0, minor = 0; - SELF_CHECK (producer_is_icc (extern_f_14_0, &major, &minor) - && major == 14 && minor == 0); + SELF_CHECK (producer_is_icc (extern_f_14_0, &major, &minor)); + SELF_CHECK (major == 14); + SELF_CHECK (minor == 0); SELF_CHECK (!producer_is_gcc (extern_f_14_0, &major, &minor)); } @@ -253,8 +254,9 @@ producer_parsing_tests () " Intel(R) 64, Version 14.0"); int major = 0, minor = 0; - SELF_CHECK (producer_is_icc (intern_f_14, &major, &minor) - && major == 14 && minor == 0); + SELF_CHECK (producer_is_icc (intern_f_14, &major, &minor)); + SELF_CHECK (major == 14); + SELF_CHECK (minor == 0); SELF_CHECK (!producer_is_gcc (intern_f_14, &major, &minor)); } @@ -263,8 +265,9 @@ producer_parsing_tests () = ("Intel(R) C++ Intel(R) 64 Compiler XE for applications running on" " Intel(R) 64, Version 14.0"); int major = 0, minor = 0; - SELF_CHECK (producer_is_icc (intern_c_14, &major, &minor) - && major == 14 && minor == 0); + SELF_CHECK (producer_is_icc (intern_c_14, &major, &minor)); + SELF_CHECK (major == 14); + SELF_CHECK (minor == 0); SELF_CHECK (!producer_is_gcc (intern_c_14, &major, &minor)); } @@ -273,8 +276,9 @@ producer_parsing_tests () = ("Intel(R) C++ Intel(R) 64 Compiler for applications running on" " Intel(R) 64, Version 18.0 Beta"); int major = 0, minor = 0; - SELF_CHECK (producer_is_icc (intern_c_18, &major, &minor) - && major == 18 && minor == 0); + SELF_CHECK (producer_is_icc (intern_c_18, &major, &minor)); + SELF_CHECK (major == 18); + SELF_CHECK (minor == 0); } { @@ -282,16 +286,18 @@ producer_parsing_tests () SELF_CHECK (!producer_is_icc (gnu, nullptr, nullptr)); int major = 0, minor = 0; - SELF_CHECK (producer_is_gcc (gnu, &major, &minor) - && major == 4 && minor == 7); + SELF_CHECK (producer_is_gcc (gnu, &major, &minor)); + SELF_CHECK (major == 4); + SELF_CHECK (minor == 7); } { static const char gnu_exp[] = "GNU C++14 5.0.0 20150123 (experimental)"; int major = 0, minor = 0; SELF_CHECK (!producer_is_icc (gnu_exp, nullptr, nullptr)); - SELF_CHECK (producer_is_gcc (gnu_exp, &major, &minor) - && major == 5 && minor == 0); + SELF_CHECK (producer_is_gcc (gnu_exp, &major, &minor)); + SELF_CHECK (major == 5); + SELF_CHECK (minor == 0); } { @@ -315,7 +321,8 @@ producer_parsing_tests () int major = 0, minor = 0; SELF_CHECK (!producer_is_gcc (gas_exp, &major, &minor)); SELF_CHECK (producer_is_gas (gas_exp, &major, &minor)); - SELF_CHECK (major == 2 && minor == 39); + SELF_CHECK (major == 2); + SELF_CHECK (minor == 39); static const char gas_incomplete_exp[] = "GNU AS "; SELF_CHECK (!producer_is_gas (gas_incomplete_exp, &major, &minor)); diff --git a/gdb/regcache.c b/gdb/regcache.c index 773e8ac37d0a..ed5ea1699840 100644 --- a/gdb/regcache.c +++ b/gdb/regcache.c @@ -1688,7 +1688,7 @@ get_thread_arch_regcache_and_check (inferior *inf_for_target_calls, regcache *regcache = get_thread_arch_regcache (inf_for_target_calls, ptid, arch); - SELF_CHECK (regcache != NULL); + SELF_CHECK (regcache != nullptr); SELF_CHECK (regcache->ptid () == ptid); SELF_CHECK (regcache->arch () == arch); } @@ -2030,7 +2030,7 @@ cooked_read_test (struct gdbarch *gdbarch) || bfd_arch == bfd_arch_m32c || bfd_arch == bfd_arch_mep || bfd_arch == bfd_arch_sh) - SELF_CHECK (status == REG_VALID || status == REG_UNKNOWN); + SELF_CHECK ((status == REG_VALID || status == REG_UNKNOWN)); else if (bfd_arch == bfd_arch_mips || bfd_arch == bfd_arch_h8300) SELF_CHECK (status == REG_UNKNOWN); @@ -2144,7 +2144,7 @@ cooked_write_test (struct gdbarch *gdbarch) { /* If we don't know how to create the expected value for the this type, make it fail. */ - SELF_CHECK (0); + SELF_CHECK (false); } readwrite.cooked_write (regnum, expected); diff --git a/gdb/rust-parse.c b/gdb/rust-parse.c index 0c4f2a501abc..bef247f71c7a 100644 --- a/gdb/rust-parse.c +++ b/gdb/rust-parse.c @@ -2207,7 +2207,7 @@ rust_lex_exception_test (rust_parser *parser, const char *input, { /* The "kind" doesn't matter. */ rust_lex_test_one (parser, input, DECIMAL_INTEGER); - SELF_CHECK (0); + SELF_CHECK (false); } catch (const gdb_exception_error &except) { diff --git a/gdb/selftest-arch.c b/gdb/selftest-arch.c index f8e28f89eca9..9ddad71c6d0e 100644 --- a/gdb/selftest-arch.c +++ b/gdb/selftest-arch.c @@ -64,7 +64,7 @@ foreach_arch_test_generator (const std::string &name, = ([=] () { struct gdbarch *gdbarch = gdbarch_find_by_info (info); - SELF_CHECK (gdbarch != NULL); + SELF_CHECK (gdbarch != nullptr); function (gdbarch); reset (); }); diff --git a/gdb/source-cache.c b/gdb/source-cache.c index 804a6a08a8c3..248805cd534e 100644 --- a/gdb/source-cache.c +++ b/gdb/source-cache.c @@ -500,13 +500,13 @@ static void extract_lines_test () std::string input_text = "abc\ndef\nghi\njkl\n"; std::string result; - SELF_CHECK (extract_lines (input_text, 1, 1, &result) - && result == "abc\n"); + SELF_CHECK (extract_lines (input_text, 1, 1, &result)); + SELF_CHECK (result == "abc\n"); SELF_CHECK (!extract_lines (input_text, 2, 1, &result)); - SELF_CHECK (extract_lines (input_text, 1, 2, &result) - && result == "abc\ndef\n"); - SELF_CHECK (extract_lines ("abc", 1, 1, &result) - && result == "abc"); + SELF_CHECK (extract_lines (input_text, 1, 2, &result)); + SELF_CHECK (result == "abc\ndef\n"); + SELF_CHECK (extract_lines ("abc", 1, 1, &result)); + SELF_CHECK (result == "abc"); } } #endif diff --git a/gdb/unittests/array-view-selftests.c b/gdb/unittests/array-view-selftests.c index ff31912ab03a..854704ed50a0 100644 --- a/gdb/unittests/array-view-selftests.c +++ b/gdb/unittests/array-view-selftests.c @@ -667,6 +667,11 @@ struct foo return this->n == other.n; } + std::string to_string () const + { + return std::to_string (this->n); + } + int n; /* Number of times the assignment operator has been called. */ diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c index b643876b1440..e47c24371aad 100644 --- a/gdb/unittests/environ-selftests.c +++ b/gdb/unittests/environ-selftests.c @@ -41,12 +41,12 @@ test_vector_initialization () /* When the vector is initialized, there should always be one NULL element in it. */ - SELF_CHECK (env.envp ()[0] == NULL); + SELF_CHECK (env.envp ()[0] == nullptr); SELF_CHECK (env.user_set_env ().size () == 0); SELF_CHECK (env.user_unset_env ().size () == 0); /* Make sure that there is no other element. */ - SELF_CHECK (env.get ("PWD") == NULL); + SELF_CHECK (env.get ("PWD") == nullptr); } /* Test initialization using the host's environ. */ @@ -105,7 +105,7 @@ test_set_A_unset_B_unset_A_cannot_find_A_can_find_B () SELF_CHECK (streq (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb")); env.unset ("GDB_SELFTEST_ENVIRON_1"); - SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON_1") == NULL); + SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON_1") == nullptr); SELF_CHECK (streq (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb")); /* The user-set environ var list must contain only one element @@ -127,10 +127,10 @@ test_unset_set_empty_vector () SELF_CHECK (set_contains (env.user_set_env (), std::string ("PWD=test"))); SELF_CHECK (env.user_unset_env ().size () == 0); /* The second element must be NULL. */ - SELF_CHECK (env.envp ()[1] == NULL); + SELF_CHECK (env.envp ()[1] == nullptr); SELF_CHECK (env.user_set_env ().size () == 1); env.unset ("PWD"); - SELF_CHECK (env.envp ()[0] == NULL); + SELF_CHECK (env.envp ()[0] == nullptr); SELF_CHECK (env.user_set_env ().size () == 0); SELF_CHECK (env.user_unset_env ().size () == 1); SELF_CHECK (set_contains (env.user_unset_env (), std::string ("PWD"))); @@ -148,10 +148,10 @@ test_vector_clear () env.set (gdb_selftest_env_var, "1"); env.clear (); - SELF_CHECK (env.envp ()[0] == NULL); + SELF_CHECK (env.envp ()[0] == nullptr); SELF_CHECK (env.user_set_env ().size () == 0); SELF_CHECK (env.user_unset_env ().size () == 0); - SELF_CHECK (env.get (gdb_selftest_env_var) == NULL); + SELF_CHECK (env.get (gdb_selftest_env_var) == nullptr); } /* Test that after a std::move the moved-from object is left at a @@ -169,15 +169,15 @@ test_std_move () SELF_CHECK (env.user_set_env ().size () == 1); env2 = std::move (env); - SELF_CHECK (env.envp ()[0] == NULL); + SELF_CHECK (env.envp ()[0] == nullptr); SELF_CHECK (streq (env2.get ("A"), "1")); - SELF_CHECK (env2.envp ()[1] == NULL); + SELF_CHECK (env2.envp ()[1] == nullptr); SELF_CHECK (env.user_set_env ().size () == 0); SELF_CHECK (set_contains (env2.user_set_env (), std::string ("A=1"))); SELF_CHECK (env2.user_set_env ().size () == 1); env.set ("B", "2"); SELF_CHECK (streq (env.get ("B"), "2")); - SELF_CHECK (env.envp ()[1] == NULL); + SELF_CHECK (env.envp ()[1] == nullptr); } /* Test that the move constructor leaves everything at a valid @@ -193,16 +193,16 @@ test_move_constructor () SELF_CHECK (set_contains (env.user_set_env (), std::string ("A=1"))); gdb_environ env2 = std::move (env); - SELF_CHECK (env.envp ()[0] == NULL); + SELF_CHECK (env.envp ()[0] == nullptr); SELF_CHECK (env.user_set_env ().size () == 0); SELF_CHECK (streq (env2.get ("A"), "1")); - SELF_CHECK (env2.envp ()[1] == NULL); + SELF_CHECK (env2.envp ()[1] == nullptr); SELF_CHECK (set_contains (env2.user_set_env (), std::string ("A=1"))); SELF_CHECK (env2.user_set_env ().size () == 1); env.set ("B", "2"); SELF_CHECK (streq (env.get ("B"), "2")); - SELF_CHECK (env.envp ()[1] == NULL); + SELF_CHECK (env.envp ()[1] == nullptr); SELF_CHECK (set_contains (env.user_set_env (), std::string ("B=2"))); SELF_CHECK (env.user_set_env ().size () == 1); } @@ -229,7 +229,7 @@ test_self_move () SELF_CHECK (streq (env.get ("A"), "1")); SELF_CHECK (streq (env.envp ()[0], "A=1")); - SELF_CHECK (env.envp ()[1] == NULL); + SELF_CHECK (env.envp ()[1] == nullptr); SELF_CHECK (set_contains (env.user_set_env (), std::string ("A=1"))); SELF_CHECK (env.user_set_env ().size () == 1); } @@ -242,7 +242,7 @@ test_set_unset_reset () gdb_environ env = gdb_environ::from_host_environ (); /* Our test variable should already be present in the host's environment. */ - SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") != NULL); + SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") != nullptr); /* Set our test variable to another value. */ env.set ("GDB_SELFTEST_ENVIRON", "test"); @@ -253,7 +253,7 @@ test_set_unset_reset () /* And unset our test variable. The variable still exists in the host's environment, but doesn't exist in our vector. */ env.unset ("GDB_SELFTEST_ENVIRON"); - SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL); + SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == nullptr); SELF_CHECK (env.user_set_env ().size () == 0); SELF_CHECK (env.user_unset_env ().size () == 1); SELF_CHECK (set_contains (env.user_unset_env (), diff --git a/gdb/unittests/frame_info_ptr-selftests.c b/gdb/unittests/frame_info_ptr-selftests.c index dc67ebf80f38..f5d58e4c94cc 100644 --- a/gdb/unittests/frame_info_ptr-selftests.c +++ b/gdb/unittests/frame_info_ptr-selftests.c @@ -30,7 +30,7 @@ validate_user_created_frame (frame_id id) { SELF_CHECK (id.stack_status == FID_STACK_VALID); SELF_CHECK (id.stack_addr == 0x1234); - SELF_CHECK (id.code_addr_p); + SELF_CHECK (id.code_addr_p != 0); SELF_CHECK (id.code_addr == 0x5678); } diff --git a/gdb/unittests/function-view-selftests.c b/gdb/unittests/function-view-selftests.c index 7a61465d440d..4c702d2d5419 100644 --- a/gdb/unittests/function-view-selftests.c +++ b/gdb/unittests/function-view-selftests.c @@ -162,9 +162,9 @@ test_function_view () /* Check the nullptr_t op= */ gdb::function_view check_op_eq_null (plus_one_fn_int); - SELF_CHECK (check_op_eq_null); + SELF_CHECK (check_op_eq_null != nullptr); check_op_eq_null = nullptr; - SELF_CHECK (!check_op_eq_null); + SELF_CHECK (check_op_eq_null == nullptr); } /* A template function where the function_view type is dependent on a diff --git a/gdb/unittests/scoped_fd-selftests.c b/gdb/unittests/scoped_fd-selftests.c index 42cb18f342c0..f1c5667a972d 100644 --- a/gdb/unittests/scoped_fd-selftests.c +++ b/gdb/unittests/scoped_fd-selftests.c @@ -42,7 +42,11 @@ test_destroy () SELF_CHECK (sfd.get () == fd); } - SELF_CHECK (close (fd) == -1 && errno == EBADF); + int ret = close (fd); + int saved_errno = errno; + + SELF_CHECK (ret == -1); + SELF_CHECK (saved_errno == EBADF); } /* Test that the file descriptor can be released. */ @@ -61,7 +65,7 @@ test_release () SELF_CHECK (sfd.release () == fd); } - SELF_CHECK (close (fd) == 0 || errno != EBADF); + SELF_CHECK ((close (fd) == 0 || errno != EBADF)); } /* Test that the file descriptor can be converted to a FILE *. */ diff --git a/gdb/unittests/scoped_ignore_signal-selftests.c b/gdb/unittests/scoped_ignore_signal-selftests.c index 7a0f2c2764fc..b7e67ea84a3a 100644 --- a/gdb/unittests/scoped_ignore_signal-selftests.c +++ b/gdb/unittests/scoped_ignore_signal-selftests.c @@ -77,8 +77,11 @@ test_sigpipe () errno = 0; r = write (fd[1], &c, 1); - SELF_CHECK (r == -1 && errno == EPIPE - && got_sigpipe == expect_sig); + int saved_errno = errno; + + SELF_CHECK (r == -1); + SELF_CHECK (saved_errno == EPIPE); + SELF_CHECK (got_sigpipe == expect_sig); }; /* Check that without a scoped_ignore_sigpipe in scope we indeed get diff --git a/gdb/unittests/scoped_mmap-selftests.c b/gdb/unittests/scoped_mmap-selftests.c index ce03d7559f7a..a2fbc0e1f3e5 100644 --- a/gdb/unittests/scoped_mmap-selftests.c +++ b/gdb/unittests/scoped_mmap-selftests.c @@ -47,7 +47,11 @@ test_destroy () SELF_CHECK (mem != nullptr); } - SELF_CHECK (msync (mem, sysconf (_SC_PAGESIZE), 0) == -1 && errno == ENOMEM); + int ret = msync (mem, sysconf (_SC_PAGESIZE), 0); + int saved_errno = errno; + + SELF_CHECK (ret == -1); + SELF_CHECK (saved_errno == ENOMEM); } /* Test that the memory can be released. */ @@ -65,7 +69,7 @@ test_release () SELF_CHECK (mem != nullptr); } - SELF_CHECK (msync (mem, sysconf (_SC_PAGESIZE), 0) == 0 || errno != ENOMEM); + SELF_CHECK ((msync (mem, sysconf (_SC_PAGESIZE), 0) == 0 || errno != ENOMEM)); munmap (mem, sysconf (_SC_PAGESIZE)); } diff --git a/gdb/unittests/style-selftests.c b/gdb/unittests/style-selftests.c index 9035050bd6ce..632a1e42a4e9 100644 --- a/gdb/unittests/style-selftests.c +++ b/gdb/unittests/style-selftests.c @@ -23,8 +23,14 @@ namespace selftests { namespace style { -#define CHECK_RGB(R, G, B) \ - SELF_CHECK (rgb[0] == (R) && rgb[1] == (G) && rgb[2] == (B)) +#define CHECK_RGB(R, G, B) \ + do \ + { \ + SELF_CHECK (rgb[0] == (R)); \ + SELF_CHECK (rgb[1] == (G)); \ + SELF_CHECK (rgb[2] == (B)); \ + } \ + while (0) static void run_tests () diff --git a/gdb/utils.c b/gdb/utils.c index 5deecdbba942..0ed943cc4929 100644 --- a/gdb/utils.c +++ b/gdb/utils.c @@ -3354,8 +3354,8 @@ gdb_realpath_check_trailer (const char *input, const char *trailer) size_t len = strlen (result.get ()); size_t trail_len = strlen (trailer); - SELF_CHECK (len >= trail_len - && streq (result.get () + len - trail_len, trailer)); + SELF_CHECK (len >= trail_len); + SELF_CHECK (streq (result.get () + len - trail_len, trailer)); } static void diff --git a/gdbserver/server.cc b/gdbserver/server.cc index 74774fcde7ca..9a1a38978b73 100644 --- a/gdbserver/server.cc +++ b/gdbserver/server.cc @@ -4075,7 +4075,9 @@ test_memory_tagging_functions (void) type = 255; strcpy (packet.data (), "qMemTags:0,0:0"); parse_fetch_memtags_request (packet.data (), &addr, &len, &type); - SELF_CHECK (addr == 0 && len == 0 && type == 0); + SELF_CHECK (addr == 0); + SELF_CHECK (len == 0); + SELF_CHECK (type == 0); /* Valid request, addr, len and type updated. */ addr = 0; @@ -4083,7 +4085,9 @@ test_memory_tagging_functions (void) type = 0; strcpy (packet.data (), "qMemTags:deadbeef,ff:5"); parse_fetch_memtags_request (packet.data (), &addr, &len, &type); - SELF_CHECK (addr == 0xdeadbeef && len == 255 && type == 5); + SELF_CHECK (addr == 0xdeadbeef); + SELF_CHECK (len == 255); + SELF_CHECK (type == 5); /* Test creating a qMemTags reply. */ @@ -4107,7 +4111,10 @@ test_memory_tagging_functions (void) strcpy (packet.data (), "QMemTags:0,0:0:"); SELF_CHECK (parse_store_memtags_request (packet.data (), &addr, &len, tags, &type) == true); - SELF_CHECK (addr == 0 && len == 0 && type == 0 && tags.size () == 0); + SELF_CHECK (addr == 0); + SELF_CHECK (len == 0); + SELF_CHECK (type == 0); + SELF_CHECK (tags.size () == 0); /* Valid request and non-empty tag data: addr, len, type and tags updated. */ @@ -4119,8 +4126,10 @@ test_memory_tagging_functions (void) "QMemTags:deadbeef,ff:5:0001020304"); SELF_CHECK (parse_store_memtags_request (packet.data (), &addr, &len, tags, &type) == true); - SELF_CHECK (addr == 0xdeadbeef && len == 255 && type == 5 - && tags.size () == 5); + SELF_CHECK (addr == 0xdeadbeef); + SELF_CHECK (len == 255); + SELF_CHECK (type == 5); + SELF_CHECK (tags.size () == 5); } /* Exercise the behavior of doing a 0-length comparison for a register in a diff --git a/gdbsupport/selftest.cc b/gdbsupport/selftest.cc index 85eee59e3c4b..55cf6d51d418 100644 --- a/gdbsupport/selftest.cc +++ b/gdbsupport/selftest.cc @@ -144,11 +144,84 @@ all_selftests () /* See selftest.h. */ -void -failed_selftest (const char *file, int line, const char *expression) +std::string +detail::quote_string (std::string_view str, char quote) { - throw_error (SELF_CHECK_FAILED_ERROR, "%s:%d: %s", file, line, - expression); + std::string result (1, quote); + + for (char c : str) + switch (c) + { + case '\n': + result += "\\n"; + break; + + case '\t': + result += "\\t"; + break; + + case '\r': + result += "\\r"; + break; + + case '\0': + result += "\\0"; + break; + + case '\\': + result += "\\\\"; + break; + + default: + if (c == quote) + { + result += '\\'; + result += c; + } + else if (c_isprint (c)) + result += c; + else + result += string_printf ("\\x%02x", static_cast (c)); + break; + } + + result += quote; + return result; +} + +/* Return a string describing the check of EXPRESSION at FILE:LINE, used + in the reports of passed and failed checks. EXPANDS_TO may be + nullptr. */ + +static std::string +describe_check (const char *file, int line, const char *expression, + const char *expands_to) +{ + if (expands_to != nullptr) + return string_printf ("%s:%d: %s: %s", file, line, expression, + expands_to); + + return string_printf ("%s:%d: %s", file, line, expression); +} + +/* See selftest.h. */ + +void +passed_selftest (const char *file, int line, const char *expression, + const char *expands_to) +{ + debug_printf ("PASS: %s\n", + describe_check (file, line, expression, expands_to).c_str ()); +} + +/* See selftest.h. */ + +void +failed_selftest (const char *file, int line, const char *expression, + const char *expands_to) +{ + throw_error (SELF_CHECK_FAILED_ERROR, "%s", + describe_check (file, line, expression, expands_to).c_str ()); } } /* namespace selftests */ diff --git a/gdbsupport/selftest.h b/gdbsupport/selftest.h index ddf925dd3524..4c5789e089e2 100644 --- a/gdbsupport/selftest.h +++ b/gdbsupport/selftest.h @@ -19,12 +19,32 @@ #ifndef GDBSUPPORT_SELFTEST_H #define GDBSUPPORT_SELFTEST_H +#include "diagnostics.h" #include "gdbsupport/array-view.h" +#include "gdbsupport/enumerate.h" +#include "gdbsupport/enum-flags.h" #include "gdbsupport/function-view.h" #include "gdbsupport/iterator-range.h" +#include "gdbsupport/packed.h" +#include +#include +#include #include +#include +#include #include +/* The SELF_CHECK macro expands to an expression that triggers -Wparentheses, + which it suppresses locally with DIAGNOSTIC_IGNORE_PARENTHESES. Before + version 10, GCC did not honor diagnostic pragmas coming from macro + expansions, so disable the warning for the whole translation unit instead. + This can be removed once the minimum supported GCC version is at least + 10. */ + +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 10 +# pragma GCC diagnostic ignored "-Wparentheses" +#endif + /* A test is just a function that does some checks and throws an exception if something has gone wrong. */ @@ -88,18 +108,524 @@ extern void reset (); error with code SELF_CHECK_FAILED_ERROR describing the failure. */ [[noreturn]] void failed_selftest (const char *file, int line, - const char *expression); + const char *expression, + const char *expands_to); +/* Function called by macro SELF_CHECK when a selftest passes, in verbose + mode. Print a line describing the check. EXPANDS_TO may be nullptr. */ + +extern void passed_selftest (const char *file, int line, + const char *expression, const char *expands_to); + +namespace detail +{ + +/* Always false, but dependent on T, for use in static_assert. + + Older compilers reject a plain "static_assert (false)" in a template, + even if the template is never instantiated. This was changed by P2593 + ("Allowing static_assert(false)"), implemented as a defect report (so + in C++17 mode too) in GCC 13 and Clang 17. This can be removed once the + minimum supported versions are at least these. */ + +template +constexpr bool dependent_false_v = false; + +/* The result of decomposing a binary comparison (like "a == b") in a + SELF_CHECK. */ + +template +struct binary_expr_holder +{ + template + auto operator&& (const Rhs2T &) const + { + static_assert (dependent_false_v, + "SELF_CHECK does not support a top-level &&: " + "split it into separate SELF_CHECKs, or wrap the " + "whole expression in extra parentheses, e.g. " + "SELF_CHECK ((a && b))."); + } + + template + auto operator|| (const Rhs2T &) const + { + static_assert (dependent_false_v, + "SELF_CHECK does not support a top-level ||: " + "wrap the whole expression in extra parentheses, " + "e.g. SELF_CHECK ((a || b))."); + } + + /* The result of the expression. */ + bool result; + + /* The operator as a string, e.g. "!=". */ + const char *op; + + /* The operands. */ + const LhsT &lhs; + const RhsT &rhs; +}; + +/* The result of decomposing the left operand of the expression of a + SELF_CHECK. */ + +template +struct lhs_holder +{ + template + binary_expr_holder operator== (const RhsT &rhs) const + { + return binary_expr_holder { this->lhs == rhs, "==", this->lhs, + rhs }; + } + + template + binary_expr_holder operator!= (const RhsT &rhs) const + { + return binary_expr_holder { this->lhs != rhs, "!=", this->lhs, + rhs }; + } + + template + binary_expr_holder operator> (const RhsT &rhs) const + { + return binary_expr_holder { this->lhs > rhs, ">", this->lhs, + rhs }; + } + + template + binary_expr_holder operator< (const RhsT &rhs) const + { + return binary_expr_holder { this->lhs < rhs, "<", this->lhs, + rhs }; + } + + template + binary_expr_holder operator<= (const RhsT &rhs) const + { + return binary_expr_holder { this->lhs <= rhs, "<=", this->lhs, + rhs }; + } + + template + binary_expr_holder operator>= (const RhsT &rhs) const + { + return binary_expr_holder { this->lhs >= rhs, ">=", this->lhs, + rhs }; + } + + template + auto operator&& (const RhsT &) const + { + static_assert (dependent_false_v, + "SELF_CHECK does not support a top-level &&: " + "split it into separate SELF_CHECKs, or wrap the " + "whole expression in extra parentheses, e.g. " + "SELF_CHECK ((a && b))."); + } + + template + auto operator|| (const RhsT &) const + { + static_assert (dependent_false_v, + "SELF_CHECK does not support a top-level ||: " + "wrap the whole expression in extra parentheses, " + "e.g. SELF_CHECK ((a || b))."); + } + + /* The left operand. */ + const LhsT &lhs; +}; + +/* Starting point of the decomposition of the expression of a SELF_CHECK. + + The general idea is: + + - SELF_CHECK (a == b) expands to "decomposer () <= a == b" + - "decomposer () <= a" gets evaluated first, yields a lhs_holder + - " == b" gets evaluated, yields a binary_expr_holder + - The resulting binary_expr_holder is passed to the check function + */ + +struct decomposer +{ + template + lhs_holder operator<= (const LhsT &lhs) const + { + return lhs_holder {lhs}; + } +}; + +/* Return STR between QUOTE characters, with QUOTE, special and + non-printable characters escaped. */ + +std::string quote_string (std::string_view str, char quote = '"'); + +/* Trait to detect types that can be iterated on with std::begin and + std::end. */ + +template +struct is_range : std::false_type +{}; + +template +struct is_range ())), + decltype (std::end (std::declval ()))>> + : std::true_type +{}; + +template +constexpr bool is_range_v = is_range::value; + +/* Trait to detect smart pointer types, that is types with a get method + returning a pointer. */ + +template +struct is_smart_pointer : std::false_type +{}; + +template +using get_method_result_t = decltype (std::declval ().get ()); + +template +struct is_smart_pointer + >>> + : std::true_type +{}; + +template +constexpr bool is_smart_pointer_v = is_smart_pointer::value; + +/* Trait to detect std::optional types. */ + +template +struct is_optional : std::false_type +{}; + +template +struct is_optional> : std::true_type +{}; + +template +constexpr bool is_optional_v = is_optional::value; + +/* Trait to detect enum_flags types. */ + +template +struct is_enum_flags : std::false_type +{}; + +template +struct is_enum_flags> : std::true_type +{}; + +template +constexpr bool is_enum_flags_v = is_enum_flags::value; + +/* Trait to detect std::pair types. */ + +template +struct is_pair : std::false_type +{}; + +template +struct is_pair> : std::true_type +{}; + +template +constexpr bool is_pair_v = is_pair::value; + +/* Trait to detect gdb::function_view types. */ + +template +struct is_function_view : std::false_type +{}; + +template +struct is_function_view> : std::true_type +{}; + +template +constexpr bool is_function_view_v = is_function_view::value; + +/* Trait to detect packed types. */ + +template +struct is_packed : std::false_type +{}; + +template +struct is_packed> : std::true_type +{ + using wrapped_type = T; +}; + +template +constexpr bool is_packed_v = is_packed::value; + +/* Trait to detect iterator types, which define an iterator_category + member type. */ + +template +struct is_iterator : std::false_type +{}; + +template +struct is_iterator> + : std::true_type +{}; + +template +constexpr bool is_iterator_v = is_iterator::value; + +/* Trait to detect std::atomic types. */ + +template +struct is_atomic : std::false_type +{}; + +template +struct is_atomic> : std::true_type +{}; + +template +constexpr bool is_atomic_v = is_atomic::value; + +/* Trait to detect types with a to_string method. */ + +template +struct has_to_string_method : std::false_type +{}; + +template +struct has_to_string_method< + T, std::void_t ().to_string ())>> + : std::true_type +{}; + +template +constexpr bool has_to_string_method_v = has_to_string_method::value; + +/* Return a string representation of VAL, used to print the operands of + SELF_CHECKs. Fails to compile if ValT is a type that it doesn't know + how to print. */ + +template +std::string +value_to_string (const ValT &val) +{ + constexpr auto is_array = std::is_array_v; + using element_type = std::remove_cv_t>; + using pointee_type = std::remove_cv_t>; + + if constexpr (is_array && std::is_same_v) + { + /* Print fixed-size character arrays as strings. The array is not + necessarily null-terminated, don't read past its end. */ + constexpr auto extent = std::extent_v; + return quote_string (std::string_view (val, strnlen (val, extent))); + } + else if constexpr (std::is_same_v) + { + /* This must come before the std::string_view case, because nullptr + converts to std::string_view. */ + return "nullptr"; + } + else if constexpr (std::is_pointer_v + && std::is_same_v) + { + /* Char pointer, print as string. This must come before the + std::string_view case, because converting a null pointer to + std::string_view is undefined behavior. */ + if (val == nullptr) + return "nullptr"; + + return quote_string (val); + } + else if constexpr (std::is_convertible_v) + { + /* std::string and friends. */ + return quote_string (val); + } + else if constexpr (std::is_pointer_v) + { + /* Any other pointer. */ + if (val == nullptr) + return "nullptr"; + + return host_address_to_string (val); + } + else if constexpr (std::is_same_v) + return val ? "true" : "false"; + else if constexpr (std::is_same_v) + return quote_string (std::string_view (&val, 1), '\''); + else if constexpr (std::is_integral_v) + return std::to_string (val); + else if constexpr (std::is_enum_v) + { + /* Print the numerical value. Don't go through value_to_string, the + underlying type could be char, and that would print it as a quoted + character. */ + return std::to_string (static_cast> (val)); + } + else if constexpr (is_function_view_v) + { + /* There is nothing interesting to print about the callable. */ + if (val == nullptr) + return "nullptr"; + + return "{function_view}"; + } + else if constexpr (is_atomic_v) + return value_to_string (val.load ()); + else if constexpr (is_packed_v) + { + using wrapped_type = typename is_packed::wrapped_type; + + return value_to_string (static_cast (val)); + } + else if constexpr (is_pair_v) + return ("{" + value_to_string (val.first) + ", " + + value_to_string (val.second) + "}"); + else if constexpr (is_enum_flags_v) + { + /* These are bit flags, so hex is more readable. */ + auto raw = static_cast (val); + + return string_printf ("0x%llx", static_cast (raw)); + } + else if constexpr (has_to_string_method_v) + { + auto str = val.to_string (); + + /* Some to_string methods return a const char *, print it like + any other value of that type. */ + if constexpr (std::is_same_v) + return str; + else + return value_to_string (str); + } + else if constexpr (is_optional_v) + { + if (!val.has_value ()) + return "std::nullopt"; + + return value_to_string (*val); + } + else if constexpr (is_smart_pointer_v) + return value_to_string (val.get ()); + else if constexpr (is_range_v) + { + /* Don't print too many elements, to keep the message readable. */ + constexpr std::size_t max_elements = 32; + std::string result = "{"; + + for (auto [i, elem] : gdb::ranges::views::enumerate (val)) + { + if (i > 0) + result += ", "; + + if (i == max_elements) + { + result += "..."; + break; + } + + result += value_to_string (elem); + } + + result += "}"; + return result; + } + else if constexpr (is_iterator_v) + { + /* Iterators can't be printed meaningfully, and dereferencing them is + not always valid. */ + return "{iterator}"; + } + else + { + static_assert (dependent_false_v, + "value_to_string: don't know how to print this type, add " + "a to_string method to it or a case in value_to_string"); + return {}; + } +} + +/* Report a passed (if running verbose) or failed selftest, based on the + result in EXPR. This function handles the binary expressions like: + + SELF_CHECK (a == b) */ + +template +void +check (const binary_expr_holder &expr, const char *file, int line, + const char *expression) +{ + auto expands_to = [&] () + { + return string_printf ("%s %s %s", + value_to_string (expr.lhs).c_str (), expr.op, + value_to_string (expr.rhs).c_str ()); + }; + + if (!expr.result) + failed_selftest (file, line, expression, expands_to ().c_str ()); + else if (run_verbose ()) + passed_selftest (file, line, expression, expands_to ().c_str ()); +} + +/* Report a passed (if running verbose) or failed selftest, based on the + result in EXPR. This function handles the unary expressions like: + + SELF_CHECK (some_bool_expression) + + This function could be templated and accept anything that converts to bool, + but our coding style is to use explicit comparison, so we might as well + enforce that. */ + +inline void +check (const lhs_holder &expr, const char *file, int line, + const char *expression) +{ + if (!expr.lhs) + failed_selftest (file, line, expression, nullptr); + else if (run_verbose ()) + passed_selftest (file, line, expression, nullptr); +} + +/* Reject non-bool values, like: + + SELF_CHECK (ptr) + + See the bool overload above. The point of this function is to give a clear + error message. */ + +template +void +check (const lhs_holder &, const char *, int, const char *) +{ + static_assert (dependent_false_v, + "SELF_CHECK on a non-bool value: use an explicit comparison, " + "e.g. SELF_CHECK (ptr != nullptr)."); +} + +} /* namespace detail */ } /* Check that VALUE is true, and, if not, throw an exception. */ -#define SELF_CHECK(VALUE) \ - do \ - { \ - if (!(VALUE)) \ - selftests::failed_selftest (__FILE__, __LINE__, #VALUE); \ - } \ +#define SELF_CHECK(VALUE) \ + do \ + { \ + DIAGNOSTIC_PUSH \ + DIAGNOSTIC_IGNORE_PARENTHESES \ + selftests::detail::check (selftests::detail::decomposer () <= VALUE, \ + __FILE__, __LINE__, #VALUE); \ + DIAGNOSTIC_POP \ + } \ while (0) #endif /* GDBSUPPORT_SELFTEST_H */ -- 2.56.0