include-what-you-use/iwyu_preprocessor.cc

1167 lines
47 KiB
C++

//===--- iwyu_preprocessor.cc - handle #includes/#defines for iwyu --------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "iwyu_preprocessor.h"
#include <cstddef> // for size_t
#include <cstring>
#include <algorithm>
#include <iterator>
#include <string> // for string, basic_string, etc
#include <utility> // for pair, make_pair
#include "iwyu_ast_util.h"
#include "iwyu_globals.h"
#include "iwyu_include_picker.h"
#include "iwyu_lexer_utils.h"
#include "iwyu_location_util.h"
#include "iwyu_output.h"
#include "iwyu_path_util.h"
#include "iwyu_port.h" // for CHECK_
#include "iwyu_stl_util.h"
#include "iwyu_string_util.h"
#include "iwyu_verrs.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Lex/MacroInfo.h"
using clang::FileEntry;
using clang::FileEntryRef;
using clang::FileID;
using clang::MacroDefinition;
using clang::MacroDirective;
using clang::MacroInfo;
using clang::NamedDecl;
using clang::Preprocessor;
using clang::SourceLocation;
using clang::SourceRange;
using clang::Token;
using llvm::errs;
using llvm::StringRef;
using std::make_pair;
using std::string;
namespace SrcMgr = clang::SrcMgr;
namespace include_what_you_use {
namespace {
// TODO(dsturtevant): Perhaps make this method accessible iwyu-wide.
// At first blush, iwyu_output is the place to put it, but that would
// introduce a circular dependency between iwyu_output and
// iwyu_ast_util.
void Warn(SourceLocation loc, const string& message) {
errs() << PrintableLoc(loc) << ": warning: " << message << "\n";
}
// For use with no_forward_declare. Allow people to specify forward
// declares with or without the leading "::", and don't make them use
// (anonymous namespace).
string NormalizeNamespaces(string symbol) {
if (StartsWith(symbol, "::")) {
symbol = symbol.substr(2);
}
const char kAnonymousNamespaceQualifier[] = "(anonymous namespace)::";
for (;;) {
const string::size_type index = symbol.find(kAnonymousNamespaceQualifier);
if (index == string::npos) {
break;
}
symbol = (symbol.substr(0, index) +
symbol.substr(index + strlen(kAnonymousNamespaceQualifier)));
}
return symbol;
}
} // namespace
//------------------------------------------------------------
// Utilities for examining source files.
// For a particular #include line that include_loc points to,
// returns the include as written by the user, including <> or "".
// This works even for computed #includes ('#include MACRO'): we
// point to the string the macro expands to.
// Simplifying wrapper around the iwyu_lexer function.
static string GetIncludeNameAsWritten(SourceLocation include_loc) {
return GetIncludeNameAsWritten(include_loc, DefaultDataGetter());
}
//------------------------------------------------------------
// Utilities on macros.
static string GetName(const Token& token) {
return token.getIdentifierInfo()->getName().str();
}
//------------------------------------------------------------
// Utilities for handling iwyu-specific pragma comments.
namespace {
// Given a vector of tokens, a token to match, and an expected number
// of tokens, return true if the number of tokens is at least the
// expected number and the first token matches the given token, else
// false. In addition, if in the 'return true' case there are more
// tokens than expected, warn if the first one doesn't start "//" or
// "*/", the latter presumably closing a C-style comment.
// <loc>, which is only used for a warning message, should refer
// to the beginning of the comment containing the tokens.
bool MatchOneToken(const vector<string>& tokens,
const string& token,
size_t num_expected_tokens,
SourceLocation loc) {
if (tokens.size() < num_expected_tokens) {
return false;
}
if (tokens[0] != token) {
return false;
}
if (tokens.size() > num_expected_tokens &&
!StartsWith(tokens[num_expected_tokens], "//") &&
!StartsWith(tokens[num_expected_tokens], "*/")) {
Warn(loc, "Extra tokens on pragma line");
}
return true;
}
// Given a vector of tokens, two tokens to match, and an expected
// number of tokens, return true if the number of tokens is at least
// the expected number and the first two tokens match the given
// tokens, else false. In addition, if in the 'return true' case there
// are more tokens than expected, warn if the first one doesn't start
// "//".
// <loc>, which is only used for a warning message, should refer
// to the beginning of the comment containing the tokens.
bool MatchTwoTokens(const vector<string>& tokens,
const string& token1,
const string& token2,
size_t num_expected_tokens,
SourceLocation loc) {
if (tokens.size() < num_expected_tokens) {
return false;
}
if (tokens[0] != token1) {
return false;
}
if (tokens[1] != token2) {
return false;
}
if (tokens.size() > num_expected_tokens &&
!StartsWith(tokens[num_expected_tokens], "//") &&
!StartsWith(tokens[num_expected_tokens], "*/")) {
// Accept but warn.
Warn(loc, "Extra tokens on pragma line");
}
return true;
}
} // namespace
// Call this function only when the given file is the one currently
// being processed (or a file directly including it, when the current
// file has not processed any comments yet). Return true if only if
// there is an open begin_exports pragma in the current state of the
// parse of the given file. Note that there may be open begin_exports
// in including files. They don't matter for this function.
bool IwyuPreprocessorInfo::HasOpenBeginExports(const FileEntry* file) const {
return !begin_exports_location_stack_.empty() &&
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);
return false; // No tokens pushed.
}
void IwyuPreprocessorInfo::HandlePragmaComment(SourceRange comment_range) {
const SourceLocation begin_loc = comment_range.getBegin();
const SourceLocation end_loc = comment_range.getEnd();
const char* begin_text = DefaultDataGetter().GetCharacterData(begin_loc);
const char* end_text = DefaultDataGetter().GetCharacterData(end_loc);
string pragma_text(begin_text, end_text);
const FileEntry* const this_file_entry = GetFileEntry(begin_loc);
// Pragmas must start comments.
if (!StripLeft(&pragma_text, "// IWYU pragma: ") &&
!StripLeft(&pragma_text, "/* IWYU pragma: ")) {
return;
}
const vector<string> tokens =
SplitOnWhiteSpacePreservingQuotes(pragma_text, 0);
if (HasOpenBeginExports(this_file_entry)) {
if (MatchOneToken(tokens, "end_exports", 1, begin_loc)) {
ERRSYM(this_file_entry) << "end_exports pragma seen\n";
SourceLocation export_loc_begin = begin_exports_location_stack_.top();
begin_exports_location_stack_.pop();
SourceRange export_range(export_loc_begin, begin_loc);
export_location_ranges_.insert(
std::make_pair(this_file_entry, export_range));
} else {
// No pragma allowed within "begin_exports" - "end_exports"
Warn(begin_loc, "Expected end_exports pragma");
}
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);
return;
}
if (MatchOneToken(tokens, "end_exports", 1, begin_loc)) {
Warn(begin_loc, "end_exports without a begin_exports");
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];
if (!IsQuotedInclude(suggested)) {
Warn(begin_loc, "Suggested include must be a quoted header");
return;
}
const string quoted_this_file =
ConvertToQuotedInclude(GetFilePath(begin_loc));
VERRS(8) << "Adding dynamic mapping for private pragma\n";
MutableGlobalIncludePicker()->AddMapping(quoted_this_file,
MappedInclude(suggested));
MutableGlobalIncludePicker()->MarkIncludeAsPrivate(quoted_this_file);
return;
}
if (MatchOneToken(tokens, "private", 1, begin_loc)) {
const string path_this_file = GetFilePath(begin_loc);
MutableGlobalIncludePicker()->MarkPathAsPrivate(path_this_file);
ERRSYM(this_file_entry) << "Adding private path: "
<< path_this_file << "\n";
return;
}
if (MatchOneToken(tokens, "no_include", 2, begin_loc)) {
// 2nd token should be an quoted header.
const string& inhibited = tokens[1];
if (!IsQuotedInclude(inhibited)) {
Warn(begin_loc, "Inhibited include must be a quoted header");
return;
}
no_include_map_[this_file_entry].insert(inhibited);
ERRSYM(this_file_entry) << "Inhibiting include of "
<< inhibited << "\n";
return;
}
if (MatchOneToken(tokens, "no_forward_declare", 2, begin_loc)) {
// 2nd token should be the qualified name of a symbol.
const string normalized_symbol = NormalizeNamespaces(tokens[1]);
no_forward_declare_map_[this_file_entry].insert(normalized_symbol);
ERRSYM(this_file_entry) << "Inhibiting forward-declare of "
<< normalized_symbol << "\n";
return;
}
if (MatchOneToken(tokens, "friend", 2, begin_loc)) {
// 2nd token should be a regex.
string regex = tokens[1];
// The regex is expected to match a quoted include. If the user
// didn't put quotes, assume they wanted a non-system file.
if (!IsQuotedInclude(regex))
regex = "\"(" + regex + ")\"";
ERRSYM(this_file_entry) << GetFilePath(begin_loc)
<< " adding friend regex " << regex << "\n";
MutableGlobalIncludePicker()->AddFriendRegex(
GetFilePath(begin_loc), regex);
return;
}
if (MatchOneToken(tokens, "associated", 1, begin_loc)) {
if (associated_pragma_location_.isInvalid()) {
associated_pragma_location_ = begin_loc;
}
return;
}
// "keep" and "export" are handled in MaybeProtectInclude.
if (!MatchOneToken(tokens, "keep", 1, begin_loc)
&& !MatchOneToken(tokens, "export", 1, begin_loc)) {
Warn(begin_loc, "Unknown or malformed pragma (" + pragma_text + ")");
return;
}
}
void IwyuPreprocessorInfo::ProcessHeadernameDirectivesInFile(
SourceLocation file_beginning) {
SourceLocation current_loc = file_beginning;
while (true) {
// Figure out the canonical name of this file. We can't use
// GetFilePath() because it may not interact properly with -I.
current_loc = GetLocationAfter(current_loc,
"@file ",
DefaultDataGetter());
if (!current_loc.isValid()) {
break;
}
const string filename = GetSourceTextUntilEndOfLine(current_loc,
DefaultDataGetter()).str();
// Use "" or <> based on where the file lives.
string quoted_private_include;
if (IsSystemIncludeFile(GetFilePath(current_loc)))
quoted_private_include = "<" + filename + ">";
else
quoted_private_include = "\"" + filename + "\"";
// TODO(dsturtevant): Maybe place restrictions on the
// placement. E.g., in a comment, before any code, or perhaps only
// when in the same comment as an @file directive.
current_loc = GetLocationAfter(current_loc,
"@headername{",
DefaultDataGetter());
if (!current_loc.isValid()) {
break;
}
string after_text = GetSourceTextUntilEndOfLine(current_loc,
DefaultDataGetter()).str();
const string::size_type close_brace_pos = after_text.find('}');
if (close_brace_pos == string::npos) {
Warn(current_loc, "@headername directive missing a closing brace");
continue;
}
after_text = after_text.substr(0, close_brace_pos);
vector<string> public_includes = Split(after_text, ",", 0);
for (string& public_include : public_includes) {
StripWhiteSpace(&public_include);
const string quoted_header_name = "<" + public_include + ">";
VERRS(8) << "Adding dynamic mapping for @headername\n";
MutableGlobalIncludePicker()->AddMapping(
quoted_private_include, MappedInclude(quoted_header_name));
MutableGlobalIncludePicker()->MarkIncludeAsPrivate(
quoted_private_include);
}
break; // No more than one @headername directive allowed.
}
}
//------------------------------------------------------------
// Utilities for adding #includes.
// Helper function that returns iwyu_file_info_map_[file_entry] if
// it already exists, or creates a new one and returns it otherwise.
IwyuFileInfo* IwyuPreprocessorInfo::GetFromFileInfoMap(const FileEntry* file) {
IwyuFileInfo* iwyu_file_info = FindInMap(&iwyu_file_info_map_, file);
if (!iwyu_file_info) {
const string quoted_include = ConvertToQuotedInclude(GetFilePath(file));
iwyu_file_info_map_.insert(
make_pair(file, IwyuFileInfo(file, this, quoted_include)));
iwyu_file_info = FindInMap(&iwyu_file_info_map_, file);
CHECK_(iwyu_file_info); // should succeed this time!
}
return iwyu_file_info;
}
void IwyuPreprocessorInfo::InsertIntoFileInfoMap(
const FileEntry* file, const string& quoted_include_name) {
if (!FindInMap(&iwyu_file_info_map_, file)) {
iwyu_file_info_map_.insert(
make_pair(file, IwyuFileInfo(file, this, quoted_include_name)));
}
}
// Sometimes, we can tell just by looking at an #include line
// that iwyu should never recommend removing the #include. For
// instance, if it has an IWYU pragma saying to keep it.
void IwyuPreprocessorInfo::MaybeProtectInclude(
SourceLocation includer_loc, const FileEntry* includee,
const string& include_name_as_written) {
const FileEntry* includer = GetFileEntry(includer_loc);
if (IsBuiltinOrCommandLineFile(includer))
return;
string protect_reason;
// We always keep lines with pragmas "keep" or "export".
// TODO(dsturtevant): As written "// // IWYU pragma: keep" is incorrectly
// 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") ||
HasOpenBeginKeep(includer)) {
protect_reason = "pragma_keep";
FileInfoFor(includer)->ReportKnownDesiredFile(includee);
} else if (ShouldKeepIncludeFor(includee)) {
// The command line version of pragma keep.
protect_reason = "--keep";
FileInfoFor(includer)->ReportKnownDesiredFile(includee);
} else if (LineHasText(includer_loc, "// IWYU pragma: export") ||
LineHasText(includer_loc, "/* IWYU pragma: export") ||
HasOpenBeginExports(includer)) {
protect_reason = "pragma_export";
const string includer_path = GetFilePath(includer);
const string quoted_includer = ConvertToQuotedInclude(includer_path);
MappedInclude map_to(quoted_includer, includer_path);
VERRS(8) << "Adding dynamic mapping for export pragma: "
<< "(" << GetFilePath(includee) << ") -> (" << includer_path
<< ")\n";
MutableGlobalIncludePicker()->AddMapping(include_name_as_written, map_to);
// Relative includes can be problematic as map keys, because they are
// context-dependent. Convert it to a context-free quoted include
// (which may contain the full path to the file), and add that too.
string map_from = ConvertToQuotedInclude(GetFilePath(includee));
if (map_from != include_name_as_written) {
VERRS(8) << "Adding dynamic mapping for export pragma (relative): "
<< "(" << GetFilePath(includee) << ") -> (" << includer_path
<< ")\n";
MutableGlobalIncludePicker()->AddMapping(map_from, map_to);
}
// We also always keep #includes of .c files: iwyu doesn't touch those.
// TODO(csilvers): instead of IsHeaderFile, check if the file has
// any "non-inlined" definitions.
} else if (!IsHeaderFile(GetFilePath(includee))) {
protect_reason = ".cc include";
// If the includee is marked as pch-in-code, it can never be removed.
} else if (FileInfoFor(includee)->is_pch_in_code()) {
protect_reason = "pch in code";
// There's one more place we keep the #include: if our file re-exports it.
// (A decision to re-export an #include counts as a "use" of it.)
// But we need to finalize all #includes before we can test that,
// so we do it in a separate function, ProtectReexportIncludes, below.
}
if (!protect_reason.empty()) {
CHECK_(ContainsKey(iwyu_file_info_map_, includer));
GetFromFileInfoMap(includer)->ReportIncludeFileUse(includee,
include_name_as_written);
ERRSYM(includer) << "Marked dep: " << GetFilePath(includer)
<< " needs to keep " << include_name_as_written
<< " (reason: " << protect_reason << ")\n";
}
}
static void ProtectReexportIncludes(
map<const FileEntry*, IwyuFileInfo>* file_info_map) {
for (map<const FileEntry*, IwyuFileInfo>::iterator
it = file_info_map->begin(); it != file_info_map->end(); ++it) {
IwyuFileInfo& includer = it->second;
set<const FileEntry*> incs = includer.direct_includes_as_fileentries();
const string includer_path = GetFilePath(it->first);
for (const FileEntry* include : incs) {
const string includee_path = GetFilePath(include);
if (GlobalIncludePicker().HasMapping(includee_path, includer_path)) {
includer.ReportIncludeFileUse(include,
ConvertToQuotedInclude(includee_path));
ERRSYM(it->first) << "Marked dep: " << includer_path << " needs to keep"
<< " " << includee_path << " (reason: re-exports)\n";
}
}
}
}
// Called when a #include is encountered. i_n_a_t includes <> or "".
// We keep track of this information in two places:
// 1) iwyu_file_info_map_ maps the includer as a FileEntry* to the
// includee both as the literal name used and as a FileEntry*.
// 2) include_to_fileentry_map_ maps the includee's literal name
// as written to the FileEntry* used. This can be used (in a
// limited way, due to non-uniqueness concerns) to map between
// names and FileEntries.
// We also tell this #include to the include-picker, which may
// use it to fine-tune its include-picking algorithms.
void IwyuPreprocessorInfo::AddDirectInclude(
SourceLocation includer_loc, const FileEntry* includee,
const string& include_name_as_written) {
if (IsBuiltinOrCommandLineFile(includee))
return;
// For files we're going to be reporting IWYU errors for, we need
// both forms of the includee to be specified. For other files, we
// don't care as much.
const FileEntry* includer = GetFileEntry(includer_loc);
if (ShouldReportIWYUViolationsFor(includer)) {
CHECK_(includee != nullptr);
CHECK_(!include_name_as_written.empty());
}
++num_includes_seen_[includer];
GetFromFileInfoMap(includer)->AddInclude(
includee, include_name_as_written, GetLineNumber(includer_loc));
// Make sure the includee has a file-info-map entry too.
InsertIntoFileInfoMap(includee, include_name_as_written);
// The first #include in every translation unit might be a precompiled header
// and we need to mark it as such for later analysis.
bool is_includer_main_compilation_unit = main_file_ && includer == main_file_;
if (is_includer_main_compilation_unit && num_includes_seen_[includer] == 1) {
CHECK_(includee && "The first #include must be an actual file.");
// Now we know includee is the first included header file. Mark it as
// pch-in-code if the user requested it on command-line.
if (GlobalFlags().pch_in_code) {
IwyuFileInfo *includee_file_info = GetFromFileInfoMap(includee);
includee_file_info->set_pch_in_code();
includee_file_info->set_prefix_header();
VERRS(4) << "Marked " << GetFilePath(includee) << " as pch-in-code.\n";
}
}
// We have a rule that if foo.h #includes bar.h, foo.cc doesn't need
// to #include bar.h as well, but instead gets it 'automatically'
// via foo.h. We say that 'foo.h' is an "associated header" for
// foo.cc. Make sure we ignore self-includes, though!
// iwyu_output.cc gets upset if a file is its own associated header.
if (includer == main_file_ && includee != includer &&
BelongsToMainCompilationUnit(includer, includee)) {
GetFromFileInfoMap(includer)
->AddAssociatedHeader(GetFromFileInfoMap(includee));
VERRS(4) << "Marked " << GetFilePath(includee)
<< " as associated header of " << GetFilePath(includer) << ".\n";
// All associated headers need to be included in IWYU analysis.
// We can only get here if IWYU is invoked with an absolute source path and
// its associated header is included by two different path names (e.g.
// "rel/path/assoc.h" and "assoc.h") in different files.
//
// TODO: This line cannot be covered with our current test framework;
// don't forget to add a test case if we build something better in the
// future.
AddGlobToReportIWYUViolationsFor(GetFilePath(includee));
}
// Besides marking headers as "associated header" with heuristics, the user
// can directly mark headers with the associated pragma.
const FileEntry* associated_includer =
GetFileEntry(associated_pragma_location_);
if (associated_pragma_location_.isValid() &&
associated_includer == includer) {
GetFromFileInfoMap(includer)->AddAssociatedHeader(
GetFromFileInfoMap(includee));
VERRS(4) << "Marked " << GetFilePath(includee)
<< " as associated header of " << GetFilePath(includer)
<< " due to associated pragma.\n";
AddGlobToReportIWYUViolationsFor(GetFilePath(includee));
associated_pragma_location_ = SourceLocation();
}
// Also keep track of what FileEntry we ended up using for this name.
// Because we use #include-next, the same include-name can map to
// several files; we use the first such mapping we see, which is the
// top of the #include-next chain.
if (!include_name_as_written.empty()) {
if (!ContainsKey(include_to_fileentry_map_, include_name_as_written)) {
include_to_fileentry_map_[include_name_as_written] = includee;
}
}
// Tell the include-picker about this new include.
MutableGlobalIncludePicker()->AddDirectInclude(
GetFilePath(includer), GetFilePath(includee), include_name_as_written);
MaybeProtectInclude(includer_loc, includee, include_name_as_written);
ERRSYM(includer) << "Added an #include: " << GetFilePath(includer)
<< " -> " << include_name_as_written << "\n";
}
//------------------------------------------------------------
// Preprocessor event handlers.
// Called whenever a macro is expanded. Example: when FOO(a, b) is
// seen in the source code, where FOO() is a macro #defined earlier,
// MacroExpands() will be called once with 'macro_use_token' being
// FOO, and 'directive' containing more information about FOO's
// definition.
void IwyuPreprocessorInfo::MacroExpands(const Token& macro_use_token,
const MacroDefinition& definition,
SourceRange range,
const clang::MacroArgs* /*args*/) {
const FileEntry* macro_file = GetFileEntry(macro_use_token);
const MacroInfo* macro_def = definition.getMacroInfo();
if (ShouldPrintSymbolFromFile(macro_file)) {
errs() << "[ Use macro ] "
<< PrintableLoc(macro_use_token.getLocation())
<< ": " << GetName(macro_use_token) << " "
<< "(from " << PrintableLoc(macro_def->getDefinitionLoc()) << ")\n";
}
ReportMacroUse(GetName(macro_use_token),
macro_use_token.getLocation(),
macro_def->getDefinitionLoc());
}
void IwyuPreprocessorInfo::MacroDefined(const Token& id,
const MacroDirective* directive) {
const MacroInfo* macro = directive->getMacroInfo();
const SourceLocation macro_loc = macro->getDefinitionLoc();
ERRSYM(GetFileEntry(macro_loc))
<< "[ #define ] " << PrintableLoc(macro_loc)
<< ": " << GetName(id) << "\n";
// We'd like to do an iwyu check on every token in the macro
// definition, but without knowing how and where the macro will be
// used, we don't have enough context to. But we *can* check those
// tokens that are macro calls: that is, one macro calling another.
// We can't do the checking as we go, since macros can refer to
// macros that come after them in the source file, so we just store
// every macro that's defined, and every macro it calls from its
// body, and then after reading the whole file we do an iwyu
// analysis on the results. (This can make mistakes if the code
// #undefs and re-defines a macro, but should work fine in practice.)
if (macro_loc.isValid())
macros_definition_loc_[GetName(id)] = macro_loc;
for (const Token& token_in_macro : macro->tokens()) {
if (token_in_macro.getKind() == clang::tok::identifier &&
token_in_macro.getIdentifierInfo()->hasMacroDefinition()) {
macros_called_from_macros_.push_back(token_in_macro);
}
}
}
void IwyuPreprocessorInfo::Ifdef(SourceLocation loc,
const Token& id,
const MacroDefinition& /*definition*/) {
ERRSYM(GetFileEntry(id.getLocation()))
<< "[ #ifdef ] " << PrintableLoc(id.getLocation())
<< ": " << GetName(id) << "\n";
FindAndReportMacroUse(GetName(id), id.getLocation());
}
void IwyuPreprocessorInfo::Ifndef(SourceLocation loc,
const Token& id,
const MacroDefinition& /*definition*/) {
ERRSYM(GetFileEntry(id.getLocation()))
<< "[ #ifndef ] " << PrintableLoc(id.getLocation())
<< ": " << GetName(id) << "\n";
FindAndReportMacroUse(GetName(id), id.getLocation());
}
// Clang will give a MacroExpands() callback for all macro-tokens
// used inside an #if or #elif, *except* macro-tokens used within a
// 'defined' operator. They produce a Defined() callback.
void IwyuPreprocessorInfo::Defined(const Token& id,
const MacroDefinition& /*definition*/,
SourceRange /*range*/) {
ERRSYM(GetFileEntry(id.getLocation()))
<< "[ #if defined ] " << PrintableLoc(id.getLocation())
<< ": " << GetName(id) << "\n";
FindAndReportMacroUse(GetName(id), id.getLocation());
}
void IwyuPreprocessorInfo::InclusionDirective(
SourceLocation hash_loc,
const Token& include_token,
StringRef filename,
bool is_angled,
clang::CharSourceRange filename_range,
clang::OptionalFileEntryRef file,
StringRef search_path,
StringRef relative_path,
const clang::Module* imported,
SrcMgr::CharacteristicKind file_type) {
include_filename_loc_ = filename_range.getBegin();
}
void IwyuPreprocessorInfo::FileChanged(SourceLocation loc,
FileChangeReason reason,
SrcMgr::CharacteristicKind file_type,
FileID exiting_from_id) {
switch (reason) {
case EnterFile:
FileChanged_EnterFile(loc);
return;
case ExitFile:
FileChanged_ExitToFile(
loc, GlobalSourceManager()->getFileEntryForID(exiting_from_id));
return;
case RenameFile:
FileChanged_RenameFile(loc);
return;
case SystemHeaderPragma:
// We see "#pragma GCC system_header".
FileChanged_SystemHeaderPragma(loc);
return;
}
CHECK_UNREACHABLE_("Unknown file change reason");
}
// Called when we see an #include, but decide we don't need to
// actually read it because it's already been #included (and is
// protected by a header guard).
void IwyuPreprocessorInfo::FileSkipped(const FileEntryRef& file,
const Token &filename,
SrcMgr::CharacteristicKind file_type) {
CHECK_(include_filename_loc_.isValid() &&
"Must skip file only for actual inclusion directive");
const string include_name_as_written =
GetIncludeNameAsWritten(include_filename_loc_);
const SourceLocation include_loc =
GetInstantiationLoc(filename.getLocation());
ERRSYM(GetFileEntry(include_loc))
<< "[ (#include) ] " << include_name_as_written
<< " (" << GetFilePath(&file.getFileEntry()) << ")\n";
AddDirectInclude(include_loc, &file.getFileEntry(), include_name_as_written);
if (ShouldReportIWYUViolationsFor(&file.getFileEntry())) {
files_to_report_iwyu_violations_for_.insert(&file.getFileEntry());
}
}
// Called when a file is #included.
void IwyuPreprocessorInfo::FileChanged_EnterFile(
SourceLocation file_beginning) {
// Get the location of the #include directive that resulted in the
// include of the file that file_beginning is in.
const SourceLocation include_loc = GlobalSourceManager()->getIncludeLoc(
GlobalSourceManager()->getFileID(file_beginning));
string include_name_as_written;
if (!IsBuiltinOrCommandLineFile(GetFileEntry(include_loc))) {
CHECK_(include_filename_loc_.isValid() &&
"Include from not built-in file must have inclusion directive");
include_name_as_written = GetIncludeNameAsWritten(include_filename_loc_);
}
ERRSYM(GetFileEntry(include_loc))
<< "[ #include ] " << include_name_as_written
<< " (" << GetFilePath(file_beginning) << ")\n";
const FileEntry* const new_file = GetFileEntry(file_beginning);
if (new_file)
AddDirectInclude(include_loc, new_file, include_name_as_written);
if (IsBuiltinOrCommandLineFile(new_file))
return;
ProcessHeadernameDirectivesInFile(file_beginning);
// The first non-special file entered is the main file.
if (main_file_ == nullptr)
main_file_ = new_file;
if (main_file_ != nullptr &&
BelongsToMainCompilationUnit(GetFileEntry(include_loc), new_file)) {
VERRS(5) << "Added to main compilation unit: "
<< GetFilePath(new_file) << "\n";
AddGlobToReportIWYUViolationsFor(GetFilePath(new_file));
}
if (ShouldReportIWYUViolationsFor(new_file)) {
files_to_report_iwyu_violations_for_.insert(new_file);
}
// Mark is_prefix_header.
CHECK_(new_file && "is_prefix_header is applicable to usual files only");
IwyuFileInfo *includee_file_info = GetFromFileInfoMap(new_file);
const FileEntry* includer_file = GetFileEntry(include_loc);
bool is_prefix_header = false;
if (includer_file) {
// File included from another prefix header file is prefix header too.
IwyuFileInfo *includer_file_info = GetFromFileInfoMap(includer_file);
is_prefix_header = includer_file_info->is_prefix_header();
} else {
// Files included from command line are prefix headers, unless it's the
// main file.
is_prefix_header = (new_file != main_file_);
}
if (is_prefix_header)
includee_file_info->set_prefix_header();
}
// Called when done with an #included file and returning to the parent file.
void IwyuPreprocessorInfo::FileChanged_ExitToFile(
SourceLocation include_loc, const FileEntry* exiting_from) {
ERRSYM(GetFileEntry(include_loc)) << "[ Exiting to ] "
<< PrintableLoc(include_loc) << "\n";
if (HasOpenBeginExports(exiting_from)) {
Warn(begin_exports_location_stack_.top(),
"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) {
ERRSYM(GetFileEntry(new_file)) << "[ Renaming to ] "
<< PrintableLoc(new_file) << "\n";
}
void IwyuPreprocessorInfo::FileChanged_SystemHeaderPragma(SourceLocation loc) {
ERRSYM(GetFileEntry(loc)) << "[ #pragma s_h ] "
<< PrintableLoc(loc) << "\n";
}
//------------------------------------------------------------
// Iwyu checkers.
// Checks whether it's OK to use the given macro defined in file defined_in.
void IwyuPreprocessorInfo::ReportMacroUse(const string& name,
SourceLocation usage_location,
SourceLocation dfn_location) {
// Don't report macro uses that aren't actually in a file somewhere.
if (!dfn_location.isValid() || GetFilePath(dfn_location) == "<built-in>")
return;
const FileEntry* used_in = GetFileEntry(usage_location);
if (ShouldReportIWYUViolationsFor(used_in)) {
// ignore symbols used outside foo.{h,cc}
// TODO(csilvers): this isn't really a symbol use -- it may be ok
// that the symbol isn't defined. For instance:
// foo.h: #define FOO
// bar.h: #ifdef FOO ... #else ... #endif
// baz.cc: #include "foo.h"
// #include "bar.h"
// bang.cc: #include "bar.h"
// We don't want to say that bar.h 'uses' FOO, and thus needs to
// #include foo.h -- adding that #include could break bang.cc.
// I think the solution is to have a 'soft' use -- don't remove it
// if it's there, but don't add it if it's not. Or something.
GetFromFileInfoMap(used_in)->ReportMacroUse(usage_location, dfn_location,
name);
}
const FileEntry* defined_in = GetFileEntry(dfn_location);
GetFromFileInfoMap(defined_in)->ReportDefinedMacroUse(used_in);
}
// As above, but get the definition location from macros_definition_loc_.
void IwyuPreprocessorInfo::FindAndReportMacroUse(const string& name,
SourceLocation loc) {
if (const SourceLocation* dfn_loc =
FindInMap(&macros_definition_loc_, name)) {
ReportMacroUse(name, loc, *dfn_loc);
}
}
//------------------------------------------------------------
// Post-processing functions (done after all source is read).
// Adds of includer's includes, direct or indirect, into retval.
void IwyuPreprocessorInfo::AddAllIncludesAsFileEntries(
const FileEntry* includer, set<const FileEntry*>* retval) const {
set<const FileEntry*> direct_incs =
FileInfoOrEmptyFor(includer).direct_includes_as_fileentries();
for (const FileEntry* include : direct_incs) {
if (ContainsKey(*retval, include)) // avoid infinite recursion
continue;
retval->insert(include);
AddAllIncludesAsFileEntries(include, retval);
}
}
void IwyuPreprocessorInfo::PopulateIntendsToProvideMap() {
CHECK_(intends_to_provide_map_.empty() && "Should only call this fn once");
// Figure out which of the header files we have are public. We'll
// map each one to a set of all private header files that map to it.
map<const FileEntry*, set<const FileEntry*>> private_headers_behind;
for (const auto& fileinfo : iwyu_file_info_map_) {
const FileEntry* header = fileinfo.first;
const vector<MappedInclude> public_headers_for_header =
GlobalIncludePicker().GetCandidateHeadersForFilepath(
GetFilePath(header));
for (const MappedInclude& pub : public_headers_for_header) {
if (const FileEntry* public_file =
GetOrDefault(include_to_fileentry_map_, pub.quoted_include,
nullptr)) {
CHECK_(ContainsKey(iwyu_file_info_map_, public_file));
if (public_file != header) // no credit for mapping to yourself :-)
private_headers_behind[public_file].insert(header);
}
}
}
// Everyone gets to provide from their direct includes. Public
// headers gets to provide from *all* their includes. Likewise,
// when you bring in a public header (because it's one of your
// direct includes), you bring in all its includes as well.
// Basically, a public header is really an equivalence class of
// itself and all its direct includes.
// TODO(csilvers): use AddAssociatedHeaders() to get includes here.
const IncludePicker& picker = GlobalIncludePicker();
for (const auto& fileinfo : iwyu_file_info_map_) {
const FileEntry* file = fileinfo.first;
if (file == nullptr)
continue;
intends_to_provide_map_[file].insert(file); // Everyone provides itself!
if (picker.IsPublic(file)) {
AddAllIncludesAsFileEntries(file, &intends_to_provide_map_[file]);
} else {
const set<const FileEntry*>& direct_includes =
fileinfo.second.direct_includes_as_fileentries();
for (const FileEntry* inc : direct_includes) {
intends_to_provide_map_[file].insert(inc);
if (picker.IsPublic(inc))
AddAllIncludesAsFileEntries(inc, &intends_to_provide_map_[file]);
}
}
}
// Ugh, we can have two files with the same name, using
// #include-next. Merge them.
for (const auto& fileinfo : iwyu_file_info_map_) {
const FileEntry* file = fileinfo.first;
// See if a round-trip to string and back ends up at a different file.
const string quoted_include = ConvertToQuotedInclude(GetFilePath(file));
const FileEntry* other_file =
GetOrDefault(include_to_fileentry_map_, quoted_include, file);
if (other_file != file) {
InsertAllInto(intends_to_provide_map_[file],
&intends_to_provide_map_[other_file]);
// TODO(csilvers): this isn't enough if there are *more* than 2
// files with the same name.
intends_to_provide_map_[file] = intends_to_provide_map_[other_file];
}
}
// Finally, for convenience, we'll say every private header file
// intends to provide exactly what its public header files do.
// That way we don't always have to be mapping private headers to
// public ones before calling this function. Since we don't know
// exactly what public header a private header might map to (if it
// can map to more than one), we just union them all.
// TODO(csilvers): this can be bad: if i1.h maps to both p1.h and
// p2.h, and we end up picking p1.h, and we say that i1.h intends
// to provide symbols from p2.h, we're promising a lie. I think
// this is ok as long as IntendsToProvide means 'If when expanding
// a templated function or class in i1.h, you see the need for
// symbol Foo which isn't a template argument, don't worry about
// it.' Double check whether that's true.
for (const auto& header_map : private_headers_behind) {
const FileEntry* public_header = header_map.first;
for (const FileEntry* private_header : header_map.second) {
CHECK_(ContainsKey(intends_to_provide_map_, private_header));
InsertAllInto(intends_to_provide_map_[public_header],
&intends_to_provide_map_[private_header]);
}
}
// Show our work, at a high enough verbosity level.
for (const auto& header_map : intends_to_provide_map_) {
VERRS(4) << "Intends-to-provide for " << GetFilePath(header_map.first)
<< ":\n";
for (const FileEntry* private_header : header_map.second) {
VERRS(4) << " " << GetFilePath(private_header) << "\n";
}
}
}
void IwyuPreprocessorInfo::PopulateTransitiveIncludeMap() {
CHECK_(transitive_include_map_.empty() && "Should only call this fn once");
for (const auto& fileinfo : iwyu_file_info_map_) {
const FileEntry* file = fileinfo.first;
transitive_include_map_[file].insert(file); // everyone includes itself!
AddAllIncludesAsFileEntries(file, &transitive_include_map_[file]);
}
}
//------------------------------------------------------------
// The public API.
void IwyuPreprocessorInfo::HandlePreprocessingDone() {
CHECK_(main_file_ && "Main file should be present");
FileChanged_ExitToFile(SourceLocation(), main_file_);
// In some cases, macros can refer to macros in files that are
// defined later in other files. In those cases, we can't
// do an iwyu check until all header files have been read.
// (For instance, if we see '#define FOO(x) BAR(!x)', BAR doesn't
// actually have to be defined until FOO is actually used, which
// could be later in the preprocessing.)
for (const Token& token : macros_called_from_macros_) {
FindAndReportMacroUse(GetName(token), token.getLocation());
}
// Other post-processing steps.
for (auto& file_info_map_entry : iwyu_file_info_map_) {
file_info_map_entry.second.HandlePreprocessingDone();
}
MutableGlobalIncludePicker()->FinalizeAddedIncludes();
ProtectReexportIncludes(&iwyu_file_info_map_);
PopulateIntendsToProvideMap();
PopulateTransitiveIncludeMap();
}
bool IwyuPreprocessorInfo::BelongsToMainCompilationUnit(
const FileEntry* includer, const FileEntry* includee) const {
// TODO: Should probably have a CHECK_(main_file_), but this method is
// currently sometimes called with a nullptr main_file_.
if (!includee)
return false;
if (GetCanonicalName(GetFilePath(includee)) ==
GetCanonicalName(GetFilePath(main_file_)))
return true;
// Heuristic: if the main compilation unit's *first* include is
// a file with the same basename, assume that it's the 'associated'
// .h file, even if the canonical names differ. This catches
// cases like 'foo/x.cc' #includes 'foo/public/x.h', or
// 'foo/mailserver/x.cc' #includes 'foo/public/x.h'.
// In the case of pch-in-code make this the *second* include,
// as the PCH must always be first.
int first_include_index = GlobalFlags().pch_in_code ? 2 : 1;
if (includer == main_file_ &&
ContainsKeyValue(num_includes_seen_, includer, first_include_index)) {
if (GetCanonicalName(Basename(GetFilePath(includee))) ==
GetCanonicalName(Basename(GetFilePath(main_file_))))
return true;
}
return false;
}
const FileEntry* IwyuPreprocessorInfo::IncludeToFileEntry(
const string quoted_include) const {
return GetOrDefault(include_to_fileentry_map_, quoted_include, nullptr);
}
IwyuFileInfo* IwyuPreprocessorInfo::FileInfoFor(const FileEntry* file) const {
return const_cast<IwyuFileInfo*>(FindInMap(&iwyu_file_info_map_, file));
}
const IwyuFileInfo& IwyuPreprocessorInfo::FileInfoOrEmptyFor(
const FileEntry* file) const {
const IwyuFileInfo* retval = FindInMap(&iwyu_file_info_map_, file);
if (retval)
return *retval;
return empty_file_info_;
}
bool IwyuPreprocessorInfo::PublicHeaderIntendsToProvide(
const FileEntry* public_header, const FileEntry* other_file) const {
if (const set<const FileEntry*>* provides =
FindInMap(&intends_to_provide_map_, public_header)) {
return ContainsKey(*provides, other_file);
}
return false;
}
bool IwyuPreprocessorInfo::FileTransitivelyIncludes(
const FileEntry* includer, const FileEntry* includee) const {
if (const set<const FileEntry*>* all_includes =
FindInMap(&transitive_include_map_, includer)) {
return ContainsKey(*all_includes, includee);
}
return false;
}
bool IwyuPreprocessorInfo::FileTransitivelyIncludes(
const FileEntry* includer, const string& quoted_includee) const {
if (const set<const FileEntry*>* all_includes =
FindInMap(&transitive_include_map_, includer)) {
for (const FileEntry* include : *all_includes) {
if (ConvertToQuotedInclude(GetFilePath(include)) == quoted_includee)
return true;
}
}
return false;
}
bool IwyuPreprocessorInfo::FileTransitivelyIncludes(
const string& quoted_includer, const FileEntry* includee) const {
for (const auto& entry : transitive_include_map_) {
if (ConvertToQuotedInclude(GetFilePath(entry.first)) == quoted_includer)
return ContainsKey(entry.second, includee);
}
return false;
}
bool IwyuPreprocessorInfo::IncludeIsInhibited(
const clang::FileEntry* file, const string& other_filename) const {
const set<string>* inhibited_includes = FindInMap(&no_include_map_, file);
return (inhibited_includes != nullptr) &&
ContainsKey(*inhibited_includes, other_filename);
}
bool IwyuPreprocessorInfo::ForwardDeclareIsInhibited(
const clang::FileEntry* file, const string& qualified_symbol_name) const {
const string normalized_symbol_name =
NormalizeNamespaces(qualified_symbol_name);
const set<string>* inhibited_forward_declares =
FindInMap(&no_forward_declare_map_, file);
return (inhibited_forward_declares != nullptr) &&
ContainsKey(*inhibited_forward_declares, normalized_symbol_name);
}
bool IwyuPreprocessorInfo::ForwardDeclareIsMarkedKeep(
const NamedDecl* decl) const {
// Use end-location so that any trailing comments match only on the last line.
SourceLocation loc = decl->getEndLoc();
// Is the decl part of a begin_keep/end_keep block?
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;
}
}
// Is the declaration itself marked with trailing comment?
return (LineHasText(loc, "// IWYU pragma: keep") ||
LineHasText(loc, "/* IWYU pragma: keep"));
}
bool IwyuPreprocessorInfo::ForwardDeclareIsExported(
const NamedDecl* decl) const {
// Use end-location so that any trailing comments match only on the last line.
SourceLocation loc = decl->getEndLoc();
// Is the decl part of a begin_exports/end_exports block?
const FileEntry* file = GetFileEntry(loc);
auto export_ranges = export_location_ranges_.equal_range(file);
for (auto it = export_ranges.first; it != export_ranges.second; ++it) {
if (it->second.fullyContains(loc)) {
return true;
}
}
// Is the declaration itself marked with trailing comment?
return (LineHasText(loc, "// IWYU pragma: export") ||
LineHasText(loc, "/* IWYU pragma: export"));
}
} // namespace include_what_you_use