From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2181) id 97166385E451; Mon, 8 Jan 2024 01:15:32 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 97166385E451 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1704676532; bh=5f+qGCEFP9F5ccBIH30BEhT92IQUjOM/a+VT2Xbl0/I=; h=From:To:Subject:Date:From; b=R2veOY42Y3nXGi4t0hZERu2axe84ONWm/sad0r04pVjFJGq8MlHMs4yJZTEF5GQRl 2/t+aSHlhgKPf6uEecFyNTY7hXIqiA8R7k82lvu9Fl10F7BcODhaVNq3kKxyZPpGFz eKZjwkR6aZLSOpWpMYsRfPM65UBPeMocll5K1BtI= 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 r14-6992] libstdc++: Implement P2905R2 "Runtime format strings" for C++20 X-Act-Checkin: gcc X-Git-Author: Jonathan Wakely X-Git-Refname: refs/heads/master X-Git-Oldrev: 37a4c5c23a270cd9350ba5d56e526371424b5742 X-Git-Newrev: 2a8ee2592e48735d88df786cbafa6b0da39fc4d6 Message-Id: <20240108011532.97166385E451@sourceware.org> Date: Mon, 8 Jan 2024 01:15:32 +0000 (GMT) List-Id: https://gcc.gnu.org/g:2a8ee2592e48735d88df786cbafa6b0da39fc4d6 commit r14-6992-g2a8ee2592e48735d88df786cbafa6b0da39fc4d6 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. * include/std/ostream (print): Remove forwarding of arguments. * include/std/print (print): Likewise. * 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. Diff: --- libstdc++-v3/include/bits/chrono_io.h | 15 +++++++---- libstdc++-v3/include/std/format | 30 +++++++++++----------- libstdc++-v3/include/std/ostream | 2 +- libstdc++-v3/include/std/print | 2 +- 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 +-- 16 files changed, 93 insertions(+), 47 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index c30451651ea..ec2ae9d53cc 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -2273,7 +2273,8 @@ namespace __detail _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; } @@ -2302,8 +2303,10 @@ namespace __detail __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; } @@ -2364,8 +2367,10 @@ namespace __detail __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 0d9f70ee555..160efa5155c 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -3413,7 +3413,7 @@ namespace __format template friend auto - make_format_args(_Argz&&...) noexcept; + make_format_args(_Argz&...) noexcept; template friend decltype(auto) @@ -3583,7 +3583,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 @@ -3621,7 +3621,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 @@ -3637,7 +3637,7 @@ namespace __format #else std:: #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. @@ -3711,11 +3711,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...); } @@ -3724,7 +3724,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...); } #endif @@ -4240,7 +4240,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...)); } #ifdef _GLIBCXX_USE_WCHAR_T @@ -4250,7 +4250,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...)); } #endif @@ -4261,7 +4261,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...)); } #ifdef _GLIBCXX_USE_WCHAR_T @@ -4272,7 +4272,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...)); } #endif @@ -4379,7 +4379,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(); } @@ -4391,7 +4391,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(); } #endif @@ -4404,7 +4404,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(); } @@ -4417,7 +4417,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(); } #endif diff --git a/libstdc++-v3/include/std/ostream b/libstdc++-v3/include/std/ostream index 4dff0cf645d..7d501d67489 100644 --- a/libstdc++-v3/include/std/ostream +++ b/libstdc++-v3/include/std/ostream @@ -981,7 +981,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION inline void print(ostream& __os, format_string<_Args...> __fmt, _Args&&... __args) { - auto __fmtargs = std::make_format_args(std::forward<_Args>(__args)...); + auto __fmtargs = std::make_format_args(__args...); if constexpr (__unicode::__literal_encoding_is_utf8()) std::vprint_unicode(__os, __fmt.get(), __fmtargs); else diff --git a/libstdc++-v3/include/std/print b/libstdc++-v3/include/std/print index f44256c5dca..492f333dfa6 100644 --- a/libstdc++-v3/include/std/print +++ b/libstdc++-v3/include/std/print @@ -103,7 +103,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION inline void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) { - auto __fmtargs = std::make_format_args(std::forward<_Args>(__args)...); + auto __fmtargs = std::make_format_args(__args...); if constexpr (__unicode::__literal_encoding_is_utf8()) std::vprint_unicode(__stream, __fmt.get(), __fmtargs); else diff --git a/libstdc++-v3/testsuite/20_util/duration/io.cc b/libstdc++-v3/testsuite/20_util/duration/io.cc index 0582c0075a5..e141baf42dc 100644 --- a/libstdc++-v3/testsuite/20_util/duration/io.cc +++ b/libstdc++-v3/testsuite/20_util/duration/io.cc @@ -82,7 +82,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 a45f3fb24df..eba129ff894 100644 --- a/libstdc++-v3/testsuite/std/format/arguments/args.cc +++ b/libstdc++-v3/testsuite/std/format/arguments/args.cc @@ -46,7 +46,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)); @@ -54,7 +59,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)); @@ -64,14 +73,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')); @@ -80,7 +92,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 f89f40203cb..8a9f9edd578 100644 --- a/libstdc++-v3/testsuite/std/format/arguments/lwg3810.cc +++ b/libstdc++-v3/testsuite/std/format/arguments/lwg3810.cc @@ -4,7 +4,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); @@ -20,5 +24,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 6320e20170c..63702edbd42 100644 --- a/libstdc++-v3/testsuite/std/format/functions/format.cc +++ b/libstdc++-v3/testsuite/std/format/functions/format.cc @@ -267,14 +267,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 fe0367f7496..2be3d6f6761 100644 --- a/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc +++ b/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc @@ -29,17 +29,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 40aaebae04e..ddb3c5625cd 100644 --- a/libstdc++-v3/testsuite/std/format/string.cc +++ b/libstdc++-v3/testsuite/std/format/string.cc @@ -149,8 +149,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(); @@ -162,9 +163,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 3454657f69f..36ce7ec7d17 100644 --- a/libstdc++-v3/testsuite/std/time/day/io.cc +++ b/libstdc++-v3/testsuite/std/time/day/io.cc @@ -51,8 +51,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 7e80a53bbb6..99ec0737305 100644 --- a/libstdc++-v3/testsuite/std/time/month/io.cc +++ b/libstdc++-v3/testsuite/std/time/month/io.cc @@ -74,8 +74,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 5ed90282f95..a56cdaef88c 100644 --- a/libstdc++-v3/testsuite/std/time/weekday/io.cc +++ b/libstdc++-v3/testsuite/std/time/weekday/io.cc @@ -77,8 +77,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 a6683ae20df..bcaa57faeb7 100644 --- a/libstdc++-v3/testsuite/std/time/year/io.cc +++ b/libstdc++-v3/testsuite/std/time/year/io.cc @@ -68,8 +68,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 15c5a9f77be..cb82ef3b612 100644 --- a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc +++ b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc @@ -97,8 +97,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); }