summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clangd/Headers.cpp
blob: 3d82fe5ee6bbb6f87bbdb8c01e0ece95fe1690a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//===--- Headers.cpp - Include headers ---------------------------*- C++-*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "Headers.h"
#include "Compiler.h"
#include "Logger.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/Support/Path.h"

namespace clang {
namespace clangd {
namespace {

class RecordHeaders : public PPCallbacks {
public:
  RecordHeaders(llvm::StringSet<> &WrittenHeaders,
                llvm::StringSet<> &ResolvedHeaders)
      : WrittenHeaders(WrittenHeaders), ResolvedHeaders(ResolvedHeaders) {}

  void InclusionDirective(SourceLocation /*HashLoc*/,
                          const Token & /*IncludeTok*/,
                          llvm::StringRef FileName, bool IsAngled,
                          CharSourceRange /*FilenameRange*/,
                          const FileEntry *File, llvm::StringRef /*SearchPath*/,
                          llvm::StringRef /*RelativePath*/,
                          const Module * /*Imported*/) override {
    WrittenHeaders.insert(
        (IsAngled ? "<" + FileName + ">" : "\"" + FileName + "\"").str());
    if (File != nullptr && !File->tryGetRealPathName().empty())
      ResolvedHeaders.insert(File->tryGetRealPathName());
  }

private:
  llvm::StringSet<> &WrittenHeaders;
  llvm::StringSet<> &ResolvedHeaders;
};

} // namespace

bool isLiteralInclude(llvm::StringRef Include) {
  return Include.startswith("<") || Include.startswith("\"");
}

bool HeaderFile::valid() const {
  return (Verbatim && isLiteralInclude(File)) ||
         (!Verbatim && llvm::sys::path::is_absolute(File));
}

/// FIXME(ioeric): we might not want to insert an absolute include path if the
/// path is not shortened.
llvm::Expected<std::string>
calculateIncludePath(llvm::StringRef File, llvm::StringRef Code,
                     const HeaderFile &DeclaringHeader,
                     const HeaderFile &InsertedHeader,
                     const tooling::CompileCommand &CompileCommand,
                     IntrusiveRefCntPtr<vfs::FileSystem> FS) {
  assert(llvm::sys::path::is_absolute(File));
  assert(DeclaringHeader.valid() && InsertedHeader.valid());
  if (File == DeclaringHeader.File || File == InsertedHeader.File)
    return "";
  FS->setCurrentWorkingDirectory(CompileCommand.Directory);

  // Set up a CompilerInstance and create a preprocessor to collect existing
  // #include headers in \p Code. Preprocesor also provides HeaderSearch with
  // which we can calculate the shortest include path for \p Header.
  std::vector<const char *> Argv;
  for (const auto &S : CompileCommand.CommandLine)
    Argv.push_back(S.c_str());
  IgnoringDiagConsumer IgnoreDiags;
  auto CI = clang::createInvocationFromCommandLine(
      Argv,
      CompilerInstance::createDiagnostics(new DiagnosticOptions(), &IgnoreDiags,
                                          false),
      FS);
  if (!CI)
    return llvm::make_error<llvm::StringError>(
        "Failed to create a compiler instance for " + File,
        llvm::inconvertibleErrorCode());
  CI->getFrontendOpts().DisableFree = false;
  // Parse the main file to get all existing #includes in the file, and then we
  // can make sure the same header (even with different include path) is not
  // added more than once.
  CI->getPreprocessorOpts().SingleFileParseMode = true;

  // The diagnostic options must be set before creating a CompilerInstance.
  CI->getDiagnosticOpts().IgnoreWarnings = true;
  auto Clang = prepareCompilerInstance(
      std::move(CI), /*Preamble=*/nullptr,
      llvm::MemoryBuffer::getMemBuffer(Code, File),
      std::make_shared<PCHContainerOperations>(), FS, IgnoreDiags);

  if (Clang->getFrontendOpts().Inputs.empty())
    return llvm::make_error<llvm::StringError>(
        "Empty frontend action inputs empty for file " + File,
        llvm::inconvertibleErrorCode());
  PreprocessOnlyAction Action;
  if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]))
    return llvm::make_error<llvm::StringError>(
        "Failed to begin preprocessor only action for file " + File,
        llvm::inconvertibleErrorCode());
  llvm::StringSet<> WrittenHeaders;
  llvm::StringSet<> ResolvedHeaders;
  Clang->getPreprocessor().addPPCallbacks(
      llvm::make_unique<RecordHeaders>(WrittenHeaders, ResolvedHeaders));
  if (!Action.Execute())
    return llvm::make_error<llvm::StringError>(
        "Failed to execute preprocessor only action for file " + File,
        llvm::inconvertibleErrorCode());
  auto Included = [&](llvm::StringRef Header) {
    return WrittenHeaders.find(Header) != WrittenHeaders.end() ||
           ResolvedHeaders.find(Header) != ResolvedHeaders.end();
  };
  if (Included(DeclaringHeader.File) || Included(InsertedHeader.File))
    return "";

  auto &HeaderSearchInfo = Clang->getPreprocessor().getHeaderSearchInfo();
  bool IsSystem = false;

  if (InsertedHeader.Verbatim)
    return InsertedHeader.File;

  std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics(
      InsertedHeader.File, CompileCommand.Directory, &IsSystem);
  if (IsSystem)
    Suggested = "<" + Suggested + ">";
  else
    Suggested = "\"" + Suggested + "\"";

  log("Suggested #include for " + InsertedHeader.File + " is: " + Suggested);
  return Suggested;
}

} // namespace clangd
} // namespace clang
OpenPOWER on IntegriCloud