For dependency scanning, it would be useful to collect header search paths (provided on command-line via `-I` and friends) that were actually used during preprocessing. This patch adds that feature to `HeaderSearch` along with a new remark that reports such paths as they get used.
Previous version of this patch tried to use the existing `LookupFileCache` to report used paths via `HitIdx`. That doesn't work for `ComputeUserEntryUsage` (which is intended to be called *after* preprocessing), because it indexes used search paths by the file name. This means the values get overwritten when the code contains `#include_next`.
Note that `HeaderSearch` doesn't use `HeaderSearchOptions::UserEntries` directly. Instead, `InitHeaderSearch` pre-processes them (adds platform-specific paths, removes duplicates, removes paths that don't exist) and creates `DirectoryLookup` instances. This means we need a mechanism for translating between those two. It's not possible to go from `DirectoryLookup` back to the original `HeaderSearch`, so `InitHeaderSearch` now tracks the relationships explicitly.
Depends on D111557.
Reviewed By: dexonsmith
Differential Revision: https://reviews.llvm.org/D102923
def UnusedPropertyIvar : DiagGroup<"unused-property-ivar">;
def UnusedGetterReturnValue : DiagGroup<"unused-getter-return-value">;
def UsedButMarkedUnused : DiagGroup<"used-but-marked-unused">;
+def UsedSearchPath : DiagGroup<"search-path-usage">;
def UserDefinedLiterals : DiagGroup<"user-defined-literals">;
def UserDefinedWarnings : DiagGroup<"user-defined-warnings">;
def ReorderCtor : DiagGroup<"reorder-ctor">;
"#pragma hdrstop filename not supported, "
"/Fp can be used to specify precompiled header filename">,
InGroup<ClangClPch>;
+def remark_pp_search_path_usage : Remark<
+ "search path used: '%0'">,
+ InGroup<UsedSearchPath>;
def err_pp_file_not_found_angled_include_not_fatal : Error<
"'%0' file not found with <angled> %select{include|import}1; "
"use \"quotes\" instead">;
static std::unique_ptr<HeaderMap> Create(const FileEntry *FE,
FileManager &FM);
- /// Check to see if the specified relative filename is located in this
- /// HeaderMap. If so, open it and return its FileEntry. If RawPath is not
- /// NULL and the file is found, RawPath will be set to the raw path at which
- /// the file was found in the file system. For example, for a search path
- /// ".." and a filename "../file.h" this would be "../../file.h".
- Optional<FileEntryRef> LookupFile(StringRef Filename, FileManager &FM) const;
-
using HeaderMapImpl::dump;
using HeaderMapImpl::getFileName;
using HeaderMapImpl::lookupFilename;
/// Header-search options used to initialize this header search.
std::shared_ptr<HeaderSearchOptions> HSOpts;
+ /// Mapping from SearchDir to HeaderSearchOptions::UserEntries indices.
+ llvm::DenseMap<unsigned, unsigned> SearchDirToHSEntry;
+
DiagnosticsEngine &Diags;
FileManager &FileMgr;
/// NoCurDirSearch is true, then the check for the file in the current
/// directory is suppressed.
std::vector<DirectoryLookup> SearchDirs;
+ /// Whether the DirectoryLookup at the corresponding index in SearchDirs has
+ /// been successfully used to lookup a file.
+ std::vector<bool> SearchDirsUsage;
unsigned AngledDirIdx = 0;
unsigned SystemDirIdx = 0;
bool NoCurDirSearch = false;
DiagnosticsEngine &getDiags() const { return Diags; }
/// Interface for setting the file search paths.
- void SetSearchPaths(const std::vector<DirectoryLookup> &dirs,
- unsigned angledDirIdx, unsigned systemDirIdx,
- bool noCurDirSearch) {
+ void SetSearchPaths(std::vector<DirectoryLookup> dirs, unsigned angledDirIdx,
+ unsigned systemDirIdx, bool noCurDirSearch,
+ llvm::DenseMap<unsigned, unsigned> searchDirToHSEntry) {
assert(angledDirIdx <= systemDirIdx && systemDirIdx <= dirs.size() &&
"Directory indices are unordered");
- SearchDirs = dirs;
+ SearchDirs = std::move(dirs);
+ SearchDirsUsage.assign(SearchDirs.size(), false);
AngledDirIdx = angledDirIdx;
SystemDirIdx = systemDirIdx;
NoCurDirSearch = noCurDirSearch;
+ SearchDirToHSEntry = std::move(searchDirToHSEntry);
//LookupFileCache.clear();
}
void AddSearchPath(const DirectoryLookup &dir, bool isAngled) {
unsigned idx = isAngled ? SystemDirIdx : AngledDirIdx;
SearchDirs.insert(SearchDirs.begin() + idx, dir);
+ SearchDirsUsage.insert(SearchDirsUsage.begin() + idx, false);
if (!isAngled)
AngledDirIdx++;
SystemDirIdx++;
return FI && FI->isImport;
}
+ /// Determine which HeaderSearchOptions::UserEntries have been successfully
+ /// used so far and mark their index with 'true' in the resulting bit vector.
+ std::vector<bool> computeUserEntryUsage() const;
+
/// This method returns a HeaderMap for the specified
/// FileEntry, uniquing them through the 'HeaderMaps' datastructure.
const HeaderMap *CreateHeaderMap(const FileEntry *FE);
Module *RequestingModule,
ModuleMap::KnownHeader *SuggestedModule);
+ /// Cache the result of a successful lookup at the given include location
+ /// using the search path at index `HitIdx`.
+ void cacheLookupSuccess(LookupFileCacheInfo &CacheLookup, unsigned HitIdx,
+ SourceLocation IncludeLoc);
+ /// Note that a lookup at the given include location was successful using the
+ /// search path at index `HitIdx`.
+ void noteLookupUsage(unsigned HitIdx, SourceLocation IncludeLoc);
+
public:
/// Retrieve the module map.
ModuleMap &getModuleMap() { return ModMap; }
search_dir_iterator system_dir_end() const { return SearchDirs.end(); }
+ /// Get the index of the given search directory.
+ Optional<unsigned> searchDirIdx(const DirectoryLookup &DL) const;
+
/// Retrieve a uniqued framework name.
StringRef getUniqueFrameworkName(StringRef Framework);
struct DirectoryLookupInfo {
IncludeDirGroup Group;
DirectoryLookup Lookup;
+ Optional<unsigned> UserEntryIdx;
- DirectoryLookupInfo(IncludeDirGroup Group, DirectoryLookup Lookup)
- : Group(Group), Lookup(Lookup) {}
+ DirectoryLookupInfo(IncludeDirGroup Group, DirectoryLookup Lookup,
+ Optional<unsigned> UserEntryIdx)
+ : Group(Group), Lookup(Lookup), UserEntryIdx(UserEntryIdx) {}
};
/// InitHeaderSearch - This class makes it easier to set the search paths of
/// AddPath - Add the specified path to the specified group list, prefixing
/// the sysroot if used.
/// Returns true if the path exists, false if it was ignored.
- bool AddPath(const Twine &Path, IncludeDirGroup Group, bool isFramework);
+ bool AddPath(const Twine &Path, IncludeDirGroup Group, bool isFramework,
+ Optional<unsigned> UserEntryIdx = None);
/// AddUnmappedPath - Add the specified path to the specified group list,
/// without performing any sysroot remapping.
/// Returns true if the path exists, false if it was ignored.
bool AddUnmappedPath(const Twine &Path, IncludeDirGroup Group,
- bool isFramework);
+ bool isFramework,
+ Optional<unsigned> UserEntryIdx = None);
/// AddSystemHeaderPrefix - Add the specified prefix to the system header
/// prefix list.
}
bool InitHeaderSearch::AddPath(const Twine &Path, IncludeDirGroup Group,
- bool isFramework) {
+ bool isFramework,
+ Optional<unsigned> UserEntryIdx) {
// Add the path with sysroot prepended, if desired and this is a system header
// group.
if (HasSysroot) {
SmallString<256> MappedPathStorage;
StringRef MappedPathStr = Path.toStringRef(MappedPathStorage);
if (CanPrefixSysroot(MappedPathStr)) {
- return AddUnmappedPath(IncludeSysroot + Path, Group, isFramework);
+ return AddUnmappedPath(IncludeSysroot + Path, Group, isFramework,
+ UserEntryIdx);
}
}
- return AddUnmappedPath(Path, Group, isFramework);
+ return AddUnmappedPath(Path, Group, isFramework, UserEntryIdx);
}
bool InitHeaderSearch::AddUnmappedPath(const Twine &Path, IncludeDirGroup Group,
- bool isFramework) {
+ bool isFramework,
+ Optional<unsigned> UserEntryIdx) {
assert(!Path.isTriviallyEmpty() && "can't handle empty path here");
FileManager &FM = Headers.getFileMgr();
// If the directory exists, add it.
if (auto DE = FM.getOptionalDirectoryRef(MappedPathStr)) {
- IncludePath.emplace_back(Group, DirectoryLookup(*DE, Type, isFramework));
+ IncludePath.emplace_back(Group, DirectoryLookup(*DE, Type, isFramework),
+ UserEntryIdx);
return true;
}
if (const HeaderMap *HM = Headers.CreateHeaderMap(*FE)) {
// It is a headermap, add it to the search path.
IncludePath.emplace_back(
- Group, DirectoryLookup(HM, Type, Group == IndexHeaderMap));
+ Group, DirectoryLookup(HM, Type, Group == IndexHeaderMap),
+ UserEntryIdx);
return true;
}
}
/// RemoveDuplicates - If there are duplicate directory entries in the specified
/// search list, remove the later (dead) ones. Returns the number of non-system
/// headers removed, which is used to update NumAngled.
-static unsigned RemoveDuplicates(std::vector<DirectoryLookup> &SearchList,
+static unsigned RemoveDuplicates(std::vector<DirectoryLookupInfo> &SearchList,
unsigned First, bool Verbose) {
llvm::SmallPtrSet<const DirectoryEntry *, 8> SeenDirs;
llvm::SmallPtrSet<const DirectoryEntry *, 8> SeenFrameworkDirs;
for (unsigned i = First; i != SearchList.size(); ++i) {
unsigned DirToRemove = i;
- const DirectoryLookup &CurEntry = SearchList[i];
+ const DirectoryLookup &CurEntry = SearchList[i].Lookup;
if (CurEntry.isNormalDir()) {
// If this isn't the first time we've seen this dir, remove it.
for (FirstDir = First;; ++FirstDir) {
assert(FirstDir != i && "Didn't find dupe?");
- const DirectoryLookup &SearchEntry = SearchList[FirstDir];
+ const DirectoryLookup &SearchEntry = SearchList[FirstDir].Lookup;
// If these are different lookup types, then they can't be the dupe.
if (SearchEntry.getLookupType() != CurEntry.getLookupType())
// If the first dir in the search path is a non-system dir, zap it
// instead of the system one.
- if (SearchList[FirstDir].getDirCharacteristic() == SrcMgr::C_User)
+ if (SearchList[FirstDir].Lookup.getDirCharacteristic() == SrcMgr::C_User)
DirToRemove = FirstDir;
}
return NonSystemRemoved;
}
+/// Extract DirectoryLookups from DirectoryLookupInfos.
+static std::vector<DirectoryLookup>
+extractLookups(const std::vector<DirectoryLookupInfo> &Infos) {
+ std::vector<DirectoryLookup> Lookups;
+ Lookups.reserve(Infos.size());
+ llvm::transform(Infos, std::back_inserter(Lookups),
+ [](const DirectoryLookupInfo &Info) { return Info.Lookup; });
+ return Lookups;
+}
+
+/// Collect the mapping between indices of DirectoryLookups and UserEntries.
+static llvm::DenseMap<unsigned, unsigned>
+mapToUserEntries(const std::vector<DirectoryLookupInfo> &Infos) {
+ llvm::DenseMap<unsigned, unsigned> LookupsToUserEntries;
+ for (unsigned I = 0, E = Infos.size(); I < E; ++I) {
+ // Check whether this DirectoryLookup maps to a HeaderSearch::UserEntry.
+ if (Infos[I].UserEntryIdx)
+ LookupsToUserEntries.insert({I, *Infos[I].UserEntryIdx});
+ }
+ return LookupsToUserEntries;
+}
void InitHeaderSearch::Realize(const LangOptions &Lang) {
// Concatenate ANGLE+SYSTEM+AFTER chains together into SearchList.
- std::vector<DirectoryLookup> SearchList;
+ std::vector<DirectoryLookupInfo> SearchList;
SearchList.reserve(IncludePath.size());
// Quoted arguments go first.
for (auto &Include : IncludePath)
if (Include.Group == Quoted)
- SearchList.push_back(Include.Lookup);
+ SearchList.push_back(Include);
// Deduplicate and remember index.
RemoveDuplicates(SearchList, 0, Verbose);
for (auto &Include : IncludePath)
if (Include.Group == Angled || Include.Group == IndexHeaderMap)
- SearchList.push_back(Include.Lookup);
+ SearchList.push_back(Include);
RemoveDuplicates(SearchList, NumQuoted, Verbose);
unsigned NumAngled = SearchList.size();
Include.Group == CXXSystem) ||
(Lang.ObjC && !Lang.CPlusPlus && Include.Group == ObjCSystem) ||
(Lang.ObjC && Lang.CPlusPlus && Include.Group == ObjCXXSystem))
- SearchList.push_back(Include.Lookup);
+ SearchList.push_back(Include);
for (auto &Include : IncludePath)
if (Include.Group == After)
- SearchList.push_back(Include.Lookup);
+ SearchList.push_back(Include);
// Remove duplicates across both the Angled and System directories. GCC does
// this and failing to remove duplicates across these two groups breaks
NumAngled -= NonSystemRemoved;
bool DontSearchCurDir = false; // TODO: set to true if -I- is set?
- Headers.SetSearchPaths(SearchList, NumQuoted, NumAngled, DontSearchCurDir);
+ Headers.SetSearchPaths(extractLookups(SearchList), NumQuoted, NumAngled,
+ DontSearchCurDir, mapToUserEntries(SearchList));
Headers.SetSystemHeaderPrefixes(SystemHeaderPrefixes);
for (unsigned i = 0, e = SearchList.size(); i != e; ++i) {
if (i == NumQuoted)
llvm::errs() << "#include <...> search starts here:\n";
- StringRef Name = SearchList[i].getName();
+ StringRef Name = SearchList[i].Lookup.getName();
const char *Suffix;
- if (SearchList[i].isNormalDir())
+ if (SearchList[i].Lookup.isNormalDir())
Suffix = "";
- else if (SearchList[i].isFramework())
+ else if (SearchList[i].Lookup.isFramework())
Suffix = " (framework directory)";
else {
- assert(SearchList[i].isHeaderMap() && "Unknown DirectoryLookup");
+ assert(SearchList[i].Lookup.isHeaderMap() && "Unknown DirectoryLookup");
Suffix = " (headermap)";
}
llvm::errs() << " " << Name << Suffix << "\n";
for (unsigned i = 0, e = HSOpts.UserEntries.size(); i != e; ++i) {
const HeaderSearchOptions::Entry &E = HSOpts.UserEntries[i];
if (E.IgnoreSysRoot) {
- Init.AddUnmappedPath(E.Path, E.Group, E.IsFramework);
+ Init.AddUnmappedPath(E.Path, E.Group, E.IsFramework, i);
} else {
- Init.AddPath(E.Path, E.Group, E.IsFramework);
+ Init.AddPath(E.Path, E.Group, E.IsFramework, i);
}
}
}
}
-/// LookupFile - Check to see if the specified relative filename is located in
-/// this HeaderMap. If so, open it and return its FileEntry.
-Optional<FileEntryRef> HeaderMap::LookupFile(StringRef Filename,
- FileManager &FM) const {
-
- SmallString<1024> Path;
- StringRef Dest = HeaderMapImpl::lookupFilename(Filename, Path);
- if (Dest.empty())
- return None;
-
- return FM.getOptionalFileRef(Dest);
-}
-
StringRef HeaderMapImpl::lookupFilename(StringRef Filename,
SmallVectorImpl<char> &DestPath) const {
const HMapHeader &Hdr = getHeader();
<< NumSubFrameworkLookups << " subframework lookups.\n";
}
+std::vector<bool> HeaderSearch::computeUserEntryUsage() const {
+ std::vector<bool> UserEntryUsage(HSOpts->UserEntries.size());
+ for (unsigned I = 0, E = SearchDirsUsage.size(); I < E; ++I) {
+ // Check whether this DirectoryLookup has been successfully used.
+ if (SearchDirsUsage[I]) {
+ auto UserEntryIdxIt = SearchDirToHSEntry.find(I);
+ // Check whether this DirectoryLookup maps to a HeaderSearch::UserEntry.
+ if (UserEntryIdxIt != SearchDirToHSEntry.end())
+ UserEntryUsage[UserEntryIdxIt->second] = true;
+ }
+ }
+ return UserEntryUsage;
+}
+
/// CreateHeaderMap - This method returns a HeaderMap for the specified
/// FileEntry, uniquing them through the 'HeaderMaps' datastructure.
const HeaderMap *HeaderSearch::CreateHeaderMap(const FileEntry *FE) {
SourceLocation ImportLoc,
bool AllowExtraModuleMapSearch) {
Module *Module = nullptr;
+ unsigned Idx;
// Look through the various header search paths to load any available module
// maps, searching for a module map that describes this module.
- for (unsigned Idx = 0, N = SearchDirs.size(); Idx != N; ++Idx) {
+ for (Idx = 0; Idx != SearchDirs.size(); ++Idx) {
if (SearchDirs[Idx].isFramework()) {
// Search for or infer a module map for a framework. Here we use
// SearchName rather than ModuleName, to permit finding private modules
break;
}
+ if (Module)
+ noteLookupUsage(Idx, ImportLoc);
+
return Module;
}
if (llvm::sys::path::is_relative(Dest)) {
MappedName.append(Dest.begin(), Dest.end());
Filename = StringRef(MappedName.begin(), MappedName.size());
- Optional<FileEntryRef> Result = HM->LookupFile(Filename, HS.getFileMgr());
- if (Result) {
- FixupSearchPath();
- return *Result;
- }
- } else if (auto Res = HS.getFileMgr().getOptionalFileRef(Dest)) {
+ Dest = HM->lookupFilename(Filename, Path);
+ }
+
+ if (auto Res = HS.getFileMgr().getOptionalFileRef(Dest)) {
FixupSearchPath();
return *Res;
}
+ // Header maps need to be marked as used whenever the filename matches.
+ // The case where the target file **exists** is handled by callee of this
+ // function as part of the regular logic that applies to include search paths.
+ // The case where the target file **does not exist** is handled here:
+ HS.noteLookupUsage(*HS.searchDirIdx(*this), IncludeLoc);
return None;
}
return None;
}
+void HeaderSearch::cacheLookupSuccess(LookupFileCacheInfo &CacheLookup,
+ unsigned HitIdx, SourceLocation Loc) {
+ CacheLookup.HitIdx = HitIdx;
+ noteLookupUsage(HitIdx, Loc);
+}
+
+void HeaderSearch::noteLookupUsage(unsigned HitIdx, SourceLocation Loc) {
+ SearchDirsUsage[HitIdx] = true;
+
+ auto UserEntryIdxIt = SearchDirToHSEntry.find(HitIdx);
+ if (UserEntryIdxIt != SearchDirToHSEntry.end())
+ Diags.Report(Loc, diag::remark_pp_search_path_usage)
+ << HSOpts->UserEntries[UserEntryIdxIt->second].Path;
+}
+
void HeaderSearch::setTarget(const TargetInfo &Target) {
ModMap.setTarget(Target);
}
&File->getFileEntry(), isAngled, FoundByHeaderMap);
// Remember this location for the next lookup we do.
- CacheLookup.HitIdx = i;
+ cacheLookupSuccess(CacheLookup, i, IncludeLoc);
return File;
}
return MSFE;
}
- LookupFileCacheInfo &CacheLookup = LookupFileCache[Filename];
- CacheLookup.HitIdx = LookupFileCache[ScratchFilename].HitIdx;
+ cacheLookupSuccess(LookupFileCache[Filename],
+ LookupFileCache[ScratchFilename].HitIdx, IncludeLoc);
// FIXME: SuggestedModule.
return File;
}
+ FrameworkMap.getAllocator().getTotalMemory();
}
+Optional<unsigned> HeaderSearch::searchDirIdx(const DirectoryLookup &DL) const {
+ for (unsigned I = 0; I < SearchDirs.size(); ++I)
+ if (&SearchDirs[I] == &DL)
+ return I;
+ return None;
+}
+
StringRef HeaderSearch::getUniqueFrameworkName(StringRef Framework) {
return FrameworkNames.insert(Framework).first->first();
}
--- /dev/null
+framework module FrameworkA {
+ header "FrameworkA.h"
+}
--- /dev/null
+framework module FrameworkB {
+ header "FrameworkB.h"
+}
--- /dev/null
+#include_next "a.h" // #a-include-next
--- /dev/null
+{
+ "mappings": {
+ "b.h": "DIR/b/b.h"
+ }
+}
--- /dev/null
+module b {
+ header "DIR/b/b.h"
+}
--- /dev/null
+// RUN: rm -rf %t && mkdir %t
+
+// Check that search paths used by `#include` and `#include_next` are reported.
+//
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -I%S/Inputs/search-path-usage/a \
+// RUN: -I%S/Inputs/search-path-usage/a_next \
+// RUN: -I%S/Inputs/search-path-usage/b \
+// RUN: -I%S/Inputs/search-path-usage/c \
+// RUN: -I%S/Inputs/search-path-usage/d \
+// RUN: -DINCLUDE -verify
+#ifdef INCLUDE
+#include "a.h" // \
+// expected-remark-re {{search path used: '{{.*}}/search-path-usage/a'}} \
+// expected-remark-re@#a-include-next {{search path used: '{{.*}}/search-path-usage/a_next'}}
+#include "d.h" // \
+// expected-remark-re {{search path used: '{{.*}}/search-path-usage/d'}}
+#endif
+
+// Check that framework search paths are reported.
+//
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -F%S/Inputs/search-path-usage/FwA \
+// RUN: -F%S/Inputs/search-path-usage/FwB \
+// RUN: -DFRAMEWORK -verify
+#ifdef FRAMEWORK
+#include "FrameworkA/FrameworkA.h" // \
+// expected-remark-re {{search path used: '{{.*}}/search-path-usage/FwA'}}
+#endif
+
+// Check that system search paths are reported.
+//
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -isystem %S/Inputs/search-path-usage/b \
+// RUN: -isystem %S/Inputs/search-path-usage/d \
+// RUN: -DSYSTEM -verify
+#ifdef SYSTEM
+#include "b.h" // \
+// expected-remark-re {{search path used: '{{.*}}/search-path-usage/b'}}
+#endif
+
+// Check that sysroot-based search paths are reported.
+//
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -isysroot %S/Inputs/search-path-usage \
+// RUN: -iwithsysroot /b \
+// RUN: -iwithsysroot /d \
+// RUN: -DSYSROOT -verify
+#ifdef SYSROOT
+#include "d.h" // \
+// expected-remark {{search path used: '/d'}}
+#endif
+
+// Check that search paths used by `__has_include()` are reported.
+//
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -I%S/Inputs/search-path-usage/b \
+// RUN: -I%S/Inputs/search-path-usage/d \
+// RUN: -DHAS_INCLUDE -verify
+#ifdef HAS_INCLUDE
+#if __has_include("b.h") // \
+// expected-remark-re {{search path used: '{{.*}}/search-path-usage/b'}}
+#endif
+#if __has_include("x.h")
+#endif
+#endif
+
+// Check that search paths used by `#import` are reported.
+//
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -I%S/Inputs/search-path-usage/b \
+// RUN: -I%S/Inputs/search-path-usage/d \
+// RUN: -DIMPORT -verify
+#ifdef IMPORT
+#import "d.h" // \
+// expected-remark-re {{search path used: '{{.*}}/search-path-usage/d'}}
+#endif
+
+// Check that used header maps are reported when the target file exists.
+//
+// RUN: sed "s|DIR|%/S/Inputs/search-path-usage|g" \
+// RUN: %S/Inputs/search-path-usage/b.hmap.json.template > %t/b.hmap.json
+// RUN: %hmaptool write %t/b.hmap.json %t/b.hmap
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -I %t/b.hmap \
+// RUN: -I b \
+// RUN: -DHMAP -verify
+#ifdef HMAP
+#include "b.h" // \
+// expected-remark-re {{search path used: '{{.*}}/b.hmap'}}
+#endif
+
+// Check that unused header map are not reported.
+//
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -I%t/b.hmap \
+// RUN: -I%S/Inputs/search-path-usage/d \
+// RUN: -DHMAP_NO_MATCH -verify
+#ifdef HMAP_NO_MATCH
+#include "d.h" // \
+// expected-remark-re {{search path used: '{{.*}}/search-path-usage/d'}}
+#endif
+
+// Check that used header map is reported even when the target file is missing.
+//
+// RUN: sed "s|DIR|%/S/Inputs/search-path-usage/missing-subdir|g" \
+// RUN: %S/Inputs/search-path-usage/b.hmap.json.template > %t/b-missing.hmap.json
+// RUN: %hmaptool write %t/b-missing.hmap.json %t/b-missing.hmap
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -I %t/b-missing.hmap \
+// RUN: -I b \
+// RUN: -DHMAP_MATCHED_BUT_MISSING -verify
+#ifdef HMAP_MATCHED_BUT_MISSING
+#include "b.h" // \
+// expected-remark-re {{search path used: '{{.*}}/b-missing.hmap'}} \
+// expected-error {{'b.h' file not found}}
+#endif
+
+// Check that used header map is reported even when the target file is missing
+// and the lookup is initiated by __has_include.
+//
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -I %t/b-missing.hmap \
+// RUN: -I b \
+// RUN: -DHMAP_MATCHED_BUT_MISSING_IN_HAS_INCLUDE -verify
+#ifdef HMAP_MATCHED_BUT_MISSING_IN_HAS_INCLUDE
+#if __has_include("b.h") // \
+// expected-remark-re {{search path used: '{{.*}}/b-missing.hmap'}}
+#endif
+#endif
+
+// Check that search paths with module maps are reported.
+//
+// RUN: mkdir %t/modulemap_abs
+// RUN: sed "s|DIR|%/S/Inputs/search-path-usage|g" \
+// RUN: %S/Inputs/search-path-usage/modulemap_abs/module.modulemap.template \
+// RUN: > %t/modulemap_abs/module.modulemap
+// RUN: %clang_cc1 -Eonly %s -Rsearch-path-usage \
+// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules \
+// RUN: -I %t/modulemap_abs \
+// RUN: -I %S/Inputs/search-path-usage/a \
+// RUN: -DMODMAP_ABS -verify
+#ifdef MODMAP_ABS
+@import b; // \
+// expected-remark-re {{search path used: '{{.*}}/modulemap_abs'}}
+#endif