From 51e283944b3dac69c1f759421bd950123fb8268c Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Sat, 29 Jul 2023 16:03:44 +0200 Subject: [PATCH] [Clang] Backport static_assert messages fixes * 4d494e7: Handle static_assert messages with an expression started by a literal * 49e0495 Produce a warning instead of an error in unevaluated strings before C++26 Emiting an error on unexpected encoding prefix - which was allowed before C++26 - caused build errors for a few users. This downgrade the error to a warning on older language modes and C --- .../include/clang/Basic/DiagnosticLexKinds.td | 4 ++ clang/lib/Lex/LiteralSupport.cpp | 41 +++++++++++++++++-- clang/lib/Parse/ParseDeclCXX.cpp | 19 +++++++-- clang/test/CXX/dcl.dcl/dcl.link/p2.cpp | 8 ++-- clang/test/CXX/dcl.dcl/p4-0x.cpp | 2 +- clang/test/FixIt/unevaluated-strings.cpp | 17 ++++++++ clang/test/Sema/static-assert.c | 17 ++++++++ clang/test/SemaCXX/static-assert.cpp | 20 +++++---- 8 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 clang/test/FixIt/unevaluated-strings.cpp diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index 0eb270aeea0e5d..6ad691975bd587 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -285,6 +285,10 @@ def ext_ms_reserved_user_defined_literal : ExtWarn< def err_unsupported_string_concat : Error< "unsupported non-standard concatenation of string literals">; +def warn_unevaluated_string_prefix : Warning< + "encoding prefix '%0' on an unevaluated string literal has no effect" + "%select{| and is incompatible with c++2c}1">, + InGroup>; def err_unevaluated_string_prefix : Error< "an unevaluated string literal cannot have an encoding prefix">; def err_unevaluated_string_udl : Error< diff --git a/clang/lib/Lex/LiteralSupport.cpp b/clang/lib/Lex/LiteralSupport.cpp index 3b9913ac8ba420..a6f50832950c88 100644 --- a/clang/lib/Lex/LiteralSupport.cpp +++ b/clang/lib/Lex/LiteralSupport.cpp @@ -57,6 +57,26 @@ static unsigned getCharWidth(tok::TokenKind kind, const TargetInfo &Target) { } } +static unsigned getEncodingPrefixLen(tok::TokenKind kind) { + switch (kind) { + default: + llvm_unreachable("Unknown token type!"); + case tok::char_constant: + case tok::string_literal: + return 0; + case tok::utf8_char_constant: + case tok::utf8_string_literal: + return 2; + case tok::wide_char_constant: + case tok::wide_string_literal: + case tok::utf16_char_constant: + case tok::utf16_string_literal: + case tok::utf32_char_constant: + case tok::utf32_string_literal: + return 1; + } +} + static CharSourceRange MakeCharSourceRange(const LangOptions &Features, FullSourceLoc TokLoc, const char *TokBegin, @@ -343,7 +363,9 @@ static unsigned ProcessCharEscape(const char *ThisTokBegin, Diag(Diags, Features, Loc, ThisTokBegin, EscapeBegin, ThisTokBuf, diag::err_unevaluated_string_invalid_escape_sequence) << StringRef(EscapeBegin, ThisTokBuf - EscapeBegin); + HadError = true; } + return ResultChar; } @@ -1917,9 +1939,22 @@ void StringLiteralParser::init(ArrayRef StringToks){ // Remember if we see any wide or utf-8/16/32 strings. // Also check for illegal concatenations. if (isUnevaluated() && Tok.getKind() != tok::string_literal) { - if (Diags) - Diags->Report(Tok.getLocation(), diag::err_unevaluated_string_prefix); - hadError = true; + if (Diags) { + SourceLocation PrefixEndLoc = Lexer::AdvanceToTokenCharacter( + Tok.getLocation(), getEncodingPrefixLen(Tok.getKind()), SM, + Features); + CharSourceRange Range = + CharSourceRange::getCharRange({Tok.getLocation(), PrefixEndLoc}); + StringRef Prefix(SM.getCharacterData(Tok.getLocation()), + getEncodingPrefixLen(Tok.getKind())); + Diags->Report(Tok.getLocation(), + Features.CPlusPlus26 + ? diag::err_unevaluated_string_prefix + : diag::warn_unevaluated_string_prefix) + << Prefix << Features.CPlusPlus << FixItHint::CreateRemoval(Range); + } + if (Features.CPlusPlus26) + hadError = true; } else if (Tok.isNot(Kind) && Tok.isNot(tok::string_literal)) { if (isOrdinary()) { Kind = Tok.getKind(); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index c1e09db2b3eeff..d9ff6c42c502ce 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -1016,10 +1016,23 @@ Decl *Parser::ParseStaticAssertDeclaration(SourceLocation &DeclEnd) { return nullptr; } - if (isTokenStringLiteral()) - AssertMessage = ParseUnevaluatedStringLiteralExpression(); - else if (getLangOpts().CPlusPlus26) + bool ParseAsExpression = false; + if (getLangOpts().CPlusPlus26) { + for (unsigned I = 0;; ++I) { + const Token &T = GetLookAheadToken(I); + if (T.is(tok::r_paren)) + break; + if (!tok::isStringLiteral(Tok.getKind())) { + ParseAsExpression = true; + break; + } + } + } + + if (ParseAsExpression) AssertMessage = ParseConstantExpressionInExprEvalContext(); + else if (tok::isStringLiteral(Tok.getKind())) + AssertMessage = ParseUnevaluatedStringLiteralExpression(); else { Diag(Tok, diag::err_expected_string_literal) << /*Source='static_assert'*/ 1; diff --git a/clang/test/CXX/dcl.dcl/dcl.link/p2.cpp b/clang/test/CXX/dcl.dcl/dcl.link/p2.cpp index 206c46f34f05b2..234db01a001fbf 100644 --- a/clang/test/CXX/dcl.dcl/dcl.link/p2.cpp +++ b/clang/test/CXX/dcl.dcl/dcl.link/p2.cpp @@ -8,7 +8,7 @@ extern "C" { extern "C" plusplus { } -extern u8"C" {} // expected-error {{an unevaluated string literal cannot have an encoding prefix}} -extern L"C" {} // expected-error {{an unevaluated string literal cannot have an encoding prefix}} -extern u"C++" {} // expected-error {{an unevaluated string literal cannot have an encoding prefix}} -extern U"C" {} // expected-error {{an unevaluated string literal cannot have an encoding prefix}} +extern u8"C" {} // expected-warning {{encoding prefix 'u8' on an unevaluated string literal has no effect and is incompatible with c++2c}} +extern L"C" {} // expected-warning {{encoding prefix 'L' on an unevaluated string literal has no effect and is incompatible with c++2c}} +extern u"C++" {} // expected-warning {{encoding prefix 'u' on an unevaluated string literal has no effect and is incompatible with c++2c}} +extern U"C" {} // expected-warning {{encoding prefix 'U' on an unevaluated string literal has no effect and is incompatible with c++2c}} diff --git a/clang/test/CXX/dcl.dcl/p4-0x.cpp b/clang/test/CXX/dcl.dcl/p4-0x.cpp index 545011822eb7d0..22b10b60ecd1b1 100644 --- a/clang/test/CXX/dcl.dcl/p4-0x.cpp +++ b/clang/test/CXX/dcl.dcl/p4-0x.cpp @@ -18,7 +18,7 @@ static_assert(S(false), "not so fast"); // expected-error {{not so fast}} static_assert(T(), ""); static_assert(U(), ""); // expected-error {{ambiguous}} -static_assert(false, L"\x14hi" // expected-error {{an unevaluated string literal cannot have an encoding prefix}} \ +static_assert(false, L"\x14hi" // expected-warning {{encoding prefix 'L' on an unevaluated string literal has no effect and is incompatible with c++2c}} \ // expected-error {{invalid escape sequence '\x14' in an unevaluated string literal}} "!" R"x(")x"); diff --git a/clang/test/FixIt/unevaluated-strings.cpp b/clang/test/FixIt/unevaluated-strings.cpp new file mode 100644 index 00000000000000..f42e2fc5fb11d3 --- /dev/null +++ b/clang/test/FixIt/unevaluated-strings.cpp @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -verify -std=c++2c %s +// RUN: cp %s %t +// RUN: not %clang_cc1 -x c++ -std=c++2c -fixit %t +// RUN: %clang_cc1 -x c++ -std=c++2c %t +// RUN: not %clang_cc1 -std=c++2c -x c++ -fsyntax-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +static_assert(true, L""); // expected-error{{an unevaluated string literal cannot have an encoding prefix}} +// CHECK: fix-it:{{.*}}:{7:21-7:22} + +static_assert(true, u8""); // expected-error{{an unevaluated string literal cannot have an encoding prefix}} +// CHECK: fix-it:{{.*}}:{10:21-10:23} + +static_assert(true, u""); // expected-error{{an unevaluated string literal cannot have an encoding prefix}} +// CHECK: fix-it:{{.*}}:{13:21-13:22} + +static_assert(true, U""); // expected-error{{an unevaluated string literal cannot have an encoding prefix}} +// CHECK: fix-it:{{.*}}:{16:21-16:22} diff --git a/clang/test/Sema/static-assert.c b/clang/test/Sema/static-assert.c index 6ede89dc006907..4e9e6b7ee558bd 100644 --- a/clang/test/Sema/static-assert.c +++ b/clang/test/Sema/static-assert.c @@ -94,3 +94,20 @@ _Static_assert(__builtin_strlen("1"), ""); // ext-warning {{'_Static_assert' is // __builtin_strlen(literal) is considered an integer constant expression // and doesn't cause a pedantic warning #endif + + +_Static_assert(0, L"\xFFFFFFFF"); // expected-warning {{encoding prefix 'L' on an unevaluated string literal has no effect}} \ + // expected-error {{invalid escape sequence '\xFFFFFFFF' in an unevaluated string literal}} \ + // expected-error {{hex escape sequence out of range}} \ + // ext-warning {{'_Static_assert' is a C11 extension}} +_Static_assert(0, L"\u1234"); // expected-warning {{encoding prefix 'L' on an unevaluated string literal has no effect}} \ + // expected-error {{static assertion failed: ሴ}} \ + // ext-warning {{'_Static_assert' is a C11 extension}} + +_Static_assert(0, L"\x1ff" // expected-warning {{encoding prefix 'L' on an unevaluated string literal has no effect}} \ + // expected-error {{hex escape sequence out of range}} \ + // expected-error {{invalid escape sequence '\x1ff' in an unevaluated string literal}} \ + // ext-warning {{'_Static_assert' is a C11 extension}} + "0\x123" // expected-error {{invalid escape sequence '\x123' in an unevaluated string literal}} + "fx\xfffff" // expected-error {{invalid escape sequence '\xfffff' in an unevaluated string literal}} + "goop"); diff --git a/clang/test/SemaCXX/static-assert.cpp b/clang/test/SemaCXX/static-assert.cpp index da8b726a3e4b07..5d64ff682a20e5 100644 --- a/clang/test/SemaCXX/static-assert.cpp +++ b/clang/test/SemaCXX/static-assert.cpp @@ -29,13 +29,19 @@ template struct S { S s1; // expected-note {{in instantiation of template class 'S' requested here}} S s2; -static_assert(false, L"\xFFFFFFFF"); // expected-error {{an unevaluated string literal cannot have an encoding prefix}} \ - // expected-error {{invalid escape sequence '\xFFFFFFFF' in an unevaluated string literal}} -static_assert(false, u"\U000317FF"); // expected-error {{an unevaluated string literal cannot have an encoding prefix}} -// FIXME: render this as u8"\u03A9" -static_assert(false, u8"Ω"); // expected-error {{an unevaluated string literal cannot have an encoding prefix}} -static_assert(false, L"\u1234"); // expected-error {{an unevaluated string literal cannot have an encoding prefix}} -static_assert(false, L"\x1ff" // expected-error {{an unevaluated string literal cannot have an encoding prefix}} \ +static_assert(false, L"\xFFFFFFFF"); // expected-warning {{encoding prefix 'L' on an unevaluated string literal has no effect and is incompatible with c++2c}} \ + // expected-error {{invalid escape sequence '\xFFFFFFFF' in an unevaluated string literal}} \ + // expected-error {{hex escape sequence out of range}} +static_assert(false, u"\U000317FF"); // expected-warning {{encoding prefix 'u' on an unevaluated string literal has no effect and is incompatible with c++2c}} \ + // expected-error {{static assertion failed}} + +static_assert(false, u8"Ω"); // expected-warning {{encoding prefix 'u8' on an unevaluated string literal has no effect and is incompatible with c++2c}} \ + // expected-error {{static assertion failed: Ω}} +static_assert(false, L"\u1234"); // expected-warning {{encoding prefix 'L' on an unevaluated string literal has no effect and is incompatible with c++2c}} \ + // expected-error {{static assertion failed: ሴ}} + +static_assert(false, L"\x1ff" // expected-warning {{encoding prefix 'L' on an unevaluated string literal has no effect and is incompatible with c++2c}} \ + // expected-error {{hex escape sequence out of range}} \ // expected-error {{invalid escape sequence '\x1ff' in an unevaluated string literal}} "0\x123" // expected-error {{invalid escape sequence '\x123' in an unevaluated string literal}} "fx\xfffff" // expected-error {{invalid escape sequence '\xfffff' in an unevaluated string literal}}