From patchwork Mon Jan 8 01:19:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 1883454 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=g18Iihur; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4T7bsK2Dkqz1xqk for ; Mon, 8 Jan 2024 12:22:45 +1100 (AEDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 5A4103870896 for ; Mon, 8 Jan 2024 01:22:43 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id 39C013861846 for ; Mon, 8 Jan 2024 01:19:39 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 39C013861846 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 39C013861846 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1704676786; cv=none; b=mxO6Pmp8jUv8vgEPuKURzocrDBrvSOQX8cFyN2OVXxmQwNaFRFpaibF+WIrfF5ICogvXyDY/yfiipwN++A+TiVDw6u7GkNapmKFXcfuEkLd5ZK81s9jmDjkuuSSI4fF8cCK86W04C6KYwhcLd7nWlGMNKUmC1BxuaGrXy3GugWQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1704676786; c=relaxed/simple; bh=i4bWjSMUiE1mWI+5Z0XNuapMRdiwlWFwgaEnUlh4i7Y=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=G4EALfCjuvx7csBZrxs0WfuLvRpPkL9UcRFpE2FBkRW+AFa7j8EyfWzJO0ka2WzGIiJQR2dERU9R9TUDduS49y2BEvxF7WzxrOAS3cUZJzJ/a2B/jPsStaYqwgLYI8nwYWZt7RNZRZiljeKo3R3NGF/jnT3A/0NPDoiI6QWbrRc= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1704676778; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=zjjcXhQkR+cVgG0aG7n1fERklbQc79njIVDUl1Lt7FA=; b=g18IihurFX9SxNbEtAS2jfhbdWHGXhJUdtU2T64wTL3RWsMCrp3+vtz44eaW9zcQGBKlfs dGTDZZXGWUyBehv+eS2jTThoXtfhQHCm+oAXPuyhZXkGmlnBX3/SxJkAUP7eviZ3KMqt12 1Oj/0aTdFINuNL9doH7U5CVB8c4C6W4= Received: from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-103-GyqlXfceNPOhJffv_zZ6MA-1; Sun, 07 Jan 2024 20:19:32 -0500 X-MC-Unique: GyqlXfceNPOhJffv_zZ6MA-1 Received: from smtp.corp.redhat.com (int-mx09.intmail.prod.int.rdu2.redhat.com [10.11.54.9]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id CA4F63806702; Mon, 8 Jan 2024 01:19:31 +0000 (UTC) Received: from localhost (unknown [10.42.28.185]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7A10F492BC8; Mon, 8 Jan 2024 01:19:31 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed 1/2] libstdc++: Implement P2905R2 "Runtime format strings" for C++20 Date: Mon, 8 Jan 2024 01:19:01 +0000 Message-ID: <20240108011930.3670651-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.9 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_NUMSUBJECT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org Tested x86_64-linux and aarch64-linux. Pushed to trunk. -- >8 -- 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. --- 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 ++++++++++++---- .../std/format/arguments/args_neg.cc | 12 ++++++++ .../testsuite/std/format/arguments/lwg3810.cc | 8 +++-- .../testsuite/std/format/functions/format.cc | 6 ++-- .../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(-) create mode 100644 libstdc++-v3/testsuite/std/format/arguments/args_neg.cc 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); } From patchwork Mon Jan 8 01:19:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 1883451 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=di671run; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4T7bpS0PzPz1xqk for ; Mon, 8 Jan 2024 12:20:16 +1100 (AEDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id D7E0C3845BD8 for ; Mon, 8 Jan 2024 01:20:13 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id AE299384DED6 for ; Mon, 8 Jan 2024 01:19:34 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org AE299384DED6 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org AE299384DED6 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1704676777; cv=none; b=kFMENc1IrjFphGJV2F3pwJRgSVZCgHc+FhaKeRer7pq9Jm1ri1fF4Nr+XwFMYV6qHmuAT6b8hoHKy0cMNNtvDs1+HQ6jdh2/GbxR9TLhe+S4LWtUAqlFC6tKMjYvpntipNOK1+KdfdZBXXM1QG7pgMY9PGqAa5E1BDPgKAsbj/8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1704676777; c=relaxed/simple; bh=8CLkMpn1ClKZ5gR5r53C8c2rFX9D0Q6LrzwZvBLsFuo=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=m9KUo+dakc+noRDKrcv9DtGruZVqsvigj1DknBJl+IgBHeTShacFMmUU27ml9qVRrzI62sno0vhw/xHGc8OqUvg2Zwm9KV+9OayAP+BHetiDiGu6rEadjkR+FOkeAoJKDLktVPUWnQxKnB5RoERpWszS3WDYxNFOdqfDctvOX+Q= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1704676774; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=KGHIXQYPGMXDxTv/Uybr2vkMZxLCnPQa5K5JszZ76/k=; b=di671runRc9K+5kMVg4poJ5/rlrrMa+w9K8kPsl/RLfh8Kgtw9jp80c9IgYPYN6L2UtU8s qcXi9WX7QajVn6TahOei4O4SyDYEH/YLWXWdyWJHqzl93mqeUljxXpW4Z/G9S+gIpKnGms XXJMPLTiMR3JRyKDM85KRVkNS6TatbQ= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-322-d0LZP_I3MNO2DUyseYe2yw-1; Sun, 07 Jan 2024 20:19:32 -0500 X-MC-Unique: d0LZP_I3MNO2DUyseYe2yw-1 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.rdu2.redhat.com [10.11.54.7]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 9EB66862DE2; Mon, 8 Jan 2024 01:19:32 +0000 (UTC) Received: from localhost (unknown [10.42.28.185]) by smtp.corp.redhat.com (Postfix) with ESMTP id 692201C060AF; Mon, 8 Jan 2024 01:19:32 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed 2/2] libstdc++: Implement P2918R0 "Runtime format strings II" for C++26 Date: Mon, 8 Jan 2024 01:19:02 +0000 Message-ID: <20240108011930.3670651-2-jwakely@redhat.com> In-Reply-To: <20240108011930.3670651-1-jwakely@redhat.com> References: <20240108011930.3670651-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.7 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.6 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_NUMSUBJECT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org Tested x86_64-linux and aarch64-linux. Pushed to trunk. -- >8 -- This adds std::runtime_format for C++26. These new overloaded functions enhance the std::format API so that it isn't necessary to use the less ergonomic std::vformat and std::make_format_args (which are meant to be implementation details). This was approved in Kona 2023 for C++26. libstdc++-v3/ChangeLog: * include/std/format (__format::_Runtime_format_string): Define new class template. (basic_format_string): Add non-consteval constructor for runtime format strings. (runtime_format): Define new function for C++26. * testsuite/std/format/runtime_format.cc: New test. --- libstdc++-v3/include/std/format | 22 +++++++++++ .../testsuite/std/format/runtime_format.cc | 37 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 libstdc++-v3/testsuite/std/format/runtime_format.cc diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 160efa5155c..b3b5a0bbdbc 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -81,6 +81,9 @@ namespace __format template using __format_context = basic_format_context<_Sink_iter<_CharT>, _CharT>; + + template + struct _Runtime_format_string { basic_string_view<_CharT> _M_str; }; } // namespace __format /// @endcond @@ -115,6 +118,11 @@ namespace __format consteval basic_format_string(const _Tp& __s); + [[__gnu__::__always_inline__]] + basic_format_string(__format::_Runtime_format_string<_CharT>&& __s) + : _M_str(__s._M_str) + { } + [[__gnu__::__always_inline__]] constexpr basic_string_view<_CharT> get() const noexcept @@ -133,6 +141,20 @@ namespace __format = basic_format_string...>; #endif +#if __cplusplus > 202302L + [[__gnu__::__always_inline__]] + inline __format::_Runtime_format_string + runtime_format(string_view __fmt) + { return {__fmt}; } + +#ifdef _GLIBCXX_USE_WCHAR_T + [[__gnu__::__always_inline__]] + inline __format::_Runtime_format_string + runtime_format(wstring_view __fmt) + { return {__fmt}; } +#endif +#endif // C++26 + // [format.formatter], formatter /// The primary template of std::formatter is disabled. diff --git a/libstdc++-v3/testsuite/std/format/runtime_format.cc b/libstdc++-v3/testsuite/std/format/runtime_format.cc new file mode 100644 index 00000000000..174334c7676 --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/runtime_format.cc @@ -0,0 +1,37 @@ +// { dg-do run { target c++26 } } + +#include +#include + +void +test_char() +{ + std::string fmt = "{}"; + auto s = std::format(std::runtime_format(fmt), 123); + VERIFY( s == "123" ); +} + +void +test_wchar() +{ + std::wstring fmt = L"{:#o}"; + auto s = std::format(std::runtime_format(fmt), 456); + VERIFY( s == L"0710" ); +} + +void +test_internal_api() +{ + // Using _Runtime_format_string directly works even in C++20 mode. + // This can be used internally by libstdc++. + std::string fmt = "{:#x}"; + auto s = std::format(std::__format::_Runtime_format_string(fmt), 789); + VERIFY( s == "0x315" ); +} + +int main() +{ + test_char(); + test_wchar(); + test_internal_api(); +}