From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2181) id EAD403857C53; Tue, 12 Mar 2024 14:17:56 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org EAD403857C53 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1710253076; bh=C2J7bMlA2vyIMOPAfca98p8CYym/2UTufS8Wrz/BOtc=; h=From:To:Subject:Date:From; b=GRdmf8Sz6UvVSdVzgXyRxwLfsH3sF1ewvlHSKt+6du4+ZCB/5cpTZYzXZZ52yPitA ub4+MYk+Pk2SUaiEff8Afi3q4D71A5+f9F3wxjYYLfzSWgdin/yJEZMubVj0zmPJ5T JyNjeR2L9bQh8ia44Gdx/5ZFmuRehmkm/TrTiCr4= MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset="utf-8" From: Jonathan Wakely To: gcc-cvs@gcc.gnu.org, libstdc++-cvs@gcc.gnu.org Subject: [gcc r13-8429] libstdc++: Implement P2905R2 "Runtime format strings" for C++20 X-Act-Checkin: gcc X-Git-Author: Jonathan Wakely X-Git-Refname: refs/heads/releases/gcc-13 X-Git-Oldrev: 2d3cc6806a9fc3c9ac299bb021819bcb5e7605ea X-Git-Newrev: 3c8faeac3d03e032d55fae390618e577c292a83e Message-Id: <20240312141756.EAD403857C53@sourceware.org> Date: Tue, 12 Mar 2024 14:17:56 +0000 (GMT) List-Id: https://gcc.gnu.org/g:3c8faeac3d03e032d55fae390618e577c292a83e commit r13-8429-g3c8faeac3d03e032d55fae390618e577c292a83e Author: Jonathan Wakely Date: Sun Jan 7 22:21:08 2024 +0000 libstdc++: Implement P2905R2 "Runtime format strings" for C++20 This change makes std::make_format_args refuse to create dangling references to temporaries. This makes the std::vformat API safer. This was approved in Kona 2023 as a DR for C++20 so the change is implemented unconditionally. libstdc++-v3/ChangeLog: * include/bits/chrono_io.h (__formatter_chrono): Always use lvalue arguments to make_format_args. * include/std/format (make_format_args): Change parameter pack from forwarding references to lvalue references. Remove use of remove_reference_t which is now unnecessary. (format_to, formatted_size): Remove incorrect forwarding of arguments. * testsuite/20_util/duration/io.cc: Use lvalues as arguments to make_format_args. * testsuite/std/format/arguments/args.cc: Likewise. * testsuite/std/format/arguments/lwg3810.cc: Likewise. * testsuite/std/format/functions/format.cc: Likewise. * testsuite/std/format/functions/vformat_to.cc: Likewise. * testsuite/std/format/string.cc: Likewise. * testsuite/std/time/day/io.cc: Likewise. * testsuite/std/time/month/io.cc: Likewise. * testsuite/std/time/weekday/io.cc: Likewise. * testsuite/std/time/year/io.cc: Likewise. * testsuite/std/time/year_month_day/io.cc: Likewise. * testsuite/std/format/arguments/args_neg.cc: New test. (cherry picked from commit 2a8ee2592e48735d88df786cbafa6b0da39fc4d6) Diff: --- libstdc++-v3/include/bits/chrono_io.h | 15 +++++++---- libstdc++-v3/include/std/format | 30 +++++++++++----------- libstdc++-v3/testsuite/20_util/duration/io.cc | 3 ++- .../testsuite/std/format/arguments/args.cc | 26 ++++++++++++++----- .../testsuite/std/format/arguments/args_neg.cc | 12 +++++++++ .../testsuite/std/format/arguments/lwg3810.cc | 8 ++++-- .../testsuite/std/format/functions/format.cc | 6 +++-- .../testsuite/std/format/functions/vformat_to.cc | 9 +++++-- libstdc++-v3/testsuite/std/format/string.cc | 7 +++-- libstdc++-v3/testsuite/std/time/day/io.cc | 4 +-- libstdc++-v3/testsuite/std/time/month/io.cc | 4 +-- libstdc++-v3/testsuite/std/time/weekday/io.cc | 4 +-- libstdc++-v3/testsuite/std/time/year/io.cc | 4 +-- .../testsuite/std/time/year_month_day/io.cc | 4 +-- 14 files changed, 91 insertions(+), 45 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index c42797f64c4..1c08130bf65 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -2195,7 +2195,8 @@ namespace chrono _Str __s = _GLIBCXX_WIDEN("{:02d} is not a valid day"); if (__d.ok()) __s = __s.substr(0, 6); - __os << std::vformat(__s, make_format_args<_Ctx>((unsigned)__d)); + auto __u = (unsigned)__d; + __os << std::vformat(__s, make_format_args<_Ctx>(__u)); return __os; } @@ -2213,8 +2214,10 @@ namespace chrono __os << std::vformat(__os.getloc(), __s.substr(0, 6), make_format_args<_Ctx>(__m)); else - __os << std::vformat(__s.substr(6), - make_format_args<_Ctx>((unsigned)__m)); + { + auto __u = (unsigned)__m; + __os << std::vformat(__s.substr(6), make_format_args<_Ctx>(__u)); + } return __os; } @@ -2253,8 +2256,10 @@ namespace chrono __os << std::vformat(__os.getloc(), __s.substr(0, 6), make_format_args<_Ctx>(__wd)); else - __os << std::vformat(__s.substr(6), - make_format_args<_Ctx>(__wd.c_encoding())); + { + auto __c = __wd.c_encoding(); + __os << std::vformat(__s.substr(6), make_format_args<_Ctx>(__c)); + } return __os; } diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 807c97680c6..7bcaddb3715 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -3117,7 +3117,7 @@ namespace __format template friend auto - make_format_args(_Argz&&...) noexcept; + make_format_args(_Argz&...) noexcept; template friend decltype(auto) @@ -3287,7 +3287,7 @@ namespace __format template friend auto - make_format_args(_Args&&...) noexcept; + make_format_args(_Args&...) noexcept; // An array of _Arg_t enums corresponding to _Args... template @@ -3325,7 +3325,7 @@ namespace __format template auto - make_format_args(_Args&&... __fmt_args) noexcept; + make_format_args(_Args&... __fmt_args) noexcept; // An array of type-erased formatting arguments. template @@ -3338,7 +3338,7 @@ namespace __format #if _GLIBCXX_INLINE_VERSION __8:: // Needed for PR c++/59256 #endif - make_format_args(_Argz&&...) noexcept; + make_format_args(_Argz&...) noexcept; // For a sufficiently small number of arguments we only store values. // basic_format_args can get the types from the _Args pack. @@ -3412,11 +3412,11 @@ namespace __format template [[nodiscard,__gnu__::__always_inline__]] inline auto - make_format_args(_Args&&... __fmt_args) noexcept + make_format_args(_Args&... __fmt_args) noexcept { using _Fmt_arg = basic_format_arg<_Context>; using _Store = __format::_Arg_store<_Context, typename _Fmt_arg::template - _Normalize>...>; + _Normalize<_Args>...>; return _Store(__fmt_args...); } @@ -3424,7 +3424,7 @@ namespace __format template [[nodiscard,__gnu__::__always_inline__]] inline auto - make_wformat_args(_Args&&... __args) noexcept + make_wformat_args(_Args&... __args) noexcept { return std::make_format_args(__args...); } /// @cond undocumented @@ -3857,7 +3857,7 @@ namespace __format format_to(_Out __out, format_string<_Args...> __fmt, _Args&&... __args) { return std::vformat_to(std::move(__out), __fmt.get(), - std::make_format_args(std::forward<_Args>(__args)...)); + std::make_format_args(__args...)); } template @@ -3866,7 +3866,7 @@ namespace __format format_to(_Out __out, wformat_string<_Args...> __fmt, _Args&&... __args) { return std::vformat_to(std::move(__out), __fmt.get(), - std::make_wformat_args(std::forward<_Args>(__args)...)); + std::make_wformat_args(__args...)); } template @@ -3876,7 +3876,7 @@ namespace __format _Args&&... __args) { return std::vformat_to(std::move(__out), __loc, __fmt.get(), - std::make_format_args(std::forward<_Args>(__args)...)); + std::make_format_args(__args...)); } template @@ -3886,7 +3886,7 @@ namespace __format _Args&&... __args) { return std::vformat_to(std::move(__out), __loc, __fmt.get(), - std::make_wformat_args(std::forward<_Args>(__args)...)); + std::make_wformat_args(__args...)); } template @@ -3988,7 +3988,7 @@ namespace __format { __format::_Counting_sink __buf; std::vformat_to(__buf.out(), __fmt.get(), - std::make_format_args(std::forward<_Args>(__args)...)); + std::make_format_args(__args...)); return __buf.count(); } @@ -3999,7 +3999,7 @@ namespace __format { __format::_Counting_sink __buf; std::vformat_to(__buf.out(), __fmt.get(), - std::make_wformat_args(std::forward<_Args>(__args)...)); + std::make_wformat_args(__args...)); return __buf.count(); } @@ -4011,7 +4011,7 @@ namespace __format { __format::_Counting_sink __buf; std::vformat_to(__buf.out(), __loc, __fmt.get(), - std::make_format_args(std::forward<_Args>(__args)...)); + std::make_format_args(__args...)); return __buf.count(); } @@ -4023,7 +4023,7 @@ namespace __format { __format::_Counting_sink __buf; std::vformat_to(__buf.out(), __loc, __fmt.get(), - std::make_wformat_args(std::forward<_Args>(__args)...)); + std::make_wformat_args(__args...)); return __buf.count(); } diff --git a/libstdc++-v3/testsuite/20_util/duration/io.cc b/libstdc++-v3/testsuite/20_util/duration/io.cc index 5f0f25eac69..2043f0c4e9d 100644 --- a/libstdc++-v3/testsuite/20_util/duration/io.cc +++ b/libstdc++-v3/testsuite/20_util/duration/io.cc @@ -83,7 +83,8 @@ test_format() char fmt[] = { '{', ':', '%', c, '}' }; try { - (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(1s)); + auto s = 1s; + (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(s)); // The call above should throw for any conversion-spec not in my_specs: VERIFY(my_specs.find(c) != my_specs.npos); } diff --git a/libstdc++-v3/testsuite/std/format/arguments/args.cc b/libstdc++-v3/testsuite/std/format/arguments/args.cc index ae2eab6d560..e2e7a3e7ee5 100644 --- a/libstdc++-v3/testsuite/std/format/arguments/args.cc +++ b/libstdc++-v3/testsuite/std/format/arguments/args.cc @@ -47,7 +47,12 @@ struct std::formatter : std::formatter void test_args() { - auto store = std::make_format_args(false, 1, '2', 3.4); + bool b = false; + int i = 1; + char c = '2'; + double d = 3.4; + + auto store = std::make_format_args(b, i, c, d); std::format_args args = store; VERIFY(equals(args.get(0), false)); VERIFY(equals(args.get(1), 1)); @@ -55,7 +60,11 @@ test_args() VERIFY(equals(args.get(3), 3.4)); VERIFY(!args.get(4)); - auto cstore = std::make_format_args(5L, 6ULL, 7.8f); + long l = 5L; + unsigned long long ull = 6ULL; + float f = 7.8f; + + auto cstore = std::make_format_args(l, ull, f); std::format_args cargs = cstore; if constexpr (sizeof(long) == sizeof(int)) VERIFY(equals(cargs.get(0), 5)); @@ -65,14 +74,17 @@ test_args() VERIFY(equals(cargs.get(2), 7.8f)); VERIFY(!cargs.get(3)); - VERIFY(equals(std::format_args(std::make_format_args(std::string("tenfour"))).get(0), std::string_view("tenfour"))); + std::string s = "tenfour"; + VERIFY(equals(std::format_args(std::make_format_args(s)).get(0), std::string_view("tenfour"))); + char nine = '9'; + wchar_t ten = L'X'; // This needs to be on the stack so that testing pointer equality works. wchar_t eleven[] = L"eleven"; - // This needs to be on the stack so that the wstring_view doesn't dangle. + long double twelve13 = 12.13L; std::wstring tenfour = L"tenfour"; - auto wstore = std::make_wformat_args('9', L'X', eleven, 12.13L, tenfour); + auto wstore = std::make_wformat_args(nine, ten, eleven, twelve13, tenfour); std::wformat_args wargs = wstore; VERIFY(equals(wargs.get(0), static_cast('9'))); VERIFY(equals(wargs.get(1), L'X')); @@ -81,7 +93,9 @@ test_args() VERIFY(equals(wargs.get(4), std::wstring_view(tenfour))); VERIFY(!wargs.get(5)); - auto another_store = std::make_format_args(nullptr, E::ByGum); + std::nullptr_t null; + E eebygum = E::ByGum; + auto another_store = std::make_format_args(null, eebygum); args = another_store; VERIFY(equals(args.get(0), static_cast(nullptr))); using handle = std::basic_format_arg::handle; diff --git a/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc b/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc new file mode 100644 index 00000000000..16ac3040146 --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc @@ -0,0 +1,12 @@ +// { dg-do compile { target c++20 } } + +// P2905R2 Runtime format strings + +#include + +std::string rval() { return "path/etic/experience"; } + +void f() +{ + (void)std::make_format_args(rval()); // { dg-error "cannot bind non-const lvalue reference" } +} diff --git a/libstdc++-v3/testsuite/std/format/arguments/lwg3810.cc b/libstdc++-v3/testsuite/std/format/arguments/lwg3810.cc index 9ccb654de1b..df3d98e33f7 100644 --- a/libstdc++-v3/testsuite/std/format/arguments/lwg3810.cc +++ b/libstdc++-v3/testsuite/std/format/arguments/lwg3810.cc @@ -5,7 +5,11 @@ #include -auto args_store = std::make_format_args(1,2,3); +int x = 1; +long y = 2; +short z = 3; + +auto args_store = std::make_format_args(x, y, z); std::basic_format_args args = args_store; static_assert(std::is_same_v); @@ -21,5 +25,5 @@ test_ctad() using SomeContext = std::wformat_context; // foo(make_format_args(...)); // won't work - foo(basic_format_args(make_format_args(1, 2, 3))); // should work + foo(basic_format_args(make_format_args(x, y, z))); // should work } diff --git a/libstdc++-v3/testsuite/std/format/functions/format.cc b/libstdc++-v3/testsuite/std/format/functions/format.cc index 531b6a312d7..1f1802e561c 100644 --- a/libstdc++-v3/testsuite/std/format/functions/format.cc +++ b/libstdc++-v3/testsuite/std/format/functions/format.cc @@ -261,14 +261,16 @@ test_width() } try { - auto args = std::make_format_args(false, true); + bool no = false, yes = true; + auto args = std::make_format_args(no, yes); s = std::vformat("DR 3720: restrict type of width arg-id {0:{1}}", args); VERIFY(false); } catch (const std::format_error&) { } try { - auto args = std::make_format_args('?', '!'); + char wat = '?', bang = '!'; + auto args = std::make_format_args(wat, bang); s = std::vformat("DR 3720: restrict type of width arg-id {0:{1}}", args); VERIFY(false); } catch (const std::format_error&) { diff --git a/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc b/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc index 6fa33b9fbac..320914e272a 100644 --- a/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc +++ b/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc @@ -30,17 +30,22 @@ private: void test_move_only() { + const char arg1[] = "matte"; + int arg2 = '!'; + std::string str; move_only_iterator mo(std::back_inserter(str)); auto res = std::vformat_to(std::move(mo), "for{:.3} that{:c}", - std::make_format_args("matte", (int)'!')); + std::make_format_args(arg1, arg2)); static_assert(std::is_same_v); VERIFY( str == "format that!" ); + const wchar_t warg1[] = L"matte"; + long warg2 = L'!'; std::wstring wstr; move_only_iterator wmo(std::back_inserter(wstr)); auto wres = std::vformat_to(std::move(wmo), L"for{:.3} that{:c}", - std::make_wformat_args(L"matte", (long)L'!')); + std::make_wformat_args(warg1, warg2)); static_assert(std::is_same_v); VERIFY( wstr == L"format that!" ); } diff --git a/libstdc++-v3/testsuite/std/format/string.cc b/libstdc++-v3/testsuite/std/format/string.cc index d70a2331645..a0c07c49f68 100644 --- a/libstdc++-v3/testsuite/std/format/string.cc +++ b/libstdc++-v3/testsuite/std/format/string.cc @@ -150,8 +150,9 @@ void test_pr110862() { try { + int i = 1; // PR libstdc++/110862 out-of-bounds read on invalid format string - (void) std::vformat("{0:{0}", std::make_format_args(1)); + (void) std::vformat("{0:{0}", std::make_format_args(i)); VERIFY( false ); } catch (const std::format_error& e) { std::string_view what = e.what(); @@ -163,9 +164,11 @@ void test_pr110974() { try { + double d = 1.0; + int i = 1; // PR libstdc++/110974 out of bounds read on invalid format string "{:{}." std::string_view fmt{"{:{}.0", 5}; // "0" is not part of the format string. - (void) std::vformat(fmt, std::make_format_args(1.0, 1)); + (void) std::vformat(fmt, std::make_format_args(d, i)); VERIFY( false ); } catch (const std::format_error& e) { std::string_view what = e.what(); diff --git a/libstdc++-v3/testsuite/std/time/day/io.cc b/libstdc++-v3/testsuite/std/time/day/io.cc index b6da0701f98..592ecbb7216 100644 --- a/libstdc++-v3/testsuite/std/time/day/io.cc +++ b/libstdc++-v3/testsuite/std/time/day/io.cc @@ -52,8 +52,8 @@ test_format() char fmt[] = { '{', ':', '%', c, '}' }; try { - (void) std::vformat(std::string_view(fmt, 5), - std::make_format_args(day(1))); + day d(1); + (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(d)); // The call above should throw for any conversion-spec not in my_specs: VERIFY(my_specs.find(c) != my_specs.npos); } diff --git a/libstdc++-v3/testsuite/std/time/month/io.cc b/libstdc++-v3/testsuite/std/time/month/io.cc index 59c303bb96c..1f2a4c0e3ce 100644 --- a/libstdc++-v3/testsuite/std/time/month/io.cc +++ b/libstdc++-v3/testsuite/std/time/month/io.cc @@ -75,8 +75,8 @@ test_format() char fmt[] = { '{', ':', '%', c, '}' }; try { - (void) std::vformat(std::string_view(fmt, 5), - std::make_format_args(month(1))); + month m(1); + (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(m)); // The call above should throw for any conversion-spec not in my_specs: VERIFY(my_specs.find(c) != my_specs.npos); } diff --git a/libstdc++-v3/testsuite/std/time/weekday/io.cc b/libstdc++-v3/testsuite/std/time/weekday/io.cc index ab213fb559a..3e1661b5052 100644 --- a/libstdc++-v3/testsuite/std/time/weekday/io.cc +++ b/libstdc++-v3/testsuite/std/time/weekday/io.cc @@ -78,8 +78,8 @@ test_format() char fmt[] = { '{', ':', '%', c, '}' }; try { - (void) std::vformat(std::string_view(fmt, 5), - std::make_format_args(weekday(1))); + weekday wd(1); + (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(wd)); // The call above should throw for any conversion-spec not in my_specs: VERIFY(my_specs.find(c) != my_specs.npos); } diff --git a/libstdc++-v3/testsuite/std/time/year/io.cc b/libstdc++-v3/testsuite/std/time/year/io.cc index b50d9d89c61..18331deee9c 100644 --- a/libstdc++-v3/testsuite/std/time/year/io.cc +++ b/libstdc++-v3/testsuite/std/time/year/io.cc @@ -69,8 +69,8 @@ test_format() char fmt[] = { '{', ':', '%', c, '}' }; try { - (void) std::vformat(std::string_view(fmt, 5), - std::make_format_args(year(2022))); + year y = 2022y; + (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(y)); // The call above should throw for any conversion-spec not in my_specs: VERIFY(my_specs.find(c) != my_specs.npos); } diff --git a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc index 967b4a72406..04e6e59c8f7 100644 --- a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc +++ b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc @@ -98,8 +98,8 @@ test_format() char fmt[] = { '{', ':', '%', c, '}' }; try { - (void) std::vformat(std::string_view(fmt, 5), - std::make_format_args(2022y/December/19)); + year_month_day ymd = 2022y/December/19; + (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(ymd)); // The call above should throw for any conversion-spec not in my_specs: VERIFY(my_specs.find(c) != my_specs.npos); }