On Wed, Oct 15, 2025 at 11:05 PM Jonathan Wakely <[email protected]> wrote:
> Instead of printing "<unknown>" in the ostream output, we could set that
> string in the stacktrace_entry::_Info object, so that description()
> returns that. Returning an empty string from that function seems simpler
> for users to handle and decide on their own behaviour, rather than
> having to parse "<unknown>".
>
> With this change locations that don't have a source file look like this:
>
> 18# __libc_start_call_main at [0x7fd6568f6574]
> 19# __libc_start_main@GLIBC_2.2.5 at [0x7fd6568f6627]
> 20# _start at [0x4006a4]
>
> Instead of:
>
> 18# __libc_start_call_main at :0
> 19# __libc_start_main@GLIBC_2.2.5 at :0
> 20# _start at :0
>
> The string representation of a default constructed stacktrace_entry is
> now "<unknown>" instead of an empty string.
>
> libstdc++-v3/ChangeLog:
>
> * include/std/stacktrace (stacktrace_entry::_Print_pc): New
> struct for writing native handle to ostream.
> (operator<<(ostream&, const stacktrace_entry&)): Improve output
> when description() or source_file() returns an empty string,
> or the stacktrace_entry is invalid.
> (operator<<(ostream&, const basic_stacktrace<A>&)): Use
> size_type of the correct specialization.
> ---
>
> Tested x86_64-linux.
>
> Any ideas for improving the output further?
>
> We need to implement std::formatter specializations for these types too,
> which could just call the to_string overloads, or could do something
> smarter.
>
> libstdc++-v3/include/std/stacktrace | 34 +++++++++++++++++++++++++----
> 1 file changed, 30 insertions(+), 4 deletions(-)
>
> diff --git a/libstdc++-v3/include/std/stacktrace
> b/libstdc++-v3/include/std/stacktrace
> index b9e260e19f89..61aea57ec0d3 100644
> --- a/libstdc++-v3/include/std/stacktrace
> +++ b/libstdc++-v3/include/std/stacktrace
> @@ -134,6 +134,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> friend ostream&
> operator<<(ostream&, const stacktrace_entry&);
>
> + struct _Print_pc
> + {
> + native_handle_type _M_pc;
> +
> + friend ostream&
> + operator<<(ostream& __os, _Print_pc __pc)
> + {
> + if (__pc._M_pc != (native_handle_type)-1) [[likely]]
> + __os << "[0x" << std::hex << __pc._M_pc << std::dec << ']';
> + else
> + __os << "<unknown>";
> + return __os;
> + }
> + };
> +
> // Type-erased wrapper for the fields of a stacktrace entry.
> // This type is independent of which std::string ABI is in use.
> struct _Info
> @@ -683,13 +698,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> inline ostream&
> operator<<(ostream& __os, const stacktrace_entry& __f)
> {
> + using _Pc = stacktrace_entry::_Print_pc;
> string __desc, __file;
> int __line;
> - if (__f._M_get_info(&__desc, &__file, &__line))
> + if (__f._M_get_info(&__desc, &__file, &__line)) [[likely]]
> {
> - __os.width(4);
> - __os << __desc << " at " << __file << ':' << __line;
> + if (__desc.empty()) [[unlikely]]
> + __os << "<unknown>";
> + else
> + __os << __desc;
> + __os << string_view(" at ");
> + if (__file.empty()) [[unlikely]]
> + __os << _Pc{__f.native_handle()};
> + else
> + __os << __file << ':' << __line;
> }
> + else
> + __os << _Pc{__f.native_handle()};
>
I think I would prefer following formulation:
if (__f._M_get_info(&__desc, &__file, &__line)) [[likely]]
{
if (__desc.empty()) [[unlikely]]
__os << "<unknown>";
else
__os << __desc;
__os << string_view(" at ");
if (!__file.empty()) [[likely]]
return __os << __file << ':' << __line;
}
if (auto __pc = __f.native_handle(); __pc != (native_handle_type)-1)
[[likely]]
__os << "[0x" << std::hex << __pc << std::dec << ']';
else
__os << "<unknown>";
return __os;
return __os;
> }
>
> @@ -697,7 +722,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> inline ostream&
> operator<<(ostream& __os, const basic_stacktrace<_Allocator>& __st)
> {
> - for (stacktrace::size_type __i = 0; __i < __st.size(); ++__i)
> + using size_type = typename basic_stacktrace<_Allocator>::size_type;
> + for (size_type __i = 0, __size = __st.size(); __i < __size; ++__i)
> {
> __os.width(4);
> __os << __i << "# " << __st[__i] << '\n';
> --
> 2.51.0
>
>