On Thu, Oct 16, 2025 at 1:24 PM Jonathan Wakely <[email protected]> wrote:
> 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
>
> For a non-empty stacktrace_entry, if the function name returned by
> description() is an empty string we now print "<unknown>" for the
> function name. For an empty (e.g. default constructed) stacktrace_entry
> the entire string representation is now "<unknown>" instead of an empty
> string.
>
> Instead of printing "<unknown>" for the function name, we could set that
> string in the stacktrace_entry::_Info object, so that description()
> returns "<unknown>" and then operator<< wouldn't need to handle an empty
> description() string. However, returning an empty string from that
> function seems simpler for users to detect, rather than having to parse
> "<unknown>".
>
> We could also choose a different string for an empty stacktrace_entry,
> maybe "<none>" or "<invalid>", but "<unknown>" seems OK for now.
>
> libstdc++-v3/ChangeLog:
>
> * include/std/stacktrace
> (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.
> ---
>
> v2: Restore fmtflags as pointed out by Nathan. Adjust control flow in
> operator<< as suggested by Tomasz.
>
> Tested x86_64-linux.
>
> libstdc++-v3/include/std/stacktrace | 29 +++++++++++++++++++++++++----
> 1 file changed, 25 insertions(+), 4 deletions(-)
>
> diff --git a/libstdc++-v3/include/std/stacktrace
> b/libstdc++-v3/include/std/stacktrace
> index b9e260e19f89..28ffda76328f 100644
> --- a/libstdc++-v3/include/std/stacktrace
> +++ b/libstdc++-v3/include/std/stacktrace
> @@ -685,11 +685,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> {
> 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()) [[likely]]
> + return __os << __file << ':' << __line;
> + // else fall through and write native_handle() as the location.
> }
> +
> + if (__f) [[likely]]
> + {
> + struct _Flag_guard
> + {
> + ios_base& _M_ios;
> + ios_base::fmtflags _M_f;
> + ~_Flag_guard() { _M_ios.setf(_M_f); }
> + };
> + _Flag_guard _ = { __os, __os.setf(ios::hex, ios::basefield) };
> + __os << "[0x" << __f.native_handle() << ']';
> + }
> + else
> + __os << "<unknown>";
> return __os;
>
I know that I ask for another change ag, but if (!__f) we will not ever get
info for it, so I would prefer:
if (!__f) [[unlikelly]
return __os << "<unknown>";
string __desc, __file;
int __line;
if (__f._M_get_info(&__desc, &__file, &__line)) [[likely]]
{
// unchanged
}
struct _Flag_guard
{
ios_base& _M_ios;
ios_base::fmtflags _M_f;
~_Flag_guard() { _M_ios.setf(_M_f); }
};
_Flag_guard _ = { __os, __os.setf(ios::hex, ios::basefield) };
__os << "[0x" << __f.native_handle() << ']';
return __os;
This way it is clear, that we never produce <unknown> at <unknown>
> }
>
> @@ -697,7 +717,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
>
>