* [PATCH 5/6] gdbsupport/selftest: print operand values when a SELF_CHECK fails
2026-10-19 16:58 [PATCH 0/6] Make SELF_CHECK print operands of comparisons Simon Marchi
` (3 preceding siblings ...)
2026-10-19 16:58 ` [PATCH 4/6] gdb/gmp-utils: rename gdb_mp{z,q,f}::str to to_string Simon Marchi
@ 2026-10-19 16:58 ` Simon Marchi
2026-10-27 14:13 ` Tom Tromey
2026-10-19 16:58 ` [PATCH 6/6] gdb: remove manual printing of values in some selftests Simon Marchi
5 siblings, 1 reply; 9+ messages in thread
From: Simon Marchi @ 2026-10-19 16:58 UTC (permalink / raw)
To: gcc-patches, gdb-patches; +Cc: Simon Marchi
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<typename LhsT>
lhs_holder<LhsT> operator<= (const LhsT &lhs) const
{
return lhs_holder<LhsT> {lhs};
}
The lhs_holder then implements all the comparison operators, such as
operator==, which does the requested comparison with the following
operand:
template<typename RhsT>
binary_expr_holder<LhsT, RhsT> operator== (const RhsT &rhs) const
{
return binary_expr_holder<LhsT, RhsT> { 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<bool>, 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<bool>, 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<int (int)> 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<unsigned char> (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 <atomic>
+#include <iterator>
+#include <optional>
#include <set>
+#include <string_view>
+#include <utility>
#include <vector>
+/* 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<typename T>
+constexpr bool dependent_false_v = false;
+
+/* The result of decomposing a binary comparison (like "a == b") in a
+ SELF_CHECK. */
+
+template<typename LhsT, typename RhsT>
+struct binary_expr_holder
+{
+ template<typename Rhs2T>
+ auto operator&& (const Rhs2T &) const
+ {
+ static_assert (dependent_false_v<Rhs2T>,
+ "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<typename Rhs2T>
+ auto operator|| (const Rhs2T &) const
+ {
+ static_assert (dependent_false_v<Rhs2T>,
+ "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<typename LhsT>
+struct lhs_holder
+{
+ template<typename RhsT>
+ binary_expr_holder<LhsT, RhsT> operator== (const RhsT &rhs) const
+ {
+ return binary_expr_holder<LhsT, RhsT> { this->lhs == rhs, "==", this->lhs,
+ rhs };
+ }
+
+ template<typename RhsT>
+ binary_expr_holder<LhsT, RhsT> operator!= (const RhsT &rhs) const
+ {
+ return binary_expr_holder<LhsT, RhsT> { this->lhs != rhs, "!=", this->lhs,
+ rhs };
+ }
+
+ template<typename RhsT>
+ binary_expr_holder<LhsT, RhsT> operator> (const RhsT &rhs) const
+ {
+ return binary_expr_holder<LhsT, RhsT> { this->lhs > rhs, ">", this->lhs,
+ rhs };
+ }
+
+ template<typename RhsT>
+ binary_expr_holder<LhsT, RhsT> operator< (const RhsT &rhs) const
+ {
+ return binary_expr_holder<LhsT, RhsT> { this->lhs < rhs, "<", this->lhs,
+ rhs };
+ }
+
+ template<typename RhsT>
+ binary_expr_holder<LhsT, RhsT> operator<= (const RhsT &rhs) const
+ {
+ return binary_expr_holder<LhsT, RhsT> { this->lhs <= rhs, "<=", this->lhs,
+ rhs };
+ }
+
+ template<typename RhsT>
+ binary_expr_holder<LhsT, RhsT> operator>= (const RhsT &rhs) const
+ {
+ return binary_expr_holder<LhsT, RhsT> { this->lhs >= rhs, ">=", this->lhs,
+ rhs };
+ }
+
+ template<typename RhsT>
+ auto operator&& (const RhsT &) const
+ {
+ static_assert (dependent_false_v<RhsT>,
+ "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<typename RhsT>
+ auto operator|| (const RhsT &) const
+ {
+ static_assert (dependent_false_v<RhsT>,
+ "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
+ - "<lhs_holder> == b" gets evaluated, yields a binary_expr_holder
+ - The resulting binary_expr_holder is passed to the check function
+ */
+
+struct decomposer
+{
+ template<typename LhsT>
+ lhs_holder<LhsT> operator<= (const LhsT &lhs) const
+ {
+ return lhs_holder<LhsT> {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<typename T, typename = void>
+struct is_range : std::false_type
+{};
+
+template<typename T>
+struct is_range<T,
+ std::void_t<decltype (std::begin (std::declval<const T &> ())),
+ decltype (std::end (std::declval<const T &> ()))>>
+ : std::true_type
+{};
+
+template<typename T>
+constexpr bool is_range_v = is_range<T>::value;
+
+/* Trait to detect smart pointer types, that is types with a get method
+ returning a pointer. */
+
+template<typename T, typename = void>
+struct is_smart_pointer : std::false_type
+{};
+
+template<typename T>
+using get_method_result_t = decltype (std::declval<const T &> ().get ());
+
+template<typename T>
+struct is_smart_pointer
+ <T, std::enable_if_t<std::is_pointer_v<get_method_result_t<T>>>>
+ : std::true_type
+{};
+
+template<typename T>
+constexpr bool is_smart_pointer_v = is_smart_pointer<T>::value;
+
+/* Trait to detect std::optional types. */
+
+template<typename T>
+struct is_optional : std::false_type
+{};
+
+template<typename T>
+struct is_optional<std::optional<T>> : std::true_type
+{};
+
+template<typename T>
+constexpr bool is_optional_v = is_optional<T>::value;
+
+/* Trait to detect enum_flags types. */
+
+template<typename T>
+struct is_enum_flags : std::false_type
+{};
+
+template<typename E>
+struct is_enum_flags<enum_flags<E>> : std::true_type
+{};
+
+template<typename T>
+constexpr bool is_enum_flags_v = is_enum_flags<T>::value;
+
+/* Trait to detect std::pair types. */
+
+template<typename T>
+struct is_pair : std::false_type
+{};
+
+template<typename T1, typename T2>
+struct is_pair<std::pair<T1, T2>> : std::true_type
+{};
+
+template<typename T>
+constexpr bool is_pair_v = is_pair<T>::value;
+
+/* Trait to detect gdb::function_view types. */
+
+template<typename T>
+struct is_function_view : std::false_type
+{};
+
+template<typename Sig>
+struct is_function_view<gdb::function_view<Sig>> : std::true_type
+{};
+
+template<typename T>
+constexpr bool is_function_view_v = is_function_view<T>::value;
+
+/* Trait to detect packed types. */
+
+template<typename T>
+struct is_packed : std::false_type
+{};
+
+template<typename T, size_t Bytes>
+struct is_packed<packed<T, Bytes>> : std::true_type
+{
+ using wrapped_type = T;
+};
+
+template<typename T>
+constexpr bool is_packed_v = is_packed<T>::value;
+
+/* Trait to detect iterator types, which define an iterator_category
+ member type. */
+
+template<typename T, typename = void>
+struct is_iterator : std::false_type
+{};
+
+template<typename T>
+struct is_iterator<T, std::void_t<typename T::iterator_category>>
+ : std::true_type
+{};
+
+template<typename T>
+constexpr bool is_iterator_v = is_iterator<T>::value;
+
+/* Trait to detect std::atomic types. */
+
+template<typename T>
+struct is_atomic : std::false_type
+{};
+
+template<typename T>
+struct is_atomic<std::atomic<T>> : std::true_type
+{};
+
+template<typename T>
+constexpr bool is_atomic_v = is_atomic<T>::value;
+
+/* Trait to detect types with a to_string method. */
+
+template<typename T, typename = void>
+struct has_to_string_method : std::false_type
+{};
+
+template<typename T>
+struct has_to_string_method<
+ T, std::void_t<decltype (std::declval<const T &> ().to_string ())>>
+ : std::true_type
+{};
+
+template<typename T>
+constexpr bool has_to_string_method_v = has_to_string_method<T>::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<typename ValT>
+std::string
+value_to_string (const ValT &val)
+{
+ constexpr auto is_array = std::is_array_v<ValT>;
+ using element_type = std::remove_cv_t<std::remove_extent_t<ValT>>;
+ using pointee_type = std::remove_cv_t<std::remove_pointer_t<ValT>>;
+
+ if constexpr (is_array && std::is_same_v<element_type, char>)
+ {
+ /* 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<ValT>;
+ return quote_string (std::string_view (val, strnlen (val, extent)));
+ }
+ else if constexpr (std::is_same_v<ValT, std::nullptr_t>)
+ {
+ /* 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<ValT>
+ && std::is_same_v<pointee_type, char>)
+ {
+ /* 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<const ValT &, std::string_view>)
+ {
+ /* std::string and friends. */
+ return quote_string (val);
+ }
+ else if constexpr (std::is_pointer_v<ValT>)
+ {
+ /* Any other pointer. */
+ if (val == nullptr)
+ return "nullptr";
+
+ return host_address_to_string (val);
+ }
+ else if constexpr (std::is_same_v<ValT, bool>)
+ return val ? "true" : "false";
+ else if constexpr (std::is_same_v<ValT, char>)
+ return quote_string (std::string_view (&val, 1), '\'');
+ else if constexpr (std::is_integral_v<ValT>)
+ return std::to_string (val);
+ else if constexpr (std::is_enum_v<ValT>)
+ {
+ /* 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<std::underlying_type_t<ValT>> (val));
+ }
+ else if constexpr (is_function_view_v<ValT>)
+ {
+ /* There is nothing interesting to print about the callable. */
+ if (val == nullptr)
+ return "nullptr";
+
+ return "{function_view}";
+ }
+ else if constexpr (is_atomic_v<ValT>)
+ return value_to_string (val.load ());
+ else if constexpr (is_packed_v<ValT>)
+ {
+ using wrapped_type = typename is_packed<ValT>::wrapped_type;
+
+ return value_to_string (static_cast<wrapped_type> (val));
+ }
+ else if constexpr (is_pair_v<ValT>)
+ return ("{" + value_to_string (val.first) + ", "
+ + value_to_string (val.second) + "}");
+ else if constexpr (is_enum_flags_v<ValT>)
+ {
+ /* These are bit flags, so hex is more readable. */
+ auto raw = static_cast<typename ValT::underlying_type> (val);
+
+ return string_printf ("0x%llx", static_cast<unsigned long long> (raw));
+ }
+ else if constexpr (has_to_string_method_v<ValT>)
+ {
+ 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<decltype (str), std::string>)
+ return str;
+ else
+ return value_to_string (str);
+ }
+ else if constexpr (is_optional_v<ValT>)
+ {
+ if (!val.has_value ())
+ return "std::nullopt";
+
+ return value_to_string (*val);
+ }
+ else if constexpr (is_smart_pointer_v<ValT>)
+ return value_to_string (val.get ());
+ else if constexpr (is_range_v<ValT>)
+ {
+ /* 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<ValT>)
+ {
+ /* Iterators can't be printed meaningfully, and dereferencing them is
+ not always valid. */
+ return "{iterator}";
+ }
+ else
+ {
+ static_assert (dependent_false_v<ValT>,
+ "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<typename LhsT, typename RhsT>
+void
+check (const binary_expr_holder<LhsT, RhsT> &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<bool> &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<typename LhsT>
+void
+check (const lhs_holder<LhsT> &, const char *, int, const char *)
+{
+ static_assert (dependent_false_v<LhsT>,
+ "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
^ permalink raw reply [flat|nested] 9+ messages in thread