https://github.com/zfogg updated https://github.com/llvm/llvm-project/pull/173721
>From 1203ee4308435eb7232de796448dc430a7327e3a Mon Sep 17 00:00:00 2001 From: Zachary Fogg <[email protected]> Date: Sat, 27 Dec 2025 07:50:15 -0500 Subject: [PATCH] [Clang][Lex] Fix __has_include_next to return false when no valid next dir When a file is found via an absolute path (not through a search directory), `getIncludeNextStart()` returns `{nullptr, LookupFromFile}`. In this case, `__has_include_next()` should return false since there is no "next" directory to search in. Previously, `EvaluateHasIncludeCommon` would still attempt the file lookup, which could cause issues with VFS overlays when headers check for the existence of other headers using `__has_include_next` in non-taken preprocessor branches. This fix: 1. Adds early return in `EvaluateHasIncludeNext` when Lookup is null 2. Refactors token consumption into `ConsumeHasIncludeTokens` helper 3. Adds a test case for the absolute path scenario --- clang/lib/Lex/PPMacroExpansion.cpp | 77 ++++++++++++++----- .../has-include-next-absolute/test_header.h | 10 +++ .../Preprocessor/has_include_next_absolute.c | 17 ++++ 3 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 clang/test/Preprocessor/Inputs/has-include-next-absolute/test_header.h create mode 100644 clang/test/Preprocessor/has_include_next_absolute.c diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp index 5efa4b5b3f872..99378b7b36d73 100644 --- a/clang/lib/Lex/PPMacroExpansion.cpp +++ b/clang/lib/Lex/PPMacroExpansion.cpp @@ -1131,13 +1131,23 @@ static bool HasExtension(const Preprocessor &PP, StringRef Extension) { #undef EXTENSION } -/// EvaluateHasIncludeCommon - Process a '__has_include("path")' +/// Result of consuming __has_include/__has_include_next tokens. +struct HasIncludeResult { + bool Valid; // Whether token parsing succeeded. + StringRef Filename; // The parsed filename. + SourceLocation FileLoc; // Location of the filename token. + bool IsAngled; // True for <...>, false for "...". +}; + +/// ConsumeHasIncludeTokens - Consume the tokens for a '__has_include("path")' /// or '__has_include_next("path")' expression. -/// Returns true if successful. -static bool EvaluateHasIncludeCommon(Token &Tok, IdentifierInfo *II, - Preprocessor &PP, - ConstSearchDirIterator LookupFrom, - const FileEntry *LookupFromFile) { +/// Returns a HasIncludeResult indicating whether parsing succeeded and +/// providing the filename if so. +static HasIncludeResult +ConsumeHasIncludeTokens(Token &Tok, IdentifierInfo *II, Preprocessor &PP, + SmallString<128> &FilenameBuffer) { + HasIncludeResult Result = {false, StringRef(), SourceLocation(), false}; + // Save the location of the current token. If a '(' is later found, use // that location. If not, use the end of this location instead. SourceLocation LParenLoc = Tok.getLocation(); @@ -1148,13 +1158,13 @@ static bool EvaluateHasIncludeCommon(Token &Tok, IdentifierInfo *II, // Return a valid identifier token. assert(Tok.is(tok::identifier)); Tok.setIdentifierInfo(II); - return false; + return Result; } // Get '('. If we don't have a '(', try to form a header-name token. do { if (PP.LexHeaderName(Tok)) - return false; + return Result; } while (Tok.getKind() == tok::comment); // Ensure we have a '('. @@ -1165,25 +1175,24 @@ static bool EvaluateHasIncludeCommon(Token &Tok, IdentifierInfo *II, // If the next token looks like a filename or the start of one, // assume it is and process it as such. if (Tok.isNot(tok::header_name)) - return false; + return Result; } else { // Save '(' location for possible missing ')' message. LParenLoc = Tok.getLocation(); if (PP.LexHeaderName(Tok)) - return false; + return Result; } if (Tok.isNot(tok::header_name)) { PP.Diag(Tok.getLocation(), diag::err_pp_expects_filename); - return false; + return Result; } // Reserve a buffer to get the spelling. - SmallString<128> FilenameBuffer; bool Invalid = false; StringRef Filename = PP.getSpelling(Tok, FilenameBuffer, &Invalid); if (Invalid) - return false; + return Result; SourceLocation FilenameLoc = Tok.getLocation(); @@ -1195,13 +1204,33 @@ static bool EvaluateHasIncludeCommon(Token &Tok, IdentifierInfo *II, PP.Diag(PP.getLocForEndOfToken(FilenameLoc), diag::err_pp_expected_after) << II << tok::r_paren; PP.Diag(LParenLoc, diag::note_matching) << tok::l_paren; - return false; + return Result; } - bool isAngled = PP.GetIncludeFilenameSpelling(Tok.getLocation(), Filename); + bool IsAngled = PP.GetIncludeFilenameSpelling(Tok.getLocation(), Filename); // If GetIncludeFilenameSpelling set the start ptr to null, there was an // error. if (Filename.empty()) + return Result; + + Result.Valid = true; + Result.Filename = Filename; + Result.FileLoc = FilenameLoc; + Result.IsAngled = IsAngled; + return Result; +} + +/// EvaluateHasIncludeCommon - Process a '__has_include("path")' +/// or '__has_include_next("path")' expression. +/// Returns true if the file exists. +static bool EvaluateHasIncludeCommon(Token &Tok, IdentifierInfo *II, + Preprocessor &PP, + ConstSearchDirIterator LookupFrom, + const FileEntry *LookupFromFile) { + SmallString<128> FilenameBuffer; + HasIncludeResult ParseResult = + ConsumeHasIncludeTokens(Tok, II, PP, FilenameBuffer); + if (!ParseResult.Valid) return false; // Passing this to LookupFile forces header search to check whether the found @@ -1211,14 +1240,16 @@ static bool EvaluateHasIncludeCommon(Token &Tok, IdentifierInfo *II, // Search include directories. OptionalFileEntryRef File = - PP.LookupFile(FilenameLoc, Filename, isAngled, LookupFrom, LookupFromFile, - nullptr, nullptr, nullptr, &KH, nullptr, nullptr); + PP.LookupFile(ParseResult.FileLoc, ParseResult.Filename, + ParseResult.IsAngled, LookupFrom, LookupFromFile, nullptr, + nullptr, nullptr, &KH, nullptr, nullptr); if (PPCallbacks *Callbacks = PP.getPPCallbacks()) { SrcMgr::CharacteristicKind FileType = SrcMgr::C_User; if (File) FileType = PP.getHeaderSearchInfo().getFileDirFlavor(*File); - Callbacks->HasInclude(FilenameLoc, Filename, isAngled, File, FileType); + Callbacks->HasInclude(ParseResult.FileLoc, ParseResult.Filename, + ParseResult.IsAngled, File, FileType); } // Get the result value. A result of true means the file exists. @@ -1333,6 +1364,16 @@ bool Preprocessor::EvaluateHasIncludeNext(Token &Tok, IdentifierInfo *II) { const FileEntry *LookupFromFile; std::tie(Lookup, LookupFromFile) = getIncludeNextStart(Tok); + // If there's no valid "next" search location (file was found via absolute + // path), consume the tokens but return false - there's no "next" to find. + // Primary file case is excluded to preserve existing behavior. + if (!Lookup && !LookupFromFile && !isInPrimaryFile()) { + // Still need to consume the tokens to maintain preprocessor state. + SmallString<128> FilenameBuffer; + ConsumeHasIncludeTokens(Tok, II, *this, FilenameBuffer); + return false; + } + return EvaluateHasIncludeCommon(Tok, II, *this, Lookup, LookupFromFile); } diff --git a/clang/test/Preprocessor/Inputs/has-include-next-absolute/test_header.h b/clang/test/Preprocessor/Inputs/has-include-next-absolute/test_header.h new file mode 100644 index 0000000000000..5142adb6dfa50 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/has-include-next-absolute/test_header.h @@ -0,0 +1,10 @@ +// Test header for __has_include_next with absolute path +// When this header is found via absolute path (not through search directories), +// __has_include_next should return false instead of searching from the start +// of the include path. + +#if __has_include_next(<nonexistent_header.h>) +#error "__has_include_next should return false for nonexistent header" +#endif + +#define TEST_HEADER_INCLUDED 1 diff --git a/clang/test/Preprocessor/has_include_next_absolute.c b/clang/test/Preprocessor/has_include_next_absolute.c new file mode 100644 index 0000000000000..35fd4bd6594fd --- /dev/null +++ b/clang/test/Preprocessor/has_include_next_absolute.c @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -E -include %S/Inputs/has-include-next-absolute/test_header.h \ +// RUN: -verify %s + +// Test that __has_include_next returns false when the current file was found +// via absolute path (not through the search directories). Previously, this +// would incorrectly search from the start of the include path, which could +// cause false positives or fatal errors when it tried to open non-existent +// files. + +// expected-warning@Inputs/has-include-next-absolute/test_header.h:6 {{#include_next in file found relative to primary source file or found by absolute path; will search from start of include path}} + +// Verify the header was included correctly +#ifndef TEST_HEADER_INCLUDED +#error "test_header.h was not included" +#endif + +int main(void) { return 0; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
