zixuw created this revision.
zixuw added reviewers: ributzka, dang, cishida.
Herald added a project: All.
zixuw requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Proof-of-concept patch. Needs more work.
Transform input headers to relative (angle) includes.
`getRelativeIncludeName` walks `HeaderSearchOptions::UserEntries` to try
to find a relative include stem. Problems:

- do we want the exact logic of `HeaderSearch::suggestPathToFileForDiagnostics`?
- in `ExtractAPIAction::PrepareToExecuteAction` where the includes are 
transformed, we don't have a pre-processor yet, so we don't have access to a 
lot of useful information like `HeaderSearch`, headermaps, DirectoryLookup etc.
- we might not always want to transform an absolute path because the resulting 
relative include name might get remapped in a headermap, for example in test 
`known_files_only_hmap.c`. But how does it work with modules where we need 
relative includes? Is the setup in `known_files_only_hmap` even valid?
- reverse lookup in headermap when deciding if the symbol is coming from a 
knwon file: I think my current implementation is correct and necessary to solve 
the headermap problem, but might have problems due to the above point: an 
unknown file might be mapped to the transformed relative name of a known input.

I think the key problem is that, by passing in an input
`/absolute/path/header.h`, do we mean to process the actual content of
that particular header, or do we mean to use it just as a "lable" and
the thing we really want to process is whatever this lable points to
with the given search paths and headermaps?
In the example of frameworks, I think the latter is true: we want to
use DSTROOT to pass in the headers because DSTROOT has a defined
framework layout so that we can transform the paths to framework-style
relative includes by matching the layout pattern `*.framework/Header/`
and rely on headermaps/VFS/module/search paths to find the intended
headers.
In that sense, the `known_files_only_hmap` test is a really strange
setup with ambiguious intention and context.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D123831

Files:
  clang/lib/ExtractAPI/ExtractAPIConsumer.cpp

Index: clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
===================================================================
--- clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
+++ clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
@@ -38,7 +38,10 @@
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Regex.h"
 #include "llvm/Support/raw_ostream.h"
 #include <memory>
 #include <utility>
@@ -55,10 +58,113 @@
   return {};
 }
 
+Optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
+                                             StringRef File) {
+  using namespace llvm::sys;
+  // Matches framework include patterns
+  const llvm::Regex Rule("/(.+)\\.framework/(.+)?Headers/(.+)");
+  StringRef WorkingDir = CI.getFileSystemOpts().WorkingDir;
+
+  SmallString<128> FilePath(File.begin(), File.end());
+  if (!WorkingDir.empty() && !path::is_absolute(FilePath))
+    fs::make_absolute(WorkingDir, FilePath);
+  path::remove_dots(FilePath, true);
+  File = FilePath;
+
+  // FIXME: The following is adapted from
+  // HeaderSearch::suggestPathToFileForDiagnostics. Need to figure out if
+  // everything here is actually needed.
+  unsigned BestPrefixLength = 0;
+  // Checks whether `Dir` is a strict path prefix of `File`. If so and that's
+  // the longest prefix we've seen so for it, returns true and updates the
+  // `BestPrefixLength` accordingly.
+  auto CheckDir = [&](llvm::StringRef Dir) -> bool {
+    llvm::SmallString<32> DirPath(Dir.begin(), Dir.end());
+    if (!WorkingDir.empty() && !path::is_absolute(DirPath))
+      fs::make_absolute(WorkingDir, DirPath);
+    path::remove_dots(DirPath, true);
+    Dir = DirPath;
+    for (auto NI = path::begin(File), NE = path::end(File),
+              DI = path::begin(Dir), DE = path::end(Dir);
+         /*termination condition in loop*/; ++NI, ++DI) {
+      // '.' components in File are ignored.
+      while (NI != NE && *NI == ".")
+        ++NI;
+      if (NI == NE)
+        break;
+
+      // '.' components in Dir are ignored.
+      while (DI != DE && *DI == ".")
+        ++DI;
+      if (DI == DE) {
+        // Dir is a prefix of File, up to '.' components and choice of path
+        // separators.
+        unsigned PrefixLength = NI - path::begin(File);
+        if (PrefixLength > BestPrefixLength) {
+          BestPrefixLength = PrefixLength;
+          return true;
+        }
+        break;
+      }
+
+      // Consider all path separators equal.
+      if (NI->size() == 1 && DI->size() == 1 &&
+          path::is_separator(NI->front()) && path::is_separator(DI->front()))
+        continue;
+
+      // Special case Apple .sdk folders since the search path is typically a
+      // symlink like `iPhoneSimulator14.5.sdk` while the file is instead
+      // located in `iPhoneSimulator.sdk` (the real folder).
+      if (NI->endswith(".sdk") && DI->endswith(".sdk")) {
+        StringRef NBasename = path::stem(*NI);
+        StringRef DBasename = path::stem(*DI);
+        if (DBasename.startswith(NBasename))
+          continue;
+      }
+
+      if (*NI != *DI)
+        break;
+    }
+    return false;
+  };
+
+  bool BestPrefixIsFramework = false;
+
+  for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries) {
+    if (CheckDir(Entry.Path)) {
+      if (Entry.IsFramework)
+        BestPrefixIsFramework = true;
+      else
+        BestPrefixIsFramework = false;
+    }
+  }
+
+  if (!BestPrefixLength && CheckDir(CI.getFileSystemOpts().WorkingDir))
+    BestPrefixIsFramework = false;
+
+  if (!BestPrefixLength)
+    return None;
+
+  if (BestPrefixIsFramework) {
+    SmallVector<StringRef, 4> Matches;
+    Rule.match(File, &Matches);
+    // Returned matches are always in stable order.
+    if (Matches.size() != 4)
+      return None;
+
+    return path::convert_to_slash(
+        (Matches[1].drop_front(Matches[1].rfind('/') + 1) + "/" + Matches[3])
+            .str());
+  }
+
+  return path::convert_to_slash(File.drop_front(BestPrefixLength));
+}
+
 struct LocationFileChecker {
   bool isLocationInKnownFile(SourceLocation Loc) {
     // If the loc refers to a macro expansion we need to first get the file
     // location of the expansion.
+    auto &SM = CI.getSourceManager();
     auto FileLoc = SM.getFileLoc(Loc);
     FileID FID = SM.getFileID(FileLoc);
     if (FID.isInvalid())
@@ -71,20 +177,44 @@
     if (KnownFileEntries.count(File))
       return true;
 
+    if (ExternalFileEntries.count(File))
+      return false;
+
+    for (const DirectoryLookup &DL :
+         CI.getPreprocessor().getHeaderSearchInfo().search_dir_range()) {
+      if (!DL.isHeaderMap())
+        continue;
+
+      StringRef SpelledFilename =
+          DL.getHeaderMap()->reverseLookupFilename(File->getName());
+      if (!SpelledFilename.empty() &&
+          llvm::find(KnownFiles, SpelledFilename) != KnownFiles.end()) {
+        // Record that the file was found to avoid future reverse lookup for the
+        // same file.
+        KnownFileEntries.insert(File);
+        return true;
+      }
+    }
+
+    // Record that the file was not found to avoid future reverse lookup for
+    // the same file.
+    ExternalFileEntries.insert(File);
     return false;
   }
 
-  LocationFileChecker(const SourceManager &SM,
-                      const std::vector<std::string> &KnownFiles)
-      : SM(SM) {
+  LocationFileChecker(const CompilerInstance &CI,
+                      std::vector<std::string> &KnownFiles)
+      : CI(CI), KnownFiles(KnownFiles), ExternalFileEntries() {
     for (const auto &KnownFilePath : KnownFiles)
-      if (auto FileEntry = SM.getFileManager().getFile(KnownFilePath))
+      if (auto FileEntry = CI.getFileManager().getFile(KnownFilePath))
         KnownFileEntries.insert(*FileEntry);
   }
 
 private:
-  const SourceManager &SM;
+  const CompilerInstance &CI;
+  std::vector<std::string> &KnownFiles;
   llvm::DenseSet<const FileEntry *> KnownFileEntries;
+  llvm::DenseSet<const FileEntry *> ExternalFileEntries;
 };
 
 /// The RecursiveASTVisitor to traverse symbol declarations and collect API
@@ -743,8 +873,7 @@
       CI.getTarget().getTriple(),
       CI.getFrontendOpts().Inputs.back().getKind().getLanguage());
 
-  auto LCF = std::make_unique<LocationFileChecker>(CI.getSourceManager(),
-                                                   KnownInputFiles);
+  auto LCF = std::make_unique<LocationFileChecker>(CI, KnownInputFiles);
 
   CI.getPreprocessor().addPPCallbacks(std::make_unique<MacroCallback>(
       CI.getSourceManager(), *LCF, *API, CI.getPreprocessor()));
@@ -767,11 +896,19 @@
       HeaderContents += "#import";
     else
       HeaderContents += "#include";
-    HeaderContents += " \"";
-    HeaderContents += FIF.getFile();
-    HeaderContents += "\"\n";
 
-    KnownInputFiles.emplace_back(FIF.getFile());
+    StringRef FilePath = FIF.getFile();
+    if (auto RelativeName = getRelativeIncludeName(CI, FilePath)) {
+      HeaderContents += " <";
+      HeaderContents += *RelativeName;
+      HeaderContents += ">\n";
+      KnownInputFiles.emplace_back(*RelativeName);
+    } else {
+      HeaderContents += " \"";
+      HeaderContents += FilePath;
+      HeaderContents += "\"\n";
+      KnownInputFiles.emplace_back(FilePath);
+    }
   }
 
   Buffer = llvm::MemoryBuffer::getMemBufferCopy(HeaderContents,
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to