From 5a48b97de64ce65f06966a296e35f82dcdfccf74 Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Mon, 13 Oct 2025 01:47:58 +0300 Subject: [PATCH 1/4] [Clang] prevent crash on unterminated __has_embed --- clang/docs/ReleaseNotes.rst | 1 + clang/lib/Lex/PPDirectives.cpp | 8 ++++---- clang/lib/Lex/PPMacroExpansion.cpp | 10 ++++------ .../Preprocessor/embed___has_embed_parsing_errors.c | 7 +++++++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 65b086caf3652..7e65ba74dc6fd 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -405,6 +405,7 @@ Bug Fixes in This Version a function without arguments caused us to try to access a non-existent argument. (#GH159080) - Fixed a failed assertion with empty filename arguments in ``__has_embed``. (#GH159898) +- Fixed a crash triggered by unterminated ``__has_embed``. (#GH162953) Bug Fixes to Compiler Builtins ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index 360593d0f33df..67906ca0f461a 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -3648,14 +3648,14 @@ Preprocessor::LexEmbedParameters(Token &CurTok, bool ForHasEmbed) { std::pair Matches) { Diag(CurTok, diag::err_expected) << Expected; Diag(Matches.second, diag::note_matching) << Matches.first; - if (CurTok.isNot(tok::eod)) + if (CurTok.isNot(EndTokenKind)) DiscardUntilEndOfDirective(CurTok); }; auto ExpectOrDiagAndSkipToEOD = [&](tok::TokenKind Kind) { if (CurTok.isNot(Kind)) { Diag(CurTok, diag::err_expected) << Kind; - if (CurTok.isNot(tok::eod)) + if (CurTok.isNot(EndTokenKind)) DiscardUntilEndOfDirective(CurTok); return false; } @@ -3746,7 +3746,7 @@ Preprocessor::LexEmbedParameters(Token &CurTok, bool ForHasEmbed) { if (Result.isNegative()) { Diag(CurTok, diag::err_requires_positive_value) << toString(Result, 10) << /*positive*/ 0; - if (CurTok.isNot(tok::eod)) + if (CurTok.isNot(EndTokenKind)) DiscardUntilEndOfDirective(CurTok); return std::nullopt; } @@ -3889,7 +3889,7 @@ Preprocessor::LexEmbedParameters(Token &CurTok, bool ForHasEmbed) { } if (!ForHasEmbed) { Diag(ParamStartLoc, diag::err_pp_unknown_parameter) << 1 << Parameter; - if (CurTok.isNot(tok::eod)) + if (CurTok.isNot(EndTokenKind)) DiscardUntilEndOfDirective(CurTok); return std::nullopt; } diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp index dec1956ea0f9a..dd80ae586a1f6 100644 --- a/clang/lib/Lex/PPMacroExpansion.cpp +++ b/clang/lib/Lex/PPMacroExpansion.cpp @@ -1262,16 +1262,11 @@ EmbedResult Preprocessor::EvaluateHasEmbed(Token &Tok, IdentifierInfo *II) { std::optional Params = this->LexEmbedParameters(Tok, /*ForHasEmbed=*/true); - assert((Params || Tok.is(tok::eod)) && - "expected success or to be at the end of the directive"); if (!Params) return EmbedResult::Invalid; - if (Params->UnrecognizedParams > 0) - return EmbedResult::NotFound; - - if (!Tok.is(tok::r_paren)) { + if (Tok.isNot(tok::r_paren)) { Diag(this->getLocForEndOfToken(FilenameLoc), diag::err_pp_expected_after) << II << tok::r_paren; Diag(LParenLoc, diag::note_matching) << tok::l_paren; @@ -1280,6 +1275,9 @@ EmbedResult Preprocessor::EvaluateHasEmbed(Token &Tok, IdentifierInfo *II) { return EmbedResult::Invalid; } + if (Params->UnrecognizedParams > 0) + return EmbedResult::NotFound; + SmallString<128> FilenameBuffer; StringRef Filename = this->getSpelling(FilenameTok, FilenameBuffer); if (Filename.empty()) diff --git a/clang/test/Preprocessor/embed___has_embed_parsing_errors.c b/clang/test/Preprocessor/embed___has_embed_parsing_errors.c index 9c512a4882e2d..a389fa257b164 100644 --- a/clang/test/Preprocessor/embed___has_embed_parsing_errors.c +++ b/clang/test/Preprocessor/embed___has_embed_parsing_errors.c @@ -250,3 +250,10 @@ #if __has_embed("") // expected-error {{empty filename}} #endif + +// expected-error@+4 {{missing ')' after '__has_embed'}} \ + expected-error@+4 {{expected value in expression}} \ + expected-error@+4 {{unterminated conditional directive}} \ + expected-note@+4 {{to match this '('}} +#if __has_embed (__FILE__ limit(1) foo +int a = __has_embed (__FILE__); From ba608beef6f036106d723a214ec534a6c25ca1fa Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Mon, 13 Oct 2025 20:14:01 +0300 Subject: [PATCH 2/4] add additional tests --- .../embed___has_embed_parsing_errors.c | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/clang/test/Preprocessor/embed___has_embed_parsing_errors.c b/clang/test/Preprocessor/embed___has_embed_parsing_errors.c index a389fa257b164..1ca0892952de4 100644 --- a/clang/test/Preprocessor/embed___has_embed_parsing_errors.c +++ b/clang/test/Preprocessor/embed___has_embed_parsing_errors.c @@ -251,9 +251,34 @@ #if __has_embed("") // expected-error {{empty filename}} #endif -// expected-error@+4 {{missing ')' after '__has_embed'}} \ - expected-error@+4 {{expected value in expression}} \ - expected-error@+4 {{unterminated conditional directive}} \ - expected-note@+4 {{to match this '('}} +// expected-error@+9 {{unterminated conditional directive}} +// expected-error@+13 {{unterminated conditional directive}} +// expected-error@+17 {{unterminated conditional directive}} +// expected-error@+21 {{unterminated conditional directive}} +// expected-error@+25 {{unterminated conditional directive}} + +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} +#if __has_embed (__FILE__ foo limit(1) + +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} +#if __has_embed (__FILE__ foo + +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} +#if __has_embed ("a" foo() + +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} +#if __has_embed ("a" bar() foo + +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} #if __has_embed (__FILE__ limit(1) foo int a = __has_embed (__FILE__); From ab1d0c73187699071ffaa641ffb9685b30128360 Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Mon, 13 Oct 2025 21:51:55 +0300 Subject: [PATCH 3/4] update tests --- .../embed___has_embed_parsing_errors.c | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/clang/test/Preprocessor/embed___has_embed_parsing_errors.c b/clang/test/Preprocessor/embed___has_embed_parsing_errors.c index 1ca0892952de4..ba2a10968ee78 100644 --- a/clang/test/Preprocessor/embed___has_embed_parsing_errors.c +++ b/clang/test/Preprocessor/embed___has_embed_parsing_errors.c @@ -1,4 +1,12 @@ -// RUN: %clang_cc1 -std=c23 %s -E -verify +// RUN: split-file %s %t +// RUN: %clang_cc1 -std=c23 -E -verify %t/test1.c +// RUN: %clang_cc1 -std=c23 -E -verify %t/test2.c +// RUN: %clang_cc1 -std=c23 -E -verify %t/test3.c +// RUN: %clang_cc1 -std=c23 -E -verify %t/test4.c +// RUN: %clang_cc1 -std=c23 -E -verify %t/test5.c +// RUN: %clang_cc1 -std=c23 -E -verify %t/test6.c + +//--- test1.c // Test the parsing behavior for __has_embed and all of its parameters to ensure we // recover from failures gracefully. @@ -251,34 +259,38 @@ #if __has_embed("") // expected-error {{empty filename}} #endif -// expected-error@+9 {{unterminated conditional directive}} -// expected-error@+13 {{unterminated conditional directive}} -// expected-error@+17 {{unterminated conditional directive}} -// expected-error@+21 {{unterminated conditional directive}} -// expected-error@+25 {{unterminated conditional directive}} - -// expected-error@+3 {{missing ')' after '__has_embed'}} \ - expected-error@+3 {{expected value in expression}} \ - expected-note@+3 {{to match this '('}} +//--- test2.c +// expected-error@+4 {{missing ')' after '__has_embed'}} \ + expected-error@+4 {{expected value in expression}} \ + expected-note@+4 {{to match this '('}} \ + expected-error@+4 {{unterminated conditional directive}} #if __has_embed (__FILE__ foo limit(1) -// expected-error@+3 {{missing ')' after '__has_embed'}} \ - expected-error@+3 {{expected value in expression}} \ - expected-note@+3 {{to match this '('}} +//--- test3.c +// expected-error@+4 {{missing ')' after '__has_embed'}} \ + expected-error@+4 {{expected value in expression}} \ + expected-note@+4 {{to match this '('}} \ + expected-error@+4 {{unterminated conditional directive}} #if __has_embed (__FILE__ foo -// expected-error@+3 {{missing ')' after '__has_embed'}} \ - expected-error@+3 {{expected value in expression}} \ - expected-note@+3 {{to match this '('}} +//--- test4.c +// expected-error@+4 {{missing ')' after '__has_embed'}} \ + expected-error@+4 {{expected value in expression}} \ + expected-note@+4 {{to match this '('}} \ + expected-error@+4 {{unterminated conditional directive}} #if __has_embed ("a" foo() -// expected-error@+3 {{missing ')' after '__has_embed'}} \ - expected-error@+3 {{expected value in expression}} \ - expected-note@+3 {{to match this '('}} +//--- test5.c +// expected-error@+4 {{missing ')' after '__has_embed'}} \ + expected-error@+4 {{expected value in expression}} \ + expected-note@+4 {{to match this '('}} \ + expected-error@+4 {{unterminated conditional directive}} #if __has_embed ("a" bar() foo -// expected-error@+3 {{missing ')' after '__has_embed'}} \ - expected-error@+3 {{expected value in expression}} \ - expected-note@+3 {{to match this '('}} +//--- test6.c +// expected-error@+4 {{missing ')' after '__has_embed'}} \ + expected-error@+4 {{expected value in expression}} \ + expected-note@+4 {{to match this '('}} \ + expected-error@+4 {{unterminated conditional directive}} #if __has_embed (__FILE__ limit(1) foo int a = __has_embed (__FILE__); From cfc2b9a1cadfe7eb6521eec4606871a5aa35e39c Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Wed, 15 Oct 2025 16:22:15 +0300 Subject: [PATCH 4/4] update tests --- .../embed___has_embed_parsing_errors.c | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/clang/test/Preprocessor/embed___has_embed_parsing_errors.c b/clang/test/Preprocessor/embed___has_embed_parsing_errors.c index ba2a10968ee78..8ab53f6b89c0d 100644 --- a/clang/test/Preprocessor/embed___has_embed_parsing_errors.c +++ b/clang/test/Preprocessor/embed___has_embed_parsing_errors.c @@ -1,12 +1,4 @@ -// RUN: split-file %s %t -// RUN: %clang_cc1 -std=c23 -E -verify %t/test1.c -// RUN: %clang_cc1 -std=c23 -E -verify %t/test2.c -// RUN: %clang_cc1 -std=c23 -E -verify %t/test3.c -// RUN: %clang_cc1 -std=c23 -E -verify %t/test4.c -// RUN: %clang_cc1 -std=c23 -E -verify %t/test5.c -// RUN: %clang_cc1 -std=c23 -E -verify %t/test6.c - -//--- test1.c +// RUN: %clang_cc1 -std=c23 %s -E -verify // Test the parsing behavior for __has_embed and all of its parameters to ensure we // recover from failures gracefully. @@ -259,38 +251,34 @@ #if __has_embed("") // expected-error {{empty filename}} #endif -//--- test2.c -// expected-error@+4 {{missing ')' after '__has_embed'}} \ - expected-error@+4 {{expected value in expression}} \ - expected-note@+4 {{to match this '('}} \ - expected-error@+4 {{unterminated conditional directive}} +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} #if __has_embed (__FILE__ foo limit(1) +#endif //--- test3.c -// expected-error@+4 {{missing ')' after '__has_embed'}} \ - expected-error@+4 {{expected value in expression}} \ - expected-note@+4 {{to match this '('}} \ - expected-error@+4 {{unterminated conditional directive}} +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} #if __has_embed (__FILE__ foo +#endif -//--- test4.c -// expected-error@+4 {{missing ')' after '__has_embed'}} \ - expected-error@+4 {{expected value in expression}} \ - expected-note@+4 {{to match this '('}} \ - expected-error@+4 {{unterminated conditional directive}} +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} #if __has_embed ("a" foo() +#endif -//--- test5.c -// expected-error@+4 {{missing ')' after '__has_embed'}} \ - expected-error@+4 {{expected value in expression}} \ - expected-note@+4 {{to match this '('}} \ - expected-error@+4 {{unterminated conditional directive}} +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} #if __has_embed ("a" bar() foo +#endif -//--- test6.c -// expected-error@+4 {{missing ')' after '__has_embed'}} \ - expected-error@+4 {{expected value in expression}} \ - expected-note@+4 {{to match this '('}} \ - expected-error@+4 {{unterminated conditional directive}} +// expected-error@+3 {{missing ')' after '__has_embed'}} \ + expected-error@+3 {{expected value in expression}} \ + expected-note@+3 {{to match this '('}} #if __has_embed (__FILE__ limit(1) foo int a = __has_embed (__FILE__); +#endif