Add begin_keep and end_keep pragmas, test and docs

this was a proposed issue in #1095 where one could
make a block of keeps as opposed to marking every
keep with a pragma: keep instruction.

added test to verify and updated documentation
where appropriate.
This commit is contained in:
Daniel Hannon 2022-10-30 21:13:55 +00:00 committed by Kim Gräsman
parent 109fdb4cdd
commit cf1624a4e2
6 changed files with 182 additions and 2 deletions

View File

@ -15,6 +15,20 @@ This pragma applies to a single `#include` directive or forward declaration. It
In this case, `std::vector` isn't used, so `<vector>` would normally be discarded, but the pragma instructs IWYU to leave it. Similarly the class `ForwardDeclaration` isn't used but is kept because of the pragma on it.
## IWYU pragma: begin_keep/end_keep ##
This pragma applies to a set of `#include` directives and forward declarations. It declares that the headers and forward declarations in between are to be left alone by IWYU.
main.cc:
// IWYU pragma: begin_keep
#include <vector>
#include <iostream>
class MyClass;
// IWYU pragma: end_keep
In the provided case nothing within the bounds of `begin_keep` and `end_keep` will be discarded.
## IWYU pragma: export ##
This pragma applies to a single `#include` directive. It says that the current file is to be considered the provider of any symbol from the included file.

View File

@ -243,6 +243,11 @@ Used after
.B #include
directives or forward declarations it ensures that they won't be removed.
.TP
.BR "// IWYU pragma: begin_keep" , " // IWYU pragma: end_keep"
Has the same effect as the previous pragma comment, but applies to a range of
.BR #include s
and forward declarations instead of a single line.
.TP
.B // IWYU pragma: export
Used after an
.B #include

View File

@ -3898,7 +3898,8 @@ class IwyuAstConsumer
} else {
SourceLocation decl_end_location = decl->getSourceRange().getEnd();
if (LineHasText(decl_end_location, "// IWYU pragma: keep") ||
LineHasText(decl_end_location, "/* IWYU pragma: keep")) {
LineHasText(decl_end_location, "/* IWYU pragma: keep") ||
preprocessor_info().ForwardDeclareInKeepRange(decl_end_location)) {
definitely_keep_fwd_decl = true;
}
}

View File

@ -177,6 +177,14 @@ bool IwyuPreprocessorInfo::HasOpenBeginExports(const FileEntry* file) const {
GetFileEntry(begin_exports_location_stack_.top()) == file;
}
// Only call this when the given files is the one being processed
// Only return true if there is an open begin_keep pragma in the current
// state of the parse of the given file.
bool IwyuPreprocessorInfo::HasOpenBeginKeep(const FileEntry* file) const {
return !begin_keep_location_stack_.empty() &&
GetFileEntry(begin_keep_location_stack_.top()) == file;
}
bool IwyuPreprocessorInfo::HandleComment(Preprocessor& pp,
SourceRange comment_range) {
HandlePragmaComment(comment_range);
@ -209,6 +217,20 @@ void IwyuPreprocessorInfo::HandlePragmaComment(SourceRange comment_range) {
return;
}
if (HasOpenBeginKeep(this_file_entry)) {
if (MatchOneToken(tokens, "end_keep", 1, begin_loc)) {
ERRSYM(this_file_entry) << "end_keep pragma seen\n";
SourceLocation keep_loc_begin = begin_keep_location_stack_.top();
begin_keep_location_stack_.pop();
SourceRange keep_range(keep_loc_begin, begin_loc);
keep_location_ranges_.insert(std::make_pair(this_file_entry, keep_range));
} else {
// No pragmas allowed within "begin_keep" - "end_keep"
Warn(begin_loc, "Expected end_keep pragma");
}
return;
}
if (MatchOneToken(tokens, "begin_exports", 1, begin_loc)) {
ERRSYM(this_file_entry) << "begin_exports pragma seen\n";
begin_exports_location_stack_.push(begin_loc);
@ -220,6 +242,17 @@ void IwyuPreprocessorInfo::HandlePragmaComment(SourceRange comment_range) {
return;
}
if (MatchOneToken(tokens, "begin_keep", 1, begin_loc)) {
ERRSYM(this_file_entry) << "begin_keep pragma seen\n";
begin_keep_location_stack_.push(begin_loc);
return;
}
if (MatchOneToken(tokens, "end_keep", 1, begin_loc)) {
Warn(begin_loc, "end_keep without a begin_keep");
return;
}
if (MatchTwoTokens(tokens, "private,", "include", 3, begin_loc)) {
// 3rd token should be a quoted header.
const string& suggested = tokens[2];
@ -397,7 +430,8 @@ void IwyuPreprocessorInfo::MaybeProtectInclude(
// interpreted as a pragma. Maybe do "keep" and "export" pragma handling
// in HandleComment?
if (LineHasText(includer_loc, "// IWYU pragma: keep") ||
LineHasText(includer_loc, "/* IWYU pragma: keep")) {
LineHasText(includer_loc, "/* IWYU pragma: keep") ||
HasOpenBeginKeep(includer)) {
protect_reason = "pragma_keep";
FileInfoFor(includer)->ReportKnownDesiredFile(includee);
@ -790,6 +824,12 @@ void IwyuPreprocessorInfo::FileChanged_ExitToFile(
"begin_exports without an end_exports");
begin_exports_location_stack_.pop();
}
if (HasOpenBeginKeep(exiting_from)) {
Warn(begin_keep_location_stack_.top(),
"begin_keep without an end_keep");
begin_keep_location_stack_.pop();
}
}
void IwyuPreprocessorInfo::FileChanged_RenameFile(SourceLocation new_file) {
@ -1087,4 +1127,15 @@ bool IwyuPreprocessorInfo::ForwardDeclareIsInhibited(
ContainsKey(*inhibited_forward_declares, normalized_symbol_name);
}
bool IwyuPreprocessorInfo::ForwardDeclareInKeepRange(SourceLocation loc) const {
const FileEntry* file = GetFileEntry(loc);
auto keep_ranges = keep_location_ranges_.equal_range(file);
for (auto it = keep_ranges.first; it != keep_ranges.second; ++it) {
if (it->second.fullyContains(loc)) {
return true;
}
}
return false;
}
} // namespace include_what_you_use

View File

@ -34,6 +34,8 @@
// f) // IWYU pragma: no_forward_declare foo::Bar
// g) // IWYU pragma: friend <regexp>
// // IWYU pragma: friend "<regexp>" -- needed if spaces in regexp.
// h) // IWYU pragma: begin_keep
// i) // IWYU pragma: end_keep
// 'Annotation' constructs:
// h) #include "foo/bar/baz.h" // IWYU pragma: export
// i) #include "foo/bar/baz.h" // IWYU pragma: keep
@ -86,6 +88,7 @@ using std::set;
using std::stack;
using std::string;
using std::vector;
using std::multimap;
class IwyuPreprocessorInfo : public clang::PPCallbacks,
public clang::CommentHandler {
@ -165,6 +168,10 @@ class IwyuPreprocessorInfo : public clang::PPCallbacks,
bool ForwardDeclareIsInhibited(
const clang::FileEntry* file, const string& qualified_symbol_name) const;
// Return true if the fwd decl is in the range of a begin_keep -> end_keep
// block.
bool ForwardDeclareInKeepRange(clang::SourceLocation loc) const;
protected:
// Preprocessor event handlers called by Clang.
void MacroExpands(const clang::Token& macro_use_token,
@ -281,6 +288,10 @@ class IwyuPreprocessorInfo : public clang::PPCallbacks,
// there is a pending "begin_exports" pragma.
bool HasOpenBeginExports(const clang::FileEntry* file) const;
// Return true if at the current point in the parse of the given files,
// there is a pending "begin_keep" pragma.
bool HasOpenBeginKeep(const clang::FileEntry* file) const;
// The C++ source file passed in as an argument to the compiler (as
// opposed to other files seen via #includes).
const clang::FileEntry* main_file_;
@ -345,6 +356,15 @@ class IwyuPreprocessorInfo : public clang::PPCallbacks,
// per file in the current inclusion chain..
stack<clang::SourceLocation> begin_exports_location_stack_;
// For processing pragmas. It is the current stack of open "begin_keep"s.
// There should be at most one item in this stack per file in the current
// inclusion chain.
stack<clang::SourceLocation> begin_keep_location_stack_;
// For processing forward decls. It is a multimap containing the bounds of
// every keep range.
multimap<const clang::FileEntry*, clang::SourceRange> keep_location_ranges_;
// For processing associated pragma. It is the current open
// "associated" pragma.
clang::SourceLocation associated_pragma_location_;

View File

@ -0,0 +1,89 @@
//===--- pragma_keep_multi.cc - test input file for iwyu ------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// IWYU_ARGS: -I .
// This tests whether or not begin_keep/end_keep works
// Check empty block
// IWYU pragma: begin_keep
// IWYU pragma: end_keep
// Check block with one include
// IWYU pragma: begin_keep
#include "tests/cxx/comment_pragmas-d22.h"
// IWYU pragma: end_keep
// Check block with several includes
// IWYU pragma: begin_keep
#include "tests/cxx/comment_pragmas-d1.h"
#include "tests/cxx/comment_pragmas-d10.h"
#include "tests/cxx/comment_pragmas-d16.h"
#include "tests/cxx/comment_pragmas-d21.h"
// IWYU pragma: end_keep
// Block with one forward decl
// IWYU pragma: begin_keep
class FakeClass;
// IWYU pragma: end_keep
// Block with several forward decls
// IWYU pragma: begin_keep
class FakeClass2;
class FakeClass3;
class FakeClass4;
// IWYU pragma: end_keep
// Block with mix of includes and fwd decls
// IWYU pragma: begin_keep
#include "tests/cxx/comment_pragmas-d17.h"
#include "tests/cxx/comment_pragmas-d13.h"
#include "tests/cxx/comment_pragmas-d14.h"
class FakeClass5;
class FakeClass6;
class FakeClass7;
// IWYU pragma: end_keep
#include "tests/cxx/comment_pragmas-d9.h"
class AnotherFakeClass;
/**** IWYU_SUMMARY
tests/cxx/pragma_keep_multi.cc should add these lines:
tests/cxx/pragma_keep_multi.cc should remove these lines:
- #include "tests/cxx/comment_pragmas-d9.h" // lines XX-XX
- class AnotherFakeClass; // lines XX-XX
The full include-list for tests/cxx/pragma_keep_multi.cc:
#include "tests/cxx/comment_pragmas-d1.h"
#include "tests/cxx/comment_pragmas-d10.h"
#include "tests/cxx/comment_pragmas-d13.h"
#include "tests/cxx/comment_pragmas-d14.h"
#include "tests/cxx/comment_pragmas-d16.h"
#include "tests/cxx/comment_pragmas-d17.h"
#include "tests/cxx/comment_pragmas-d21.h"
#include "tests/cxx/comment_pragmas-d22.h"
class FakeClass2; // lines XX-XX
class FakeClass3; // lines XX-XX
class FakeClass4; // lines XX-XX
class FakeClass5; // lines XX-XX
class FakeClass6; // lines XX-XX
class FakeClass7; // lines XX-XX
class FakeClass; // lines XX-XX
***** IWYU_SUMMARY */