<div dir="ltr">Apologies, missed this. r329685 should fix.</div><br><div class="gmail_quote"><div dir="ltr">On Tue, Apr 10, 2018 at 12:53 AM Galina Kistanova <<a href="mailto:gkistanova@gmail.com">gkistanova@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">Hello Sam,<br><br>It looks like this commit added broken tests to one of our builders:<br><a href="http://lab.llvm.org:8011/builders/llvm-clang-x86_64-expensive-checks-win/builds/8957/steps/test-check-all/logs/stdio" target="_blank">http://lab.llvm.org:8011/builders/llvm-clang-x86_64-expensive-checks-win/builds/8957/steps/test-check-all/logs/stdio</a><br><br>Failing Tests (5):<br> Clang-Unit :: Tooling/./ToolingTests.exe/InterpolateTest.Case<br> Clang-Unit :: Tooling/./ToolingTests.exe/InterpolateTest.Language<br> Clang-Unit :: Tooling/./ToolingTests.exe/InterpolateTest.Nearby<br> Clang-Unit :: Tooling/./ToolingTests.exe/InterpolateTest.Strip<br>. . .<br>Please have a look?<br><br>The builder was red and did not send notifications.<br><br>Thanks<br><br>Galina<br></div><div class="gmail_extra"><br><div class="gmail_quote">On Mon, Apr 9, 2018 at 8:17 AM, Sam McCall via cfe-commits <span dir="ltr"><<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Author: sammccall<br>
Date: Mon Apr 9 08:17:39 2018<br>
New Revision: 329580<br>
<br>
URL: <a href="http://llvm.org/viewvc/llvm-project?rev=329580&view=rev" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project?rev=329580&view=rev</a><br>
Log:<br>
[Tooling] A CompilationDatabase wrapper that infers header commands.<br>
<br>
Summary:<br>
The wrapper finds the closest matching compile command using filename heuristics<br>
and makes minimal tweaks so it can be used with the header.<br>
<br>
Subscribers: klimek, mgorny, cfe-commits<br>
<br>
Differential Revision: <a href="https://reviews.llvm.org/D45006" rel="noreferrer" target="_blank">https://reviews.llvm.org/D45006</a><br>
<br>
Added:<br>
cfe/trunk/lib/Tooling/InterpolatingCompilationDatabase.cpp<br>
Modified:<br>
cfe/trunk/include/clang/Tooling/CompilationDatabase.h<br>
cfe/trunk/lib/Tooling/CMakeLists.txt<br>
cfe/trunk/unittests/Tooling/CompilationDatabaseTest.cpp<br>
<br>
Modified: cfe/trunk/include/clang/Tooling/CompilationDatabase.h<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/CompilationDatabase.h?rev=329580&r1=329579&r2=329580&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/CompilationDatabase.h?rev=329580&r1=329579&r2=329580&view=diff</a><br>
==============================================================================<br>
--- cfe/trunk/include/clang/Tooling/CompilationDatabase.h (original)<br>
+++ cfe/trunk/include/clang/Tooling/CompilationDatabase.h Mon Apr 9 08:17:39 2018<br>
@@ -213,6 +213,13 @@ private:<br>
std::vector<CompileCommand> CompileCommands;<br>
};<br>
<br>
+/// Returns a wrapped CompilationDatabase that defers to the provided one,<br>
+/// but getCompileCommands() will infer commands for unknown files.<br>
+/// The return value of getAllFiles() or getAllCompileCommands() is unchanged.<br>
+/// See InterpolatingCompilationDatabase.cpp for details on heuristics.<br>
+std::unique_ptr<CompilationDatabase><br>
+ inferMissingCompileCommands(std::unique_ptr<CompilationDatabase>);<br>
+<br>
} // namespace tooling<br>
} // namespace clang<br>
<br>
<br>
Modified: cfe/trunk/lib/Tooling/CMakeLists.txt<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/CMakeLists.txt?rev=329580&r1=329579&r2=329580&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/CMakeLists.txt?rev=329580&r1=329579&r2=329580&view=diff</a><br>
==============================================================================<br>
--- cfe/trunk/lib/Tooling/CMakeLists.txt (original)<br>
+++ cfe/trunk/lib/Tooling/CMakeLists.txt Mon Apr 9 08:17:39 2018<br>
@@ -15,6 +15,7 @@ add_clang_library(clangTooling<br>
Execution.cpp<br>
FileMatchTrie.cpp<br>
FixIt.cpp<br>
+ InterpolatingCompilationDatabase.cpp<br>
JSONCompilationDatabase.cpp<br>
Refactoring.cpp<br>
RefactoringCallbacks.cpp<br>
<br>
Added: cfe/trunk/lib/Tooling/InterpolatingCompilationDatabase.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/InterpolatingCompilationDatabase.cpp?rev=329580&view=auto" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/InterpolatingCompilationDatabase.cpp?rev=329580&view=auto</a><br>
==============================================================================<br>
--- cfe/trunk/lib/Tooling/InterpolatingCompilationDatabase.cpp (added)<br>
+++ cfe/trunk/lib/Tooling/InterpolatingCompilationDatabase.cpp Mon Apr 9 08:17:39 2018<br>
@@ -0,0 +1,458 @@<br>
+//===- InterpolatingCompilationDatabase.cpp ---------------------*- C++ -*-===//<br>
+//<br>
+// The LLVM Compiler Infrastructure<br>
+//<br>
+// This file is distributed under the University of Illinois Open Source<br>
+// License. See LICENSE.TXT for details.<br>
+//<br>
+//===----------------------------------------------------------------------===//<br>
+//<br>
+// InterpolatingCompilationDatabase wraps another CompilationDatabase and<br>
+// attempts to heuristically determine appropriate compile commands for files<br>
+// that are not included, such as headers or newly created files.<br>
+//<br>
+// Motivating cases include:<br>
+// Header files that live next to their implementation files. These typically<br>
+// share a base filename. (libclang/CXString.h, libclang/CXString.cpp).<br>
+// Some projects separate headers from includes. Filenames still typically<br>
+// match, maybe other path segments too. (include/llvm/IR/Use.h, lib/IR/Use.cc).<br>
+// Matches are sometimes only approximate (Sema.h, SemaDecl.cpp). This goes<br>
+// for directories too (Support/Unix/Process.inc, lib/Support/Process.cpp).<br>
+// Even if we can't find a "right" compile command, even a random one from<br>
+// the project will tend to get important flags like -I and -x right.<br>
+//<br>
+// We "borrow" the compile command for the closest available file:<br>
+// - points are awarded if the filename matches (ignoring extension)<br>
+// - points are awarded if the directory structure matches<br>
+// - ties are broken by length of path prefix match<br>
+//<br>
+// The compile command is adjusted, replacing the filename and removing output<br>
+// file arguments. The -x and -std flags may be affected too.<br>
+//<br>
+// Source language is a tricky issue: is it OK to use a .c file's command<br>
+// for building a .cc file? What language is a .h file in?<br>
+// - We only consider compile commands for c-family languages as candidates.<br>
+// - For files whose language is implied by the filename (e.g. .m, .hpp)<br>
+// we prefer candidates from the same language.<br>
+// If we must cross languages, we drop any -x and -std flags.<br>
+// - For .h files, candidates from any c-family language are acceptable.<br>
+// We use the candidate's language, inserting e.g. -x c++-header.<br>
+//<br>
+// This class is only useful when wrapping databases that can enumerate all<br>
+// their compile commands. If getAllFilenames() is empty, no inference occurs.<br>
+//<br>
+//===----------------------------------------------------------------------===//<br>
+<br>
+#include "clang/Driver/Options.h"<br>
+#include "clang/Driver/Types.h"<br>
+#include "clang/Frontend/LangStandard.h"<br>
+#include "clang/Tooling/CompilationDatabase.h"<br>
+#include "llvm/ADT/DenseMap.h"<br>
+#include "llvm/ADT/StringExtras.h"<br>
+#include "llvm/ADT/StringSwitch.h"<br>
+#include "llvm/Option/ArgList.h"<br>
+#include "llvm/Option/OptTable.h"<br>
+#include "llvm/Support/Debug.h"<br>
+#include "llvm/Support/Path.h"<br>
+#include "llvm/Support/StringSaver.h"<br>
+#include "llvm/Support/raw_ostream.h"<br>
+#include <memory><br>
+<br>
+namespace clang {<br>
+namespace tooling {<br>
+namespace {<br>
+using namespace llvm;<br>
+namespace types = clang::driver::types;<br>
+namespace path = llvm::sys::path;<br>
+<br>
+// The length of the prefix these two strings have in common.<br>
+size_t matchingPrefix(StringRef L, StringRef R) {<br>
+ size_t Limit = std::min(L.size(), R.size());<br>
+ for (size_t I = 0; I < Limit; ++I)<br>
+ if (L[I] != R[I])<br>
+ return I;<br>
+ return Limit;<br>
+}<br>
+<br>
+// A comparator for searching SubstringWithIndexes with std::equal_range etc.<br>
+// Optionaly prefix semantics: compares equal if the key is a prefix.<br>
+template <bool Prefix> struct Less {<br>
+ bool operator()(StringRef Key, std::pair<StringRef, size_t> Value) const {<br>
+ StringRef V = Prefix ? Value.first.substr(0, Key.size()) : Value.first;<br>
+ return Key < V;<br>
+ }<br>
+ bool operator()(std::pair<StringRef, size_t> Value, StringRef Key) const {<br>
+ StringRef V = Prefix ? Value.first.substr(0, Key.size()) : Value.first;<br>
+ return V < Key;<br>
+ }<br>
+};<br>
+<br>
+// Infer type from filename. If we might have gotten it wrong, set *Certain.<br>
+// *.h will be inferred as a C header, but not certain.<br>
+types::ID guessType(StringRef Filename, bool *Certain = nullptr) {<br>
+ // path::extension is ".cpp", lookupTypeForExtension wants "cpp".<br>
+ auto Lang =<br>
+ types::lookupTypeForExtension(path::extension(Filename).substr(1));<br>
+ if (Certain)<br>
+ *Certain = Lang != types::TY_CHeader && Lang != types::TY_INVALID;<br>
+ return Lang;<br>
+}<br>
+<br>
+// Return Lang as one of the canonical supported types.<br>
+// e.g. c-header --> c; fortran --> TY_INVALID<br>
+static types::ID foldType(types::ID Lang) {<br>
+ switch (Lang) {<br>
+ case types::TY_C:<br>
+ case types::TY_CHeader:<br>
+ return types::TY_C;<br>
+ case types::TY_ObjC:<br>
+ case types::TY_ObjCHeader:<br>
+ return types::TY_ObjC;<br>
+ case types::TY_CXX:<br>
+ case types::TY_CXXHeader:<br>
+ return types::TY_CXX;<br>
+ case types::TY_ObjCXX:<br>
+ case types::TY_ObjCXXHeader:<br>
+ return types::TY_ObjCXX;<br>
+ default:<br>
+ return types::TY_INVALID;<br>
+ }<br>
+}<br>
+<br>
+// A CompileCommand that can be applied to another file.<br>
+struct TransferableCommand {<br>
+ // Flags that should not apply to all files are stripped from CommandLine.<br>
+ CompileCommand Cmd;<br>
+ // Language detected from -x or the filename.<br>
+ types::ID Type = types::TY_INVALID;<br>
+ // Standard specified by -std.<br>
+ LangStandard::Kind Std = LangStandard::lang_unspecified;<br>
+<br>
+ TransferableCommand(CompileCommand C)<br>
+ : Cmd(std::move(C)), Type(guessType(Cmd.Filename)) {<br>
+ std::vector<std::string> NewArgs = {Cmd.CommandLine.front()};<br>
+ // Parse the old args in order to strip out and record unwanted flags.<br>
+ auto OptTable = clang::driver::createDriverOptTable();<br>
+ std::vector<const char *> Argv;<br>
+ for (unsigned I = 1; I < Cmd.CommandLine.size(); ++I)<br>
+ Argv.push_back(Cmd.CommandLine[I].c_str());<br>
+ unsigned MissingI, MissingC;<br>
+ auto ArgList = OptTable->ParseArgs(Argv, MissingI, MissingC);<br>
+ for (const auto *Arg : ArgList) {<br>
+ const auto &option = Arg->getOption();<br>
+ // Strip input and output files.<br>
+ if (option.matches(clang::driver::options::OPT_INPUT) ||<br>
+ option.matches(clang::driver::options::OPT_o)) {<br>
+ continue;<br>
+ }<br>
+ // Strip -x, but record the overridden language.<br>
+ if (option.matches(clang::driver::options::OPT_x)) {<br>
+ for (const char *Value : Arg->getValues())<br>
+ Type = types::lookupTypeForTypeSpecifier(Value);<br>
+ continue;<br>
+ }<br>
+ // Strip --std, but record the value.<br>
+ if (option.matches(clang::driver::options::OPT_std_EQ)) {<br>
+ for (const char *Value : Arg->getValues()) {<br>
+ Std = llvm::StringSwitch<LangStandard::Kind>(Value)<br>
+#define LANGSTANDARD(id, name, lang, desc, features) \<br>
+ .Case(name, LangStandard::lang_##id)<br>
+#define LANGSTANDARD_ALIAS(id, alias) .Case(alias, LangStandard::lang_##id)<br>
+#include "clang/Frontend/LangStandards.def"<br>
+ .Default(Std);<br>
+ }<br>
+ continue;<br>
+ }<br>
+ llvm::opt::ArgStringList ArgStrs;<br>
+ Arg->render(ArgList, ArgStrs);<br>
+ NewArgs.insert(NewArgs.end(), ArgStrs.begin(), ArgStrs.end());<br>
+ }<br>
+ Cmd.CommandLine = std::move(NewArgs);<br>
+<br>
+ if (Std != LangStandard::lang_unspecified) // -std take precedence over -x<br>
+ Type = toType(LangStandard::getLangStandardForKind(Std).getLanguage());<br>
+ Type = foldType(Type);<br>
+ }<br>
+<br>
+ // Produce a CompileCommand for \p filename, based on this one.<br>
+ CompileCommand transferTo(StringRef Filename) const {<br>
+ CompileCommand Result = Cmd;<br>
+ Result.Filename = Filename;<br>
+ bool TypeCertain;<br>
+ auto TargetType = guessType(Filename, &TypeCertain);<br>
+ // If the filename doesn't determine the language (.h), transfer with -x.<br>
+ if (!TypeCertain) {<br>
+ TargetType = types::onlyPrecompileType(TargetType) // header?<br>
+ ? types::lookupHeaderTypeForSourceType(Type)<br>
+ : Type;<br>
+ Result.CommandLine.push_back("-x");<br>
+ Result.CommandLine.push_back(types::getTypeName(TargetType));<br>
+ }<br>
+ // --std flag may only be transferred if the language is the same.<br>
+ // We may consider "translating" these, e.g. c++11 -> c11.<br>
+ if (Std != LangStandard::lang_unspecified && foldType(TargetType) == Type) {<br>
+ Result.CommandLine.push_back("-std");<br>
+ Result.CommandLine.push_back(<br>
+ LangStandard::getLangStandardForKind(Std).getName());<br>
+ }<br>
+ Result.CommandLine.push_back(Filename);<br>
+ return Result;<br>
+ }<br>
+<br>
+private:<br>
+ // Map the language from the --std flag to that of the -x flag.<br>
+ static types::ID toType(InputKind::Language Lang) {<br>
+ switch (Lang) {<br>
+ case InputKind::C:<br>
+ return types::TY_C;<br>
+ case InputKind::CXX:<br>
+ return types::TY_CXX;<br>
+ case InputKind::ObjC:<br>
+ return types::TY_ObjC;<br>
+ case InputKind::ObjCXX:<br>
+ return types::TY_ObjCXX;<br>
+ default:<br>
+ return types::TY_INVALID;<br>
+ }<br>
+ }<br>
+};<br>
+<br>
+// CommandIndex does the real work: given a filename, it produces the best<br>
+// matching TransferableCommand by matching filenames. Basic strategy:<br>
+// - Build indexes of each of the substrings we want to look up by.<br>
+// These indexes are just sorted lists of the substrings.<br>
+// - Forward requests to the inner CDB. If it fails, we must pick a proxy.<br>
+// - Each criterion corresponds to a range lookup into the index, so we only<br>
+// need O(log N) string comparisons to determine scores.<br>
+// - We then break ties among the candidates with the highest score.<br>
+class CommandIndex {<br>
+public:<br>
+ CommandIndex(std::vector<TransferableCommand> AllCommands)<br>
+ : Commands(std::move(AllCommands)), Strings(Arena) {<br>
+ // Sort commands by filename for determinism (index is a tiebreaker later).<br>
+ llvm::sort(<br>
+ Commands.begin(), Commands.end(),<br>
+ [](const TransferableCommand &Left, const TransferableCommand &Right) {<br>
+ return Left.Cmd.Filename < Right.Cmd.Filename;<br>
+ });<br>
+ for (size_t I = 0; I < Commands.size(); ++I) {<br>
+ StringRef Path =<br>
+ Strings.save(StringRef(Commands[I].Cmd.Filename).lower());<br>
+ Paths.push_back({Path, I});<br>
+ Stems.emplace_back(sys::path::stem(Path), I);<br>
+ auto Dir = ++sys::path::rbegin(Path), DirEnd = sys::path::rend(Path);<br>
+ for (int J = 0; J < DirectorySegmentsIndexed && Dir != DirEnd; ++J, ++Dir)<br>
+ if (Dir->size() > ShortDirectorySegment) // not trivial ones<br>
+ Components.emplace_back(*Dir, I);<br>
+ }<br>
+ llvm::sort(Paths.begin(), Paths.end());<br>
+ llvm::sort(Stems.begin(), Stems.end());<br>
+ llvm::sort(Components.begin(), Components.end());<br>
+ }<br>
+<br>
+ bool empty() const { return Commands.empty(); }<br>
+<br>
+ // Returns the command that best fits OriginalFilename.<br>
+ // Candidates with PreferLanguage will be chosen over others (unless it's<br>
+ // TY_INVALID, or all candidates are bad).<br>
+ const TransferableCommand &chooseProxy(StringRef OriginalFilename,<br>
+ types::ID PreferLanguage) const {<br>
+ assert(!empty() && "need at least one candidate!");<br>
+ std::string Filename = OriginalFilename.lower();<br>
+ auto Candidates = scoreCandidates(Filename);<br>
+ std::pair<size_t, int> Best =<br>
+ pickWinner(Candidates, Filename, PreferLanguage);<br>
+<br>
+ DEBUG_WITH_TYPE("interpolate",<br>
+ llvm::dbgs()<br>
+ << "interpolate: chose "<br>
+ << Commands[Best.first].Cmd.Filename << " as proxy for "<br>
+ << OriginalFilename << " preferring "<br>
+ << (PreferLanguage == types::TY_INVALID<br>
+ ? "none"<br>
+ : types::getTypeName(PreferLanguage))<br>
+ << " score=" << Best.second << "\n");<br>
+ return Commands[Best.first];<br>
+ }<br>
+<br>
+private:<br>
+ using SubstringAndIndex = std::pair<StringRef, size_t>;<br>
+ // Directory matching parameters: we look at the last two segments of the<br>
+ // parent directory (usually the semantically significant ones in practice).<br>
+ // We search only the last four of each candidate (for efficiency).<br>
+ constexpr static int DirectorySegmentsIndexed = 4;<br>
+ constexpr static int DirectorySegmentsQueried = 2;<br>
+ constexpr static int ShortDirectorySegment = 1; // Only look at longer names.<br>
+<br>
+ // Award points to candidate entries that should be considered for the file.<br>
+ // Returned keys are indexes into paths, and the values are (nonzero) scores.<br>
+ DenseMap<size_t, int> scoreCandidates(StringRef Filename) const {<br>
+ // Decompose Filename into the parts we care about.<br>
+ // /some/path/complicated/project/Interesting.h<br>
+ // [-prefix--][---dir---] [-dir-] [--stem---]<br>
+ StringRef Stem = sys::path::stem(Filename);<br>
+ llvm::SmallVector<StringRef, DirectorySegmentsQueried> Dirs;<br>
+ llvm::StringRef Prefix;<br>
+ auto Dir = ++sys::path::rbegin(Filename),<br>
+ DirEnd = sys::path::rend(Filename);<br>
+ for (int I = 0; I < DirectorySegmentsQueried && Dir != DirEnd; ++I, ++Dir) {<br>
+ if (Dir->size() > ShortDirectorySegment)<br>
+ Dirs.push_back(*Dir);<br>
+ Prefix = Filename.substr(0, Dir - DirEnd);<br>
+ }<br>
+<br>
+ // Now award points based on lookups into our various indexes.<br>
+ DenseMap<size_t, int> Candidates; // Index -> score.<br>
+ auto Award = [&](int Points, ArrayRef<SubstringAndIndex> Range) {<br>
+ for (const auto &Entry : Range)<br>
+ Candidates[Entry.second] += Points;<br>
+ };<br>
+ // Award one point if the file's basename is a prefix of the candidate,<br>
+ // and another if it's an exact match (so exact matches get two points).<br>
+ Award(1, indexLookup</*Prefix=*/true>(Stem, Stems));<br>
+ Award(1, indexLookup</*Prefix=*/false>(Stem, Stems));<br>
+ // For each of the last few directories in the Filename, award a point<br>
+ // if it's present in the candidate.<br>
+ for (StringRef Dir : Dirs)<br>
+ Award(1, indexLookup</*Prefix=*/false>(Dir, Components));<br>
+ // Award one more point if the whole rest of the path matches.<br>
+ if (sys::path::root_directory(Prefix) != Prefix)<br>
+ Award(1, indexLookup</*Prefix=*/true>(Prefix, Paths));<br>
+ return Candidates;<br>
+ }<br>
+<br>
+ // Pick a single winner from the set of scored candidates.<br>
+ // Returns (index, score).<br>
+ std::pair<size_t, int> pickWinner(const DenseMap<size_t, int> &Candidates,<br>
+ StringRef Filename,<br>
+ types::ID PreferredLanguage) const {<br>
+ struct ScoredCandidate {<br>
+ size_t Index;<br>
+ bool Preferred;<br>
+ int Points;<br>
+ size_t PrefixLength;<br>
+ };<br>
+ // Choose the best candidate by (preferred, points, prefix length, alpha).<br>
+ ScoredCandidate Best = {size_t(-1), false, 0, 0};<br>
+ for (const auto &Candidate : Candidates) {<br>
+ ScoredCandidate S;<br>
+ S.Index = Candidate.first;<br>
+ S.Preferred = PreferredLanguage == types::TY_INVALID ||<br>
+ PreferredLanguage == Commands[S.Index].Type;<br>
+ S.Points = Candidate.second;<br>
+ if (!S.Preferred && Best.Preferred)<br>
+ continue;<br>
+ if (S.Preferred == Best.Preferred) {<br>
+ if (S.Points < Best.Points)<br>
+ continue;<br>
+ if (S.Points == Best.Points) {<br>
+ S.PrefixLength = matchingPrefix(Filename, Paths[S.Index].first);<br>
+ if (S.PrefixLength < Best.PrefixLength)<br>
+ continue;<br>
+ // hidden heuristics should at least be deterministic!<br>
+ if (S.PrefixLength == Best.PrefixLength)<br>
+ if (S.Index > Best.Index)<br>
+ continue;<br>
+ }<br>
+ }<br>
+ // PrefixLength was only set above if actually needed for a tiebreak.<br>
+ // But it definitely needs to be set to break ties in the future.<br>
+ S.PrefixLength = matchingPrefix(Filename, Paths[S.Index].first);<br>
+ Best = S;<br>
+ }<br>
+ // Edge case: no candidate got any points.<br>
+ // We ignore PreferredLanguage at this point (not ideal).<br>
+ if (Best.Index == size_t(-1))<br>
+ return {longestMatch(Filename, Paths).second, 0};<br>
+ return {Best.Index, Best.Points};<br>
+ }<br>
+<br>
+ // Returns the range within a sorted index that compares equal to Key.<br>
+ // If Prefix is true, it's instead the range starting with Key.<br>
+ template <bool Prefix><br>
+ ArrayRef<SubstringAndIndex><br>
+ indexLookup(StringRef Key, const std::vector<SubstringAndIndex> &Idx) const {<br>
+ // Use pointers as iteratiors to ease conversion of result to ArrayRef.<br>
+ auto Range =<br>
+ std::equal_range(&Idx[0], &Idx[Idx.size()], Key, Less<Prefix>());<br>
+ return {Range.first, Range.second};<br>
+ }<br>
+<br>
+ // Performs a point lookup into a nonempty index, returning a longest match.<br>
+ SubstringAndIndex<br>
+ longestMatch(StringRef Key, const std::vector<SubstringAndIndex> &Idx) const {<br>
+ assert(!Idx.empty());<br>
+ // Longest substring match will be adjacent to a direct lookup.<br>
+ auto It =<br>
+ std::lower_bound(Idx.begin(), Idx.end(), SubstringAndIndex{Key, 0});<br>
+ if (It == Idx.begin())<br>
+ return *It;<br>
+ if (It == Idx.end())<br>
+ return *--It;<br>
+ // Have to choose between It and It-1<br>
+ size_t Prefix = matchingPrefix(Key, It->first);<br>
+ size_t PrevPrefix = matchingPrefix(Key, (It - 1)->first);<br>
+ return Prefix > PrevPrefix ? *It : *--It;<br>
+ }<br>
+<br>
+ std::vector<TransferableCommand> Commands; // Indexes point into this.<br>
+ BumpPtrAllocator Arena;<br>
+ StringSaver Strings;<br>
+ // Indexes of candidates by certain substrings.<br>
+ // String is lowercase and sorted, index points into OriginalPaths.<br>
+ std::vector<SubstringAndIndex> Paths; // Full path.<br>
+ std::vector<SubstringAndIndex> Stems; // Basename, without extension.<br>
+ std::vector<SubstringAndIndex> Components; // Last path components.<br>
+};<br>
+<br>
+// The actual CompilationDatabase wrapper delegates to its inner database.<br>
+// If no match, looks up a command in CommandIndex and transfers it to the file.<br>
+class InterpolatingCompilationDatabase : public CompilationDatabase {<br>
+public:<br>
+ InterpolatingCompilationDatabase(std::unique_ptr<CompilationDatabase> Inner)<br>
+ : Inner(std::move(Inner)), Index(allCommands()) {}<br>
+<br>
+ std::vector<CompileCommand><br>
+ getCompileCommands(StringRef Filename) const override {<br>
+ auto Known = Inner->getCompileCommands(Filename);<br>
+ if (Index.empty() || !Known.empty())<br>
+ return Known;<br>
+ bool TypeCertain;<br>
+ auto Lang = guessType(Filename, &TypeCertain);<br>
+ if (!TypeCertain)<br>
+ Lang = types::TY_INVALID;<br>
+ return {Index.chooseProxy(Filename, foldType(Lang)).transferTo(Filename)};<br>
+ }<br>
+<br>
+ std::vector<std::string> getAllFiles() const override {<br>
+ return Inner->getAllFiles();<br>
+ }<br>
+<br>
+ std::vector<CompileCommand> getAllCompileCommands() const override {<br>
+ return Inner->getAllCompileCommands();<br>
+ }<br>
+<br>
+private:<br>
+ std::vector<TransferableCommand> allCommands() {<br>
+ std::vector<TransferableCommand> Result;<br>
+ for (auto Command : Inner->getAllCompileCommands()) {<br>
+ Result.emplace_back(std::move(Command));<br>
+ if (Result.back().Type == types::TY_INVALID)<br>
+ Result.pop_back();<br>
+ }<br>
+ return Result;<br>
+ }<br>
+<br>
+ std::unique_ptr<CompilationDatabase> Inner;<br>
+ CommandIndex Index;<br>
+};<br>
+<br>
+} // namespace<br>
+<br>
+std::unique_ptr<CompilationDatabase><br>
+inferMissingCompileCommands(std::unique_ptr<CompilationDatabase> Inner) {<br>
+ return llvm::make_unique<InterpolatingCompilationDatabase>(std::move(Inner));<br>
+}<br>
+<br>
+} // namespace tooling<br>
+} // namespace clang<br>
<br>
Modified: cfe/trunk/unittests/Tooling/CompilationDatabaseTest.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/CompilationDatabaseTest.cpp?rev=329580&r1=329579&r2=329580&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/CompilationDatabaseTest.cpp?rev=329580&r1=329579&r2=329580&view=diff</a><br>
==============================================================================<br>
--- cfe/trunk/unittests/Tooling/CompilationDatabaseTest.cpp (original)<br>
+++ cfe/trunk/unittests/Tooling/CompilationDatabaseTest.cpp Mon Apr 9 08:17:39 2018<br>
@@ -626,5 +626,115 @@ TEST(ParseFixedCompilationDatabase, Hand<br>
EXPECT_EQ(2, Argc);<br>
}<br>
<br>
+struct MemCDB : public CompilationDatabase {<br>
+ using EntryMap = llvm::StringMap<SmallVector<CompileCommand, 1>>;<br>
+ EntryMap Entries;<br>
+ MemCDB(const EntryMap &E) : Entries(E) {}<br>
+<br>
+ std::vector<CompileCommand> getCompileCommands(StringRef F) const override {<br>
+ auto Ret = Entries.lookup(F);<br>
+ return {Ret.begin(), Ret.end()};<br>
+ }<br>
+<br>
+ std::vector<std::string> getAllFiles() const override {<br>
+ std::vector<std::string> Result;<br>
+ for (const auto &Entry : Entries)<br>
+ Result.push_back(Entry.first());<br>
+ return Result;<br>
+ }<br>
+};<br>
+<br>
+class InterpolateTest : public ::testing::Test {<br>
+protected:<br>
+ // Adds an entry to the underlying compilation database.<br>
+ // A flag is injected: -D <File>, so the command used can be identified.<br>
+ void add(llvm::StringRef File, llvm::StringRef Flags = "") {<br>
+ llvm::SmallVector<StringRef, 8> Argv = {"clang", File, "-D", File};<br>
+ llvm::SplitString(Flags, Argv);<br>
+ llvm::SmallString<32> Dir;<br>
+ llvm::sys::path::system_temp_directory(false, Dir);<br>
+ Entries[path(File)].push_back(<br>
+ {Dir, path(File), {Argv.begin(), Argv.end()}, "foo.o"});<br>
+ }<br>
+<br>
+ // Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h)<br>
+ std::string path(llvm::SmallString<32> File) {<br>
+ llvm::SmallString<32> Dir;<br>
+ llvm::sys::path::system_temp_directory(false, Dir);<br>
+ llvm::sys::path::native(File);<br>
+ llvm::SmallString<64> Result;<br>
+ llvm::sys::path::append(Result, Dir, File);<br>
+ return Result.str();<br>
+ }<br>
+<br>
+ // Look up the command from a relative path, and return it in string form.<br>
+ // The input file is not included in the returned command.<br>
+ std::string getCommand(llvm::StringRef F) {<br>
+ auto Results =<br>
+ inferMissingCompileCommands(llvm::make_unique<MemCDB>(Entries))<br>
+ ->getCompileCommands(path(F));<br>
+ if (Results.empty())<br>
+ return "none";<br>
+ // drop the input file argument, so tests don't have to deal with path().<br>
+ EXPECT_EQ(Results[0].CommandLine.back(), path(F))<br>
+ << "Last arg should be the file";<br>
+ Results[0].CommandLine.pop_back();<br>
+ return llvm::join(Results[0].CommandLine, " ");<br>
+ }<br>
+<br>
+ MemCDB::EntryMap Entries;<br>
+};<br>
+<br>
+TEST_F(InterpolateTest, Nearby) {<br>
+ add("dir/foo.cpp");<br>
+ add("dir/bar.cpp");<br>
+ add("an/other/foo.cpp");<br>
+<br>
+ // great: dir and name both match (prefix or full, case insensitive)<br>
+ EXPECT_EQ(getCommand("dir/f.cpp"), "clang -D dir/foo.cpp");<br>
+ EXPECT_EQ(getCommand("dir/FOO.cpp"), "clang -D dir/foo.cpp");<br>
+ // no name match. prefer matching dir, break ties by alpha<br>
+ EXPECT_EQ(getCommand("dir/a.cpp"), "clang -D dir/bar.cpp");<br>
+ // an exact name match beats one segment of directory match<br>
+ EXPECT_EQ(getCommand("some/other/bar.h"),<br>
+ "clang -D dir/bar.cpp -x c++-header");<br>
+ // two segments of directory match beat a prefix name match<br>
+ EXPECT_EQ(getCommand("an/other/b.cpp"), "clang -D an/other/foo.cpp");<br>
+ // if nothing matches at all, we still get the closest alpha match<br>
+ EXPECT_EQ(getCommand("below/some/obscure/path.cpp"),<br>
+ "clang -D an/other/foo.cpp");<br>
+}<br>
+<br>
+TEST_F(InterpolateTest, Language) {<br>
+ add("dir/foo.cpp", "-std=c++17");<br>
+ add("dir/baz.cee", "-x c");<br>
+<br>
+ // .h is ambiguous, so we add explicit language flags<br>
+ EXPECT_EQ(getCommand("foo.h"),<br>
+ "clang -D dir/foo.cpp -x c++-header -std c++17");<br>
+ // and don't add -x if the inferred language is correct.<br>
+ EXPECT_EQ(getCommand("foo.hpp"), "clang -D dir/foo.cpp -std c++17");<br>
+ // respect -x if it's already there.<br>
+ EXPECT_EQ(getCommand("baz.h"), "clang -D dir/baz.cee -x c-header");<br>
+ // prefer a worse match with the right language<br>
+ EXPECT_EQ(getCommand("foo.c"), "clang -D dir/baz.cee");<br>
+ Entries.erase(path(StringRef("dir/baz.cee")));<br>
+ // Now we transfer across languages, so drop -std too.<br>
+ EXPECT_EQ(getCommand("foo.c"), "clang -D dir/foo.cpp");<br>
+}<br>
+<br>
+TEST_F(InterpolateTest, Strip) {<br>
+ add("dir/foo.cpp", "-o foo.o -Wall");<br>
+ // the -o option and the input file are removed, but -Wall is preserved.<br>
+ EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall");<br>
+}<br>
+<br>
+TEST_F(InterpolateTest, Case) {<br>
+ add("FOO/BAR/BAZ/SHOUT.cc");<br>
+ add("foo/bar/baz/quiet.cc");<br>
+ // Case mismatches are completely ignored, so we choose the name match.<br>
+ EXPECT_EQ(getCommand("foo/bar/baz/shout.C"), "clang -D FOO/BAR/BAZ/SHOUT.cc");<br>
+}<br>
+<br>
} // end namespace tooling<br>
} // end namespace clang<br>
<br>
<br>
_______________________________________________<br>
cfe-commits mailing list<br>
<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a><br>
<a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits</a><br>
</blockquote></div><br></div>
</blockquote></div>