https://gcc.gnu.org/g:6ef8c905e0064c4dfb7ca302355fc20cb96b147b
commit r15-1180-g6ef8c905e0064c4dfb7ca302355fc20cb96b147b Author: Andi Kleen <a...@linux.intel.com> Date: Sun May 5 22:40:20 2024 -0700 Factor out static_assert constexpr string extraction for reuse The only semantics changes are slightly more vague error messages to generalize. gcc/cp/ChangeLog: * cp-tree.h (class cexpr_str): Add. * semantics.cc (finish_static_assert): Convert to use cexpr_str. (cexpr_str::type_check): Extract constexpr string code to here. (cexpr_str::extract): ... and here. gcc/testsuite/ChangeLog: * g++.dg/cpp26/static_assert1.C: Update to new error message. * g++.dg/cpp0x/udlit-error1.C: Dito. Diff: --- gcc/cp/cp-tree.h | 18 ++ gcc/cp/semantics.cc | 256 +++++++++++++++------------- gcc/testsuite/g++.dg/cpp0x/udlit-error1.C | 2 +- gcc/testsuite/g++.dg/cpp26/static_assert1.C | 32 ++-- 4 files changed, 176 insertions(+), 132 deletions(-) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 1ac31d073d18..62718ff126a2 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -9015,6 +9015,24 @@ struct push_access_scope_guard } }; +/* Extracting strings from constexpr. */ + +class cexpr_str +{ +public: + cexpr_str (tree message) : message (message) {} + cexpr_str (const cexpr_str &) = delete; + ~cexpr_str () { XDELETEVEC (buf); } + + bool type_check (location_t location); + bool extract (location_t location, const char * & msg, int &len); + tree message; +private: + tree message_data = NULL_TREE; + tree message_sz = NULL_TREE; + char *buf = nullptr; +}; + /* True if TYPE is an extended floating-point type. */ inline bool diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 44cc4289f39c..20f4675833e2 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -11667,28 +11667,18 @@ init_cp_semantics (void) } -/* Build a STATIC_ASSERT for a static assertion with the condition - CONDITION and the message text MESSAGE. LOCATION is the location - of the static assertion in the source code. When MEMBER_P, this - static assertion is a member of a class. If SHOW_EXPR_P is true, - print the condition (because it was instantiation-dependent). */ +/* Get constant string at LOCATION. Returns true if successful, + otherwise false. */ -void -finish_static_assert (tree condition, tree message, location_t location, - bool member_p, bool show_expr_p) +bool +cexpr_str::type_check (location_t location) { tsubst_flags_t complain = tf_warning_or_error; - tree message_sz = NULL_TREE, message_data = NULL_TREE; if (message == NULL_TREE || message == error_mark_node - || condition == NULL_TREE - || condition == error_mark_node) - return; - - if (check_for_bare_parameter_packs (condition) || check_for_bare_parameter_packs (message)) - return; + return false; if (TREE_CODE (message) != STRING_CST && !type_dependent_expression_p (message)) @@ -11704,10 +11694,10 @@ finish_static_assert (tree condition, tree message, location_t location, false, complain); if (message_sz == error_mark_node || message_data == error_mark_node) { - error_at (location, "%<static_assert%> message must be a string " - "literal or object with %<size%> and " - "%<data%> members"); - return; + error_at (location, "constexpr string must be a string " + "literal or object with %<size%> and " + "%<data%> members"); + return false; } releasing_vec size_args, data_args; message_sz = finish_call_expr (message_sz, &size_args, false, false, @@ -11715,26 +11705,144 @@ finish_static_assert (tree condition, tree message, location_t location, message_data = finish_call_expr (message_data, &data_args, false, false, complain); if (message_sz == error_mark_node || message_data == error_mark_node) - return; + return false; message_sz = build_converted_constant_expr (size_type_node, message_sz, - complain); + complain); if (message_sz == error_mark_node) { - error_at (location, "%<static_assert%> message %<size()%> " - "must be implicitly convertible to " - "%<std::size_t%>"); - return; + error_at (location, "constexpr string %<size()%> " + "must be implicitly convertible to " + "%<std::size_t%>"); + return false; } message_data = build_converted_constant_expr (const_string_type_node, - message_data, complain); + message_data, complain); if (message_data == error_mark_node) { - error_at (location, "%<static_assert%> message %<data()%> " - "must be implicitly convertible to " - "%<const char*%>"); - return; + error_at (location, "constexpr string %<data()%> " + "must be implicitly convertible to " + "%<const char*%>"); + return false; } } + return true; +} + +/* Extract constant string at LOCATION into output string MSG with LEN. + Returns true if successful, otherwise false. */ + +bool +cexpr_str::extract (location_t location, const char * & msg, int &len) +{ + tsubst_flags_t complain = tf_warning_or_error; + + msg = NULL; + if (message_sz && message_data) + { + tree msz = cxx_constant_value (message_sz, NULL_TREE, complain); + if (!tree_fits_uhwi_p (msz)) + { + error_at (location, + "constexpr string %<size()%> " + "must be a constant expression"); + return false; + } + else if ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (msz) + != tree_to_uhwi (msz)) + { + error_at (location, + "constexpr string message %<size()%> " + "%qE too large", msz); + return false; + } + len = tree_to_uhwi (msz); + tree data = maybe_constant_value (message_data, NULL_TREE, + mce_true); + if (!reduced_constant_expression_p (data)) + data = NULL_TREE; + if (len) + { + if (data) + msg = c_getstr (data); + if (msg == NULL) + buf = XNEWVEC (char, len); + for (int i = 0; i < len; ++i) + { + tree t = message_data; + if (i) + t = build2 (POINTER_PLUS_EXPR, + TREE_TYPE (message_data), message_data, + size_int (i)); + t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t); + tree t2 = cxx_constant_value (t, NULL_TREE, complain); + if (!tree_fits_shwi_p (t2)) + { + error_at (location, + "constexpr string %<data()[%d]%> " + "must be a constant expression", i); + return false; + } + if (msg == NULL) + buf[i] = tree_to_shwi (t2); + /* If c_getstr worked, just verify the first and + last characters using constant evaluation. */ + else if (len > 2 && i == 0) + i = len - 2; + } + if (msg == NULL) + msg = buf; + } + else if (!data) + { + /* We don't have any function to test whether some + expression is a core constant expression. So, instead + test whether (message.data (), 0) is a constant + expression. */ + data = build2 (COMPOUND_EXPR, integer_type_node, + message_data, integer_zero_node); + tree t = cxx_constant_value (data, NULL_TREE, complain); + if (!integer_zerop (t)) + { + error_at (location, + "constexpr string %<data()%> " + "must be a core constant expression"); + return false; + } + } + } + else + { + tree eltype = TREE_TYPE (TREE_TYPE (message)); + int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (eltype)); + msg = TREE_STRING_POINTER (message); + len = TREE_STRING_LENGTH (message) / sz - 1; + } + + return true; +} + +/* Build a STATIC_ASSERT for a static assertion with the condition + CONDITION and the message text MESSAGE. LOCATION is the location + of the static assertion in the source code. When MEMBER_P, this + static assertion is a member of a class. If SHOW_EXPR_P is true, + print the condition (because it was instantiation-dependent). */ + +void +finish_static_assert (tree condition, tree message, location_t location, + bool member_p, bool show_expr_p) +{ + tsubst_flags_t complain = tf_warning_or_error; + + if (condition == NULL_TREE + || condition == error_mark_node) + return; + + if (check_for_bare_parameter_packs (condition)) + return; + + cexpr_str cstr(message); + if (!cstr.type_check (location)) + return; /* Save the condition in case it was a concept check. */ tree orig_condition = condition; @@ -11747,7 +11855,7 @@ finish_static_assert (tree condition, tree message, location_t location, defer: tree assertion = make_node (STATIC_ASSERT); STATIC_ASSERT_CONDITION (assertion) = orig_condition; - STATIC_ASSERT_MESSAGE (assertion) = message; + STATIC_ASSERT_MESSAGE (assertion) = cstr.message; STATIC_ASSERT_SOURCE_LOCATION (assertion) = location; if (member_p) @@ -11780,88 +11888,8 @@ finish_static_assert (tree condition, tree message, location_t location, int len; const char *msg = NULL; - char *buf = NULL; - if (message_sz && message_data) - { - tree msz = cxx_constant_value (message_sz, NULL_TREE, complain); - if (!tree_fits_uhwi_p (msz)) - { - error_at (location, - "%<static_assert%> message %<size()%> " - "must be a constant expression"); - return; - } - else if ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (msz) - != tree_to_uhwi (msz)) - { - error_at (location, - "%<static_assert%> message %<size()%> " - "%qE too large", msz); - return; - } - len = tree_to_uhwi (msz); - tree data = maybe_constant_value (message_data, NULL_TREE, - mce_true); - if (!reduced_constant_expression_p (data)) - data = NULL_TREE; - if (len) - { - if (data) - msg = c_getstr (data); - if (msg == NULL) - buf = XNEWVEC (char, len); - for (int i = 0; i < len; ++i) - { - tree t = message_data; - if (i) - t = build2 (POINTER_PLUS_EXPR, - TREE_TYPE (message_data), message_data, - size_int (i)); - t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t); - tree t2 = cxx_constant_value (t, NULL_TREE, complain); - if (!tree_fits_shwi_p (t2)) - { - error_at (location, - "%<static_assert%> message %<data()[%d]%> " - "must be a constant expression", i); - XDELETEVEC (buf); - return; - } - if (msg == NULL) - buf[i] = tree_to_shwi (t2); - /* If c_getstr worked, just verify the first and - last characters using constant evaluation. */ - else if (len > 2 && i == 0) - i = len - 2; - } - if (msg == NULL) - msg = buf; - } - else if (!data) - { - /* We don't have any function to test whether some - expression is a core constant expression. So, instead - test whether (message.data (), 0) is a constant - expression. */ - data = build2 (COMPOUND_EXPR, integer_type_node, - message_data, integer_zero_node); - tree t = cxx_constant_value (data, NULL_TREE, complain); - if (!integer_zerop (t)) - { - error_at (location, - "%<static_assert%> message %<data()%> " - "must be a core constant expression"); - return; - } - } - } - else - { - tree eltype = TREE_TYPE (TREE_TYPE (message)); - int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (eltype)); - msg = TREE_STRING_POINTER (message); - len = TREE_STRING_LENGTH (message) / sz - 1; - } + if (!cstr.extract (location, msg, len)) + return; /* See if we can find which clause was failing (for logical AND). */ tree bad = find_failing_clause (NULL, orig_condition); @@ -11877,8 +11905,6 @@ finish_static_assert (tree condition, tree message, location_t location, else error_at (cloc, "static assertion failed: %.*s", len, msg); - XDELETEVEC (buf); - diagnose_failing_condition (bad, cloc, show_expr_p); } else if (condition && condition != error_mark_node) diff --git a/gcc/testsuite/g++.dg/cpp0x/udlit-error1.C b/gcc/testsuite/g++.dg/cpp0x/udlit-error1.C index 6d6cd454540a..ea939c52c339 100644 --- a/gcc/testsuite/g++.dg/cpp0x/udlit-error1.C +++ b/gcc/testsuite/g++.dg/cpp0x/udlit-error1.C @@ -12,7 +12,7 @@ void operator""_x(const char *, decltype(sizeof(0))); extern "C"_x { void g(); } // { dg-error "before user-defined string literal" } static_assert(true, "foo"_x); // { dg-error "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } + // { dg-error "constexpr string must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } // { dg-error "invalid use of 'void'" "" { target *-*-* } .-2 } [[deprecated("oof"_x)]] // { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } } diff --git a/gcc/testsuite/g++.dg/cpp26/static_assert1.C b/gcc/testsuite/g++.dg/cpp26/static_assert1.C index 59724ae32ce3..7840b6b04d27 100644 --- a/gcc/testsuite/g++.dg/cpp26/static_assert1.C +++ b/gcc/testsuite/g++.dg/cpp26/static_assert1.C @@ -6,25 +6,25 @@ static_assert (true, ""); static_assert (true, ("")); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } + // { dg-error "constexpr string must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } // { dg-error "request for member 'size' in '\\\(\\\"\\\"\\\)', which is of non-class type 'const char \\\[1\\\]'" "" { target *-*-* } .-2 } static_assert (true, "" + 0); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } + // { dg-error "constexpr string must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } // { dg-error "request for member 'size' in '\\\(const char\\\*\\\)\\\"\\\"', which is of non-class type 'const char\\\*'" "" { target *-*-* } .-2 } static_assert (true, 0); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } + // { dg-error "constexpr string must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } // { dg-error "request for member 'size' in '0', which is of non-class type 'int'" "" { target *-*-* } .-2 } struct A {}; static_assert (true, A {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } + // { dg-error "constexpr string must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } // { dg-error "'struct A' has no member named 'size'" "" { target *-*-* } .-2 } struct B { int size; }; static_assert (true, B {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } + // { dg-error "constexpr string must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } // { dg-error "'struct B' has no member named 'data'" "" { target *-*-* } .-2 } struct C { constexpr int size () const { return 0; } }; static_assert (true, C {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } + // { dg-error "constexpr string must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } // { dg-error "'struct C' has no member named 'data'" "" { target *-*-* } .-2 } struct D { constexpr int size () const { return 0; } int data; }; static_assert (true, D {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } @@ -37,13 +37,13 @@ static_assert (true, E {}); // { dg-warning "'static_assert' with non-string mes struct F { constexpr const char *size () const { return ""; } constexpr const char *data () const { return ""; } }; static_assert (true, F {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message 'size\\\(\\\)' must be implicitly convertible to 'std::size_t'" "" { target *-*-* } .-1 } + // { dg-error "constexpr string 'size\\\(\\\)' must be implicitly convertible to 'std::size_t'" "" { target *-*-* } .-1 } // { dg-error "could not convert 'F\\\(\\\).F::size\\\(\\\)' from 'const char\\\*' to '\[^']*'" "" { target *-*-* } .-2 } // { dg-error "conversion from 'const char\\\*' to '\[^']*' in a converted constant expression" "" { target *-*-* } .-3 } struct G { constexpr long size () const { return 0; } constexpr float data () const { return 0.0f; } }; static_assert (true, G {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 } + // { dg-error "constexpr string 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 } // { dg-error "could not convert 'G\\\(\\\).G::data\\\(\\\)' from 'float' to 'const char\\\*'" "" { target *-*-* } .-2 } struct H { short size () const { return 0; } constexpr const char *data () const { return ""; } }; @@ -59,7 +59,7 @@ static_assert (true, J (1)); // { dg-warning "'static_assert' with non-string me static_assert (false, J (0)); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } // { dg-error "static assertion failed" "" { target *-*-* } .-1 } static_assert (false, J (1)); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target *-*-* } .-1 } + // { dg-error "constexpr string 'size\\\(\\\)' must be a constant expression" "" { target *-*-* } .-1 } struct K { constexpr operator int () { return 4; } }; struct L { constexpr operator const char * () { return "test"; } }; struct M { constexpr K size () const { return {}; } @@ -72,7 +72,7 @@ struct N { constexpr int size () const { return 3; } constexpr const char *data () const { return new char[3] { 'b', 'a', 'd' }; } }; // { dg-error "'\\\* N\\\(\\\).N::data\\\(\\\)' is not a constant expression because allocated storage has not been deallocated" "" { target c++20 } } static_assert (true, N {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } } static_assert (false, N {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } } - // { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++20 } .-1 } + // { dg-error "constexpr string 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++20 } .-1 } #endif constexpr const char a[] = { 't', 'e', 's', 't' }; struct O { constexpr int size () const { return 4; } @@ -133,7 +133,7 @@ static_assert (false, string_view (4, "testwithextrachars")); // { dg-warning "' // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 } static_assert (false, string_view (42, "test")); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } // { dg-error "array subscript value '41' is outside the bounds of array type 'const char \\\[5\\\]'" "" { target *-*-* } .-1 } - // { dg-error "'static_assert' message 'data\\\(\\\)\\\[41\\\]' must be a constant expression" "" { target *-*-* } .-2 } + // { dg-error "constexpr string 'data\\\(\\\)\\\[41\\\]' must be a constant expression" "" { target *-*-* } .-2 } template <typename T, size_t N> struct array { @@ -143,7 +143,7 @@ struct array { }; static_assert (true, array<char, 2> { 'O', 'K' }); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } static_assert (true, array<wchar_t, 2> { L'O', L'K' }); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - // { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 } + // { dg-error "constexpr string 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 } // { dg-error "could not convert 'array<wchar_t, 2>{const wchar_t \\\[2\\\]{\[0-9]+, \[0-9]+}}.array<wchar_t, 2>::data\\\(\\\)' from 'const wchar_t\\\*' to 'const char\\\*'" "" { target *-*-* } .-2 } static_assert (false, array<char, 4> { 't', 'e', 's', 't' }); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 } @@ -235,7 +235,7 @@ namespace NN template <typename T> struct G { static_assert (false, T{}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } } - }; // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } + }; // { dg-error "constexpr string must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 } // { dg-error "request for member 'size' in '0', which is of non-class type 'long int'" "" { target *-*-* } .-2 } F<E> fe; G<long> gl; @@ -263,7 +263,7 @@ namespace NN static constexpr const char *data (int x = 0) { if (x) return nullptr; else throw 1; } }; // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++14 } } static_assert (true, J{}); // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } } static_assert (false, J{}); // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } } - // { dg-error "'static_assert' message 'data\\\(\\\)' must be a core constant expression" "" { target c++14 } .-1 } + // { dg-error "constexpr string 'data\\\(\\\)' must be a core constant expression" "" { target c++14 } .-1 } #endif #if __cpp_if_consteval >= 202106L struct K { @@ -286,14 +286,14 @@ namespace NN }; static_assert (true, M{}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } } static_assert (false, M{}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } } - // { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target c++23 } .-1 } + // { dg-error "'constexpr string 'size\\\(\\\)' must be a constant expression" "" { target c++23 } .-1 } struct N { static constexpr int size () { return 4; } static constexpr const char *data () { if consteval { throw 1; } else { return "test"; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } } }; static_assert (true, N{}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } } static_assert (false, N{}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } } - // { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++23 } .-1 } + // { dg-error "'constexpr string 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++23 } .-1 } #endif struct O { constexpr int operator () () const { return 12; } }; struct P { constexpr const char *operator () () const { return "another test"; } };