-
Notifications
You must be signed in to change notification settings - Fork 15.6k
[Clang][Lex] Fix __has_include_next to return false when no valid next dir #173721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@llvm/pr-subscribers-clang Author: Zachary Fogg (zfogg) ChangesSummaryFix When a file is included via absolute path, ProblemPreviously,
Solution
Test PlanAdded
cc @jansvoboda11 @AaronBallman Full diff: https://github.com/llvm/llvm-project/pull/173721.diff 3 Files Affected:
diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp
index 5efa4b5b3f872..16a3d9e6665a6 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; }
|
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
e8fc481 to
8201169
Compare
…t 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
8201169 to
1203ee4
Compare
Summary
Fix
__has_include_nextto returnfalsewhen the current file was found via an absolute path (not through a search directory).When a file is included via absolute path,
getIncludeNextStart()returns{nullptr, LookupFromFile}because there's no search directory iterator. In this case,__has_include_next()should returnfalsesince there is no "next" directory to search in.Problem
Previously,
EvaluateHasIncludeCommonwould still attempt the file lookup even whenLookupwas null. This caused issues with:__has_include_nextin non-taken preprocessor branchesSolution
EvaluateHasIncludeNextwhenLookupis nullConsumeHasIncludeTokenshelper to properly consume tokens before returningTest Plan
Added
clang/test/Preprocessor/has_include_next_absolute.cwhich verifies:__has_include_nextreturns 0 when file is included via absolute path__has_includestill works correctlycc @jansvoboda11 @AaronBallman