xref: /freebsd/contrib/llvm-project/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp (revision 0fcececbac9880b092aeb56a41a16f1ec8ac1ae6)
1  //===- ExtractAPI/ExtractAPIConsumer.cpp ------------------------*- C++ -*-===//
2  //
3  // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4  // See https://llvm.org/LICENSE.txt for license information.
5  // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6  //
7  //===----------------------------------------------------------------------===//
8  ///
9  /// \file
10  /// This file implements the ExtractAPIAction, and ASTConsumer to collect API
11  /// information.
12  ///
13  //===----------------------------------------------------------------------===//
14  
15  #include "clang/AST/ASTConsumer.h"
16  #include "clang/AST/ASTContext.h"
17  #include "clang/Basic/DiagnosticFrontend.h"
18  #include "clang/Basic/SourceLocation.h"
19  #include "clang/Basic/SourceManager.h"
20  #include "clang/Basic/TargetInfo.h"
21  #include "clang/ExtractAPI/API.h"
22  #include "clang/ExtractAPI/APIIgnoresList.h"
23  #include "clang/ExtractAPI/ExtractAPIVisitor.h"
24  #include "clang/ExtractAPI/FrontendActions.h"
25  #include "clang/ExtractAPI/Serialization/SymbolGraphSerializer.h"
26  #include "clang/Frontend/ASTConsumers.h"
27  #include "clang/Frontend/CompilerInstance.h"
28  #include "clang/Frontend/FrontendOptions.h"
29  #include "clang/Lex/MacroInfo.h"
30  #include "clang/Lex/PPCallbacks.h"
31  #include "clang/Lex/Preprocessor.h"
32  #include "clang/Lex/PreprocessorOptions.h"
33  #include "llvm/ADT/DenseSet.h"
34  #include "llvm/ADT/STLExtras.h"
35  #include "llvm/ADT/SmallVector.h"
36  #include "llvm/Support/Error.h"
37  #include "llvm/Support/FileSystem.h"
38  #include "llvm/Support/MemoryBuffer.h"
39  #include "llvm/Support/Path.h"
40  #include "llvm/Support/Regex.h"
41  #include "llvm/Support/raw_ostream.h"
42  #include <memory>
43  #include <optional>
44  #include <utility>
45  
46  using namespace clang;
47  using namespace extractapi;
48  
49  namespace {
50  
51  std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
52                                                    StringRef File,
53                                                    bool *IsQuoted = nullptr) {
54    assert(CI.hasFileManager() &&
55           "CompilerInstance does not have a FileNamager!");
56  
57    using namespace llvm::sys;
58    // Matches framework include patterns
59    const llvm::Regex Rule("/(.+)\\.framework/(.+)?Headers/(.+)");
60  
61    const auto &FS = CI.getVirtualFileSystem();
62  
63    SmallString<128> FilePath(File.begin(), File.end());
64    FS.makeAbsolute(FilePath);
65    path::remove_dots(FilePath, true);
66    FilePath = path::convert_to_slash(FilePath);
67    File = FilePath;
68  
69    // Checks whether `Dir` is a strict path prefix of `File`. If so returns
70    // the prefix length. Otherwise return 0.
71    auto CheckDir = [&](llvm::StringRef Dir) -> unsigned {
72      llvm::SmallString<32> DirPath(Dir.begin(), Dir.end());
73      FS.makeAbsolute(DirPath);
74      path::remove_dots(DirPath, true);
75      Dir = DirPath;
76      for (auto NI = path::begin(File), NE = path::end(File),
77                DI = path::begin(Dir), DE = path::end(Dir);
78           /*termination condition in loop*/; ++NI, ++DI) {
79        // '.' components in File are ignored.
80        while (NI != NE && *NI == ".")
81          ++NI;
82        if (NI == NE)
83          break;
84  
85        // '.' components in Dir are ignored.
86        while (DI != DE && *DI == ".")
87          ++DI;
88  
89        // Dir is a prefix of File, up to '.' components and choice of path
90        // separators.
91        if (DI == DE)
92          return NI - path::begin(File);
93  
94        // Consider all path separators equal.
95        if (NI->size() == 1 && DI->size() == 1 &&
96            path::is_separator(NI->front()) && path::is_separator(DI->front()))
97          continue;
98  
99        // Special case Apple .sdk folders since the search path is typically a
100        // symlink like `iPhoneSimulator14.5.sdk` while the file is instead
101        // located in `iPhoneSimulator.sdk` (the real folder).
102        if (NI->endswith(".sdk") && DI->endswith(".sdk")) {
103          StringRef NBasename = path::stem(*NI);
104          StringRef DBasename = path::stem(*DI);
105          if (DBasename.startswith(NBasename))
106            continue;
107        }
108  
109        if (*NI != *DI)
110          break;
111      }
112      return 0;
113    };
114  
115    unsigned PrefixLength = 0;
116  
117    // Go through the search paths and find the first one that is a prefix of
118    // the header.
119    for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries) {
120      // Note whether the match is found in a quoted entry.
121      if (IsQuoted)
122        *IsQuoted = Entry.Group == frontend::Quoted;
123  
124      if (auto EntryFile = CI.getFileManager().getOptionalFileRef(Entry.Path)) {
125        if (auto HMap = HeaderMap::Create(*EntryFile, CI.getFileManager())) {
126          // If this is a headermap entry, try to reverse lookup the full path
127          // for a spelled name before mapping.
128          StringRef SpelledFilename = HMap->reverseLookupFilename(File);
129          if (!SpelledFilename.empty())
130            return SpelledFilename.str();
131  
132          // No matching mapping in this headermap, try next search entry.
133          continue;
134        }
135      }
136  
137      // Entry is a directory search entry, try to check if it's a prefix of File.
138      PrefixLength = CheckDir(Entry.Path);
139      if (PrefixLength > 0) {
140        // The header is found in a framework path, construct the framework-style
141        // include name `<Framework/Header.h>`
142        if (Entry.IsFramework) {
143          SmallVector<StringRef, 4> Matches;
144          Rule.match(File, &Matches);
145          // Returned matches are always in stable order.
146          if (Matches.size() != 4)
147            return std::nullopt;
148  
149          return path::convert_to_slash(
150              (Matches[1].drop_front(Matches[1].rfind('/') + 1) + "/" +
151               Matches[3])
152                  .str());
153        }
154  
155        // The header is found in a normal search path, strip the search path
156        // prefix to get an include name.
157        return path::convert_to_slash(File.drop_front(PrefixLength));
158      }
159    }
160  
161    // Couldn't determine a include name, use full path instead.
162    return std::nullopt;
163  }
164  
165  struct LocationFileChecker {
166    bool operator()(SourceLocation Loc) {
167      // If the loc refers to a macro expansion we need to first get the file
168      // location of the expansion.
169      auto &SM = CI.getSourceManager();
170      auto FileLoc = SM.getFileLoc(Loc);
171      FileID FID = SM.getFileID(FileLoc);
172      if (FID.isInvalid())
173        return false;
174  
175      const auto *File = SM.getFileEntryForID(FID);
176      if (!File)
177        return false;
178  
179      if (KnownFileEntries.count(File))
180        return true;
181  
182      if (ExternalFileEntries.count(File))
183        return false;
184  
185      StringRef FileName = File->tryGetRealPathName().empty()
186                               ? File->getName()
187                               : File->tryGetRealPathName();
188  
189      // Try to reduce the include name the same way we tried to include it.
190      bool IsQuoted = false;
191      if (auto IncludeName = getRelativeIncludeName(CI, FileName, &IsQuoted))
192        if (llvm::any_of(KnownFiles,
193                         [&IsQuoted, &IncludeName](const auto &KnownFile) {
194                           return KnownFile.first.equals(*IncludeName) &&
195                                  KnownFile.second == IsQuoted;
196                         })) {
197          KnownFileEntries.insert(File);
198          return true;
199        }
200  
201      // Record that the file was not found to avoid future reverse lookup for
202      // the same file.
203      ExternalFileEntries.insert(File);
204      return false;
205    }
206  
207    LocationFileChecker(const CompilerInstance &CI,
208                        SmallVector<std::pair<SmallString<32>, bool>> &KnownFiles)
209        : CI(CI), KnownFiles(KnownFiles), ExternalFileEntries() {
210      for (const auto &KnownFile : KnownFiles)
211        if (auto FileEntry = CI.getFileManager().getFile(KnownFile.first))
212          KnownFileEntries.insert(*FileEntry);
213    }
214  
215  private:
216    const CompilerInstance &CI;
217    SmallVector<std::pair<SmallString<32>, bool>> &KnownFiles;
218    llvm::DenseSet<const FileEntry *> KnownFileEntries;
219    llvm::DenseSet<const FileEntry *> ExternalFileEntries;
220  };
221  
222  class ExtractAPIConsumer : public ASTConsumer {
223  public:
224    ExtractAPIConsumer(ASTContext &Context,
225                       std::unique_ptr<LocationFileChecker> LCF, APISet &API)
226        : Visitor(Context, *LCF, API), LCF(std::move(LCF)) {}
227  
228    void HandleTranslationUnit(ASTContext &Context) override {
229      // Use ExtractAPIVisitor to traverse symbol declarations in the context.
230      Visitor.TraverseDecl(Context.getTranslationUnitDecl());
231    }
232  
233  private:
234    ExtractAPIVisitor Visitor;
235    std::unique_ptr<LocationFileChecker> LCF;
236  };
237  
238  class MacroCallback : public PPCallbacks {
239  public:
240    MacroCallback(const SourceManager &SM, LocationFileChecker &LCF, APISet &API,
241                  Preprocessor &PP)
242        : SM(SM), LCF(LCF), API(API), PP(PP) {}
243  
244    void MacroDefined(const Token &MacroNameToken,
245                      const MacroDirective *MD) override {
246      auto *MacroInfo = MD->getMacroInfo();
247  
248      if (MacroInfo->isBuiltinMacro())
249        return;
250  
251      auto SourceLoc = MacroNameToken.getLocation();
252      if (SM.isWrittenInBuiltinFile(SourceLoc) ||
253          SM.isWrittenInCommandLineFile(SourceLoc))
254        return;
255  
256      PendingMacros.emplace_back(MacroNameToken, MD);
257    }
258  
259    // If a macro gets undefined at some point during preprocessing of the inputs
260    // it means that it isn't an exposed API and we should therefore not add a
261    // macro definition for it.
262    void MacroUndefined(const Token &MacroNameToken, const MacroDefinition &MD,
263                        const MacroDirective *Undef) override {
264      // If this macro wasn't previously defined we don't need to do anything
265      // here.
266      if (!Undef)
267        return;
268  
269      llvm::erase_if(PendingMacros, [&MD, this](const PendingMacro &PM) {
270        return MD.getMacroInfo()->isIdenticalTo(*PM.MD->getMacroInfo(), PP,
271                                                /*Syntactically*/ false);
272      });
273    }
274  
275    void EndOfMainFile() override {
276      for (auto &PM : PendingMacros) {
277        // `isUsedForHeaderGuard` is only set when the preprocessor leaves the
278        // file so check for it here.
279        if (PM.MD->getMacroInfo()->isUsedForHeaderGuard())
280          continue;
281  
282        if (!LCF(PM.MacroNameToken.getLocation()))
283          continue;
284  
285        StringRef Name = PM.MacroNameToken.getIdentifierInfo()->getName();
286        PresumedLoc Loc = SM.getPresumedLoc(PM.MacroNameToken.getLocation());
287        StringRef USR =
288            API.recordUSRForMacro(Name, PM.MacroNameToken.getLocation(), SM);
289  
290        API.addMacroDefinition(
291            Name, USR, Loc,
292            DeclarationFragmentsBuilder::getFragmentsForMacro(Name, PM.MD),
293            DeclarationFragmentsBuilder::getSubHeadingForMacro(Name),
294            SM.isInSystemHeader(PM.MacroNameToken.getLocation()));
295      }
296  
297      PendingMacros.clear();
298    }
299  
300  private:
301    struct PendingMacro {
302      Token MacroNameToken;
303      const MacroDirective *MD;
304  
305      PendingMacro(const Token &MacroNameToken, const MacroDirective *MD)
306          : MacroNameToken(MacroNameToken), MD(MD) {}
307    };
308  
309    const SourceManager &SM;
310    LocationFileChecker &LCF;
311    APISet &API;
312    Preprocessor &PP;
313    llvm::SmallVector<PendingMacro> PendingMacros;
314  };
315  
316  } // namespace
317  
318  std::unique_ptr<ASTConsumer>
319  ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
320    OS = CreateOutputFile(CI, InFile);
321    if (!OS)
322      return nullptr;
323  
324    auto ProductName = CI.getFrontendOpts().ProductName;
325  
326    // Now that we have enough information about the language options and the
327    // target triple, let's create the APISet before anyone uses it.
328    API = std::make_unique<APISet>(
329        CI.getTarget().getTriple(),
330        CI.getFrontendOpts().Inputs.back().getKind().getLanguage(), ProductName);
331  
332    auto LCF = std::make_unique<LocationFileChecker>(CI, KnownInputFiles);
333  
334    CI.getPreprocessor().addPPCallbacks(std::make_unique<MacroCallback>(
335        CI.getSourceManager(), *LCF, *API, CI.getPreprocessor()));
336  
337    // Do not include location in anonymous decls.
338    PrintingPolicy Policy = CI.getASTContext().getPrintingPolicy();
339    Policy.AnonymousTagLocations = false;
340    CI.getASTContext().setPrintingPolicy(Policy);
341  
342    if (!CI.getFrontendOpts().ExtractAPIIgnoresFile.empty()) {
343      llvm::handleAllErrors(
344          APIIgnoresList::create(CI.getFrontendOpts().ExtractAPIIgnoresFile,
345                                 CI.getFileManager())
346              .moveInto(IgnoresList),
347          [&CI](const IgnoresFileNotFound &Err) {
348            CI.getDiagnostics().Report(
349                diag::err_extract_api_ignores_file_not_found)
350                << Err.Path;
351          });
352    }
353  
354    return std::make_unique<ExtractAPIConsumer>(CI.getASTContext(),
355                                                std::move(LCF), *API);
356  }
357  
358  bool ExtractAPIAction::PrepareToExecuteAction(CompilerInstance &CI) {
359    auto &Inputs = CI.getFrontendOpts().Inputs;
360    if (Inputs.empty())
361      return true;
362  
363    if (!CI.hasFileManager())
364      if (!CI.createFileManager())
365        return false;
366  
367    auto Kind = Inputs[0].getKind();
368  
369    // Convert the header file inputs into a single input buffer.
370    SmallString<256> HeaderContents;
371    bool IsQuoted = false;
372    for (const FrontendInputFile &FIF : Inputs) {
373      if (Kind.isObjectiveC())
374        HeaderContents += "#import";
375      else
376        HeaderContents += "#include";
377  
378      StringRef FilePath = FIF.getFile();
379      if (auto RelativeName = getRelativeIncludeName(CI, FilePath, &IsQuoted)) {
380        if (IsQuoted)
381          HeaderContents += " \"";
382        else
383          HeaderContents += " <";
384  
385        HeaderContents += *RelativeName;
386  
387        if (IsQuoted)
388          HeaderContents += "\"\n";
389        else
390          HeaderContents += ">\n";
391        KnownInputFiles.emplace_back(static_cast<SmallString<32>>(*RelativeName),
392                                     IsQuoted);
393      } else {
394        HeaderContents += " \"";
395        HeaderContents += FilePath;
396        HeaderContents += "\"\n";
397        KnownInputFiles.emplace_back(FilePath, true);
398      }
399    }
400  
401    if (CI.getHeaderSearchOpts().Verbose)
402      CI.getVerboseOutputStream() << getInputBufferName() << ":\n"
403                                  << HeaderContents << "\n";
404  
405    Buffer = llvm::MemoryBuffer::getMemBufferCopy(HeaderContents,
406                                                  getInputBufferName());
407  
408    // Set that buffer up as our "real" input in the CompilerInstance.
409    Inputs.clear();
410    Inputs.emplace_back(Buffer->getMemBufferRef(), Kind, /*IsSystem*/ false);
411  
412    return true;
413  }
414  
415  void ExtractAPIAction::EndSourceFileAction() {
416    if (!OS)
417      return;
418  
419    // Setup a SymbolGraphSerializer to write out collected API information in
420    // the Symbol Graph format.
421    // FIXME: Make the kind of APISerializer configurable.
422    SymbolGraphSerializer SGSerializer(*API, IgnoresList);
423    SGSerializer.serialize(*OS);
424    OS.reset();
425  }
426  
427  std::unique_ptr<raw_pwrite_stream>
428  ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) {
429    std::unique_ptr<raw_pwrite_stream> OS =
430        CI.createDefaultOutputFile(/*Binary=*/false, InFile, /*Extension=*/"json",
431                                   /*RemoveFileOnSignal=*/false);
432    if (!OS)
433      return nullptr;
434    return OS;
435  }
436