[clang] [clang][InstallAPI] Add input file support to library (PR #81701)

Cyndy Ishida via cfe-commits cfe-commits at lists.llvm.org
Wed Feb 14 16:40:30 PST 2024


https://github.com/cyndyishida updated https://github.com/llvm/llvm-project/pull/81701

>From 9d0da0010f145b23ce2b7e5b6183558609600789 Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Tue, 13 Feb 2024 18:22:23 -0800
Subject: [PATCH 1/3] [clang][InstallAPI] Add input file support to library

This patch adds support for expected InstallAPI inputs.
InstallAPI  accepts a well defined filelist of headers and how those headers
represent a single library.

InstallAPI captures header files to determine linkable symbols
to then compare against what was compiled in a binary dylib and
generate TBD files.
---
 clang/include/clang/InstallAPI/FileList.h     |  53 +++++
 clang/include/clang/InstallAPI/HeaderFile.h   |  72 +++++++
 clang/lib/ExtractAPI/CMakeLists.txt           |   1 +
 clang/lib/ExtractAPI/ExtractAPIConsumer.cpp   |   7 +-
 clang/lib/InstallAPI/CMakeLists.txt           |   2 +
 clang/lib/InstallAPI/FileList.cpp             | 196 ++++++++++++++++++
 clang/lib/InstallAPI/HeaderFile.cpp           |  37 ++++
 clang/unittests/CMakeLists.txt                |   1 +
 clang/unittests/InstallAPI/CMakeLists.txt     |   9 +
 clang/unittests/InstallAPI/FileListTest.cpp   | 140 +++++++++++++
 clang/unittests/InstallAPI/HeaderFileTest.cpp |  89 ++++++++
 11 files changed, 603 insertions(+), 4 deletions(-)
 create mode 100644 clang/include/clang/InstallAPI/FileList.h
 create mode 100644 clang/include/clang/InstallAPI/HeaderFile.h
 create mode 100644 clang/lib/InstallAPI/FileList.cpp
 create mode 100644 clang/lib/InstallAPI/HeaderFile.cpp
 create mode 100644 clang/unittests/InstallAPI/CMakeLists.txt
 create mode 100644 clang/unittests/InstallAPI/FileListTest.cpp
 create mode 100644 clang/unittests/InstallAPI/HeaderFileTest.cpp

diff --git a/clang/include/clang/InstallAPI/FileList.h b/clang/include/clang/InstallAPI/FileList.h
new file mode 100644
index 00000000000000..01ec9b35e31490
--- /dev/null
+++ b/clang/include/clang/InstallAPI/FileList.h
@@ -0,0 +1,53 @@
+//===- InstallAPI/FileList.h ------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// The JSON file list parser is used to communicate input to InstallAPI.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_INSTALLAPI_FILELIST_H
+#define LLVM_CLANG_INSTALLAPI_FILELIST_H
+
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/InstallAPI/HeaderFile.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+namespace clang {
+namespace installapi {
+
+/// JSON decoder for InstallAPI Inputs.
+class FileListReader {
+
+  class Implementation;
+  Implementation &Impl;
+
+  FileListReader(std::unique_ptr<llvm::MemoryBuffer> InputBuffer,
+                 llvm::Error &Err);
+
+public:
+  /// Decode JSON input and append header input into destination container.
+  /// Headers are loaded in the order they appear in the JSON input.
+  ///
+  /// \param InputBuffer JSON input data.
+  /// \param Destination Container to load headers into.
+  static llvm::Error
+  loadHeaders(std::unique_ptr<llvm::MemoryBuffer> InputBuffer,
+              HeaderSeq &Destination);
+
+  ~FileListReader();
+
+  FileListReader(const FileListReader &) = delete;
+  FileListReader &operator=(const FileListReader &) = delete;
+};
+
+} // namespace installapi
+} // namespace clang
+
+#endif // LLVM_CLANG_INSTALLAPI_FILELIST_H
diff --git a/clang/include/clang/InstallAPI/HeaderFile.h b/clang/include/clang/InstallAPI/HeaderFile.h
new file mode 100644
index 00000000000000..6ccd944f8b01be
--- /dev/null
+++ b/clang/include/clang/InstallAPI/HeaderFile.h
@@ -0,0 +1,72 @@
+//===- InstallAPI/HeaderFile.h ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// Representations of a library's headers for InstallAPI.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_INSTALLAPI_HEADERFILE_H
+#define LLVM_CLANG_INSTALLAPI_HEADERFILE_H
+
+#include "clang/Basic/LangStandard.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Regex.h"
+#include <optional>
+#include <string>
+
+namespace clang::installapi {
+enum class HeaderType {
+  /// Represents declarations accessible to all clients.
+  Public,
+  /// Represents declarations accessible to a disclosed set of clients.
+  Private,
+  /// Represents declarations only accessible as implementation details to the
+  /// input library.
+  Project,
+};
+
+class HeaderFile {
+  /// Full input path to header.
+  std::string FullPath;
+  /// Access level of header.
+  HeaderType Type;
+  /// Expected way header will be included by clients.
+  std::string IncludeName;
+  /// Supported language mode for header.
+  std::optional<clang::Language> Language;
+
+public:
+  HeaderFile(StringRef FullPath, HeaderType Type,
+             StringRef IncludeName = StringRef(),
+             std::optional<clang::Language> Language = std::nullopt)
+      : FullPath(FullPath), Type(Type), IncludeName(IncludeName),
+        Language(Language) {}
+
+  static llvm::Regex getFrameworkIncludeRule();
+
+  bool operator==(const HeaderFile &Other) const {
+    return std::tie(Type, FullPath, IncludeName, Language) ==
+           std::tie(Other.Type, Other.FullPath, Other.IncludeName,
+                    Other.Language);
+  }
+};
+
+/// Assemble expected way header will be included by clients.
+/// As in what maps inside the brackets of `#include <IncludeName.h>`
+/// For example,
+/// "/System/Library/Frameworks/Foo.framework/Headers/Foo.h" returns
+/// "Foo/Foo.h"
+///
+/// \param FullPath Path to the header file which includes the library
+/// structure.
+std::optional<std::string> createIncludeHeaderName(const StringRef FullPath);
+using HeaderSeq = std::vector<HeaderFile>;
+
+} // namespace clang::installapi
+
+#endif // LLVM_CLANG_INSTALLAPI_HEADERFILE_H
diff --git a/clang/lib/ExtractAPI/CMakeLists.txt b/clang/lib/ExtractAPI/CMakeLists.txt
index b43fe742478ce2..f0e3e06c0ec17e 100644
--- a/clang/lib/ExtractAPI/CMakeLists.txt
+++ b/clang/lib/ExtractAPI/CMakeLists.txt
@@ -17,5 +17,6 @@ add_clang_library(clangExtractAPI
   clangBasic
   clangFrontend
   clangIndex
+  clangInstallAPI
   clangLex
   )
diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
index fd62d841197d9f..275f49be22e15a 100644
--- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
+++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
@@ -30,6 +30,7 @@
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/FrontendOptions.h"
 #include "clang/Frontend/MultiplexConsumer.h"
+#include "clang/InstallAPI/HeaderFile.h"
 #include "clang/Lex/MacroInfo.h"
 #include "clang/Lex/PPCallbacks.h"
 #include "clang/Lex/Preprocessor.h"
@@ -61,9 +62,6 @@ std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
          "CompilerInstance does not have a FileNamager!");
 
   using namespace llvm::sys;
-  // Matches framework include patterns
-  const llvm::Regex Rule("/(.+)\\.framework/(.+)?Headers/(.+)");
-
   const auto &FS = CI.getVirtualFileSystem();
 
   SmallString<128> FilePath(File.begin(), File.end());
@@ -147,7 +145,8 @@ std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
       // include name `<Framework/Header.h>`
       if (Entry.IsFramework) {
         SmallVector<StringRef, 4> Matches;
-        Rule.match(File, &Matches);
+        clang::installapi::HeaderFile::getFrameworkIncludeRule().match(
+            File, &Matches);
         // Returned matches are always in stable order.
         if (Matches.size() != 4)
           return std::nullopt;
diff --git a/clang/lib/InstallAPI/CMakeLists.txt b/clang/lib/InstallAPI/CMakeLists.txt
index 1476b737c5e61c..6c9cb4b559f67d 100644
--- a/clang/lib/InstallAPI/CMakeLists.txt
+++ b/clang/lib/InstallAPI/CMakeLists.txt
@@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS
 
 add_clang_library(clangInstallAPI
   Context.cpp
+  FileList.cpp
+  HeaderFile.cpp
 
   LINK_LIBS
   clangAST
diff --git a/clang/lib/InstallAPI/FileList.cpp b/clang/lib/InstallAPI/FileList.cpp
new file mode 100644
index 00000000000000..6b307682346e2d
--- /dev/null
+++ b/clang/lib/InstallAPI/FileList.cpp
@@ -0,0 +1,196 @@
+//===- FileList.cpp ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/InstallAPI/FileList.h"
+#include "clang/Basic/DiagnosticFrontend.h"
+#include "clang/InstallAPI/FileList.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/TextAPI/TextAPIError.h"
+#include <optional>
+
+// clang-format off
+/*
+InstallAPI JSON Input Format specification.
+
+{
+  "headers" : [                              # Required: Key must exist.
+    {                                        # Optional: May contain 0 or more header inputs.
+      "path" : "/usr/include/mach-o/dlfn.h", # Required: Path should point to destination 
+                                             #           location where applicable.
+      "type" : "public",                     # Required: Maps to HeaderType for header.
+      "language": "c++"                      # Optional: Language mode for header.
+    }
+  ],
+  "version" : "3"                            # Required: Version 3 supports language mode 
+                                                         & project header input.
+}
+*/
+// clang-format on
+
+using namespace llvm;
+using namespace llvm::json;
+using namespace llvm::MachO;
+using namespace clang::installapi;
+
+class FileListReader::Implementation {
+private:
+  Expected<StringRef> parseString(const Object *Obj, StringRef Key,
+                                  StringRef Error);
+  Expected<StringRef> parsePath(const Object *Obj);
+  Expected<HeaderType> parseType(const Object *Obj);
+  std::optional<clang::Language> parseLanguage(const Object *Obj);
+  Error parseHeaders(Array &Headers);
+
+public:
+  std::unique_ptr<MemoryBuffer> InputBuffer;
+  unsigned Version;
+  HeaderSeq HeaderList;
+
+  Error parse(StringRef Input);
+};
+
+Expected<StringRef>
+FileListReader::Implementation::parseString(const Object *Obj, StringRef Key,
+                                            StringRef Error) {
+  auto Str = Obj->getString(Key);
+  if (!Str)
+    return make_error<StringError>(Error, inconvertibleErrorCode());
+  return *Str;
+}
+
+Expected<HeaderType>
+FileListReader::Implementation::parseType(const Object *Obj) {
+  auto TypeStr =
+      parseString(Obj, "type", "required field 'type' not specified");
+  if (!TypeStr)
+    return TypeStr.takeError();
+
+  if (*TypeStr == "public")
+    return HeaderType::Public;
+  else if (*TypeStr == "private")
+    return HeaderType::Private;
+  else if (*TypeStr == "project" && Version >= 2)
+    return HeaderType::Project;
+
+  return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
+                                  "unsupported header type");
+}
+
+Expected<StringRef>
+FileListReader::Implementation::parsePath(const Object *Obj) {
+  auto Path = parseString(Obj, "path", "required field 'path' not specified");
+  if (!Path)
+    return Path.takeError();
+
+  return *Path;
+}
+
+std::optional<clang::Language>
+FileListReader::Implementation::parseLanguage(const Object *Obj) {
+  auto Language = Obj->getString("language");
+  if (!Language)
+    return std::nullopt;
+
+  return StringSwitch<clang::Language>(*Language)
+      .Case("c", clang::Language::C)
+      .Case("c++", clang::Language::CXX)
+      .Case("objective-c", clang::Language::ObjC)
+      .Case("objective-c++", clang::Language::ObjCXX)
+      .Default(clang::Language::Unknown);
+}
+
+Error FileListReader::Implementation::parseHeaders(Array &Headers) {
+  for (const auto &H : Headers) {
+    auto *Obj = H.getAsObject();
+    if (!Obj)
+      return make_error<StringError>("expect a JSON object",
+                                     inconvertibleErrorCode());
+    auto Type = parseType(Obj);
+    if (!Type)
+      return Type.takeError();
+    auto Path = parsePath(Obj);
+    if (!Path)
+      return Path.takeError();
+    auto Language = parseLanguage(Obj);
+
+    StringRef PathStr = *Path;
+    if (*Type == HeaderType::Project) {
+      HeaderList.emplace_back(
+          HeaderFile{PathStr, *Type, /*IncludeName=*/"", Language});
+      continue;
+    }
+    auto IncludeName = createIncludeHeaderName(PathStr);
+    HeaderList.emplace_back(PathStr, *Type,
+                            IncludeName.has_value() ? IncludeName.value() : "",
+                            Language);
+  }
+
+  return Error::success();
+}
+
+Error FileListReader::Implementation::parse(StringRef Input) {
+  auto Val = json::parse(Input);
+  if (!Val)
+    return Val.takeError();
+
+  auto *Root = Val->getAsObject();
+  if (!Root)
+    return make_error<StringError>("not a JSON object",
+                                   inconvertibleErrorCode());
+
+  auto VersionStr = Root->getString("version");
+  if (!VersionStr)
+    return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
+                                    "required field 'version' not specified");
+  if (VersionStr->getAsInteger(10, Version))
+    return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
+                                    "invalid version number");
+
+  if (Version < 1 || Version > 3)
+    return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
+                                    "unsupported version");
+
+  // Not specifying any header files should be atypical, but valid.
+  auto Headers = Root->getArray("headers");
+  if (!Headers)
+    return Error::success();
+
+  Error Err = parseHeaders(*Headers);
+  if (Err)
+    return Err;
+
+  return Error::success();
+}
+
+FileListReader::FileListReader(std::unique_ptr<MemoryBuffer> InputBuffer,
+                               Error &Error)
+    : Impl(*new FileListReader::Implementation()) {
+  ErrorAsOutParameter ErrorAsOutParam(&Error);
+  Impl.InputBuffer = std::move(InputBuffer);
+
+  Error = Impl.parse(Impl.InputBuffer->getBuffer());
+}
+
+llvm::Error
+FileListReader::loadHeaders(std::unique_ptr<MemoryBuffer> InputBuffer,
+                            HeaderSeq &Destination) {
+  llvm::Error Err = Error::success();
+  std::unique_ptr<FileListReader> Reader(
+      new FileListReader(std::move(InputBuffer), Err));
+  if (Err)
+    return Err;
+
+  Destination.reserve(Destination.size() + Reader->Impl.HeaderList.size());
+  llvm::move(Reader->Impl.HeaderList, std::back_inserter(Destination));
+
+  return Err;
+}
+
+FileListReader::~FileListReader() { delete &Impl; }
diff --git a/clang/lib/InstallAPI/HeaderFile.cpp b/clang/lib/InstallAPI/HeaderFile.cpp
new file mode 100644
index 00000000000000..9c42a2d1d3b44a
--- /dev/null
+++ b/clang/lib/InstallAPI/HeaderFile.cpp
@@ -0,0 +1,37 @@
+//===- HeaderFile.cpp ------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/InstallAPI/HeaderFile.h"
+
+using namespace llvm;
+namespace clang::installapi {
+
+llvm::Regex getFrameworkIncludeRule() {
+  return llvm::Regex("/(.+)\\.framework/(.+)?Headers/(.+)");
+}
+
+std::optional<std::string> createIncludeHeaderName(const StringRef FullPath) {
+  // Headers in usr(/local)*/include.
+  std::string Pattern = "/include/";
+  auto PathPrefix = FullPath.find(Pattern);
+  if (PathPrefix != StringRef::npos) {
+    PathPrefix += Pattern.size();
+    return FullPath.drop_front(PathPrefix).str();
+  }
+
+  // Framework Headers.
+  SmallVector<StringRef, 4> Matches;
+  getFrameworkIncludeRule().match(FullPath, &Matches);
+  // Returned matches are always in stable order.
+  if (Matches.size() != 4)
+    return std::nullopt;
+
+  return Matches[1].drop_front(Matches[1].rfind('/') + 1).str() + "/" +
+         Matches[3].str();
+}
+} // namespace clang::installapi
diff --git a/clang/unittests/CMakeLists.txt b/clang/unittests/CMakeLists.txt
index f4e4f585bdd800..37ca3107b54774 100644
--- a/clang/unittests/CMakeLists.txt
+++ b/clang/unittests/CMakeLists.txt
@@ -51,5 +51,6 @@ endif()
 add_subdirectory(DirectoryWatcher)
 add_subdirectory(Rename)
 add_subdirectory(Index)
+add_subdirectory(InstallAPI)
 add_subdirectory(Serialization)
 add_subdirectory(Support)
diff --git a/clang/unittests/InstallAPI/CMakeLists.txt b/clang/unittests/InstallAPI/CMakeLists.txt
new file mode 100644
index 00000000000000..b70b7c136e64a6
--- /dev/null
+++ b/clang/unittests/InstallAPI/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_clang_unittest(InstallAPITests
+  HeaderFileTest.cpp
+  FileListTest.cpp
+  )
+
+clang_target_link_libraries(InstallAPITests
+  PRIVATE
+  clangInstallAPI 
+  )
diff --git a/clang/unittests/InstallAPI/FileListTest.cpp b/clang/unittests/InstallAPI/FileListTest.cpp
new file mode 100644
index 00000000000000..7e4ee715a26204
--- /dev/null
+++ b/clang/unittests/InstallAPI/FileListTest.cpp
@@ -0,0 +1,140 @@
+//===- unittests/InstallAPI/FileList.cpp - File List Tests ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/InstallAPI/FileList.h"
+#include "clang/InstallAPI/HeaderFile.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang::installapi;
+
+static void testValidFileList(std::string Input, HeaderSeq &Expected) {
+  auto InputBuf = MemoryBuffer::getMemBuffer(Input);
+  HeaderSeq Headers;
+  llvm::Error Err = FileListReader::loadHeaders(std::move(InputBuf), Headers);
+  ASSERT_FALSE(Err);
+  EXPECT_EQ(Expected.size(), Headers.size());
+  EXPECT_EQ(Headers, Expected);
+}
+
+TEST(FileListReader, Version3) {
+  static const char Input[] = R"({
+    "version" : "3",
+    "headers" : [
+      {
+        "type" : "public",
+        "path" : "/tmp/dst/usr/include/foo.h",
+        "language" : "objective-c"
+      },
+      {
+        "type" : "private",
+        "path" : "/tmp/dst/usr/local/include/bar.h",
+        "language" : "objective-c++"
+      },
+      {
+        "type" : "project",
+        "path" : "/tmp/src/baz.h"
+      }
+    ]
+  })";
+
+  HeaderSeq Expected = {
+      {"/tmp/dst/usr/include/foo.h", HeaderType::Public, "foo.h",
+       clang::Language::ObjC},
+      {"/tmp/dst/usr/local/include/bar.h", HeaderType::Private, "bar.h",
+       clang::Language::ObjCXX},
+      {"/tmp/src/baz.h", HeaderType::Project, "", std::nullopt}};
+
+  testValidFileList(Input, Expected);
+}
+
+TEST(FileList, Version1) {
+  static const char Input[] = R"({
+    "version" : "1",
+    "headers" : [
+      {
+        "type" : "public",
+        "path" : "/usr/include/foo.h"
+      },
+      {
+        "type" : "private",
+        "path" : "/usr/local/include/bar.h"
+      }
+    ]
+  })";
+
+  HeaderSeq Expected = {
+      {"/usr/include/foo.h", HeaderType::Public, "foo.h", std::nullopt},
+      {"/usr/local/include/bar.h", HeaderType::Private, "bar.h", std::nullopt},
+  };
+
+  testValidFileList(Input, Expected);
+}
+
+TEST(FileListReader, Version2) {
+  static const auto Input = R"({
+    "version" : "2",
+    "headers" : [
+      {
+        "type" : "public",
+        "path" : "/usr/include/foo.h"
+      },
+      {
+        "type" : "project",
+        "path" : "src/bar.h"
+      }
+    ]
+  })";
+  HeaderSeq Expected = {
+      {"/usr/include/foo.h", HeaderType::Public, "foo.h", std::nullopt},
+      {"src/bar.h", HeaderType::Project, "", std::nullopt},
+  };
+
+  testValidFileList(Input, Expected);
+}
+
+TEST(FileList, MissingVersion) {
+  static const char Input[] = R"({
+    "headers" : [
+      {
+        "type" : "public",
+        "path" : "/usr/include/foo.h"
+      },
+      {
+        "type" : "private",
+        "path" : "/usr/local/include/bar.h"
+      }
+    ]
+  })";
+  auto InputBuf = MemoryBuffer::getMemBuffer(Input);
+  HeaderSeq Headers;
+  llvm::Error Err = FileListReader::loadHeaders(std::move(InputBuf), Headers);
+  EXPECT_FALSE(!Err);
+  EXPECT_STREQ("invalid input format: required field 'version' not specified\n",
+               toString(std::move(Err)).c_str());
+}
+
+TEST(FileList, InvalidTypes) {
+  static const char Input[] = R"({
+    "version" : "1",
+    "headers" : [
+      {
+        "type" : "project",
+        "path" : "/usr/include/foo.h"
+      }
+    ]
+  })";
+  auto InputBuf = MemoryBuffer::getMemBuffer(Input);
+  HeaderSeq Headers;
+  llvm::Error Err = FileListReader::loadHeaders(std::move(InputBuf), Headers);
+  EXPECT_FALSE(!Err);
+  EXPECT_STREQ("invalid input format: unsupported header type\n",
+               toString(std::move(Err)).c_str());
+}
diff --git a/clang/unittests/InstallAPI/HeaderFileTest.cpp b/clang/unittests/InstallAPI/HeaderFileTest.cpp
new file mode 100644
index 00000000000000..7c0b7c5db36892
--- /dev/null
+++ b/clang/unittests/InstallAPI/HeaderFileTest.cpp
@@ -0,0 +1,89 @@
+//===- unittests/InstallAPI/HeaderFile.cpp - HeaderFile Test --------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/InstallAPI/HeaderFile.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang::installapi;
+
+TEST(HeaderFile, FrameworkIncludes) {
+  const char *Path = "/System/Library/Frameworks/Foo.framework/Headers/Foo.h";
+  std::optional<std::string> IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo.h");
+
+  Path = "/System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/"
+         "Headers/SimpleBar.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "Bar/SimpleBar.h");
+
+  Path = "/tmp/Foo.framework/Versions/A/Headers/SimpleFoo.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "Foo/SimpleFoo.h");
+
+  Path = "/System/Library/PrivateFrameworks/Foo.framework/Headers/Foo.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo.h");
+
+  Path = "/AppleInternal/Developer/Library/Frameworks/"
+         "HelloFramework.framework/Headers/HelloFramework.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "HelloFramework/HelloFramework.h");
+
+  Path = "/tmp/BuildProducts/Foo.framework/Versions/A/"
+         "PrivateHeaders/Foo+Private.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo+Private.h");
+
+  Path = "/Applications/Xcode.app/Contents/Developer/SDKS/MacOS.sdk/System/"
+         "Library/Frameworks/Foo.framework/PrivateHeaders/Foo_Private.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo_Private.h");
+
+  Path =
+      "/System/Library/PrivateFrameworks/Foo.framework/PrivateHeaders/Foo.hpp";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo.hpp");
+
+  Path = "/Applications/Xcode.app/Contents/Developer/SDKS/MacOS.sdk/System/"
+         "Library/Frameworks/Foo.framework/Headers/BarDir/Bar.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "Foo/BarDir/Bar.h");
+}
+
+TEST(HeaderFile, DylibIncludes) {
+  const char *Path = "/usr/include/foo.h";
+  std::optional<std::string> IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "foo.h");
+
+  Path = "/tmp/BuildProducts/usr/include/a/A.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "a/A.h");
+
+  Path = "/Applications/Xcode.app/Contents/Developer/SDKS/MacOS.sdk/"
+         "usr/include/simd/types.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "simd/types.h");
+
+  Path = "/usr/local/include/hidden/A.h";
+  IncludeName = createIncludeHeaderName(Path);
+  EXPECT_TRUE(IncludeName.has_value());
+  EXPECT_STREQ(IncludeName.value().c_str(), "hidden/A.h");
+}

>From 88485b85c70f4d9bba2b8559e5479fe2a3e92480 Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Wed, 14 Feb 2024 16:11:32 -0800
Subject: [PATCH 2/3] Add missing scope identifer

---
 clang/lib/InstallAPI/HeaderFile.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/InstallAPI/HeaderFile.cpp b/clang/lib/InstallAPI/HeaderFile.cpp
index 9c42a2d1d3b44a..c2d8372741ee07 100644
--- a/clang/lib/InstallAPI/HeaderFile.cpp
+++ b/clang/lib/InstallAPI/HeaderFile.cpp
@@ -11,7 +11,7 @@
 using namespace llvm;
 namespace clang::installapi {
 
-llvm::Regex getFrameworkIncludeRule() {
+llvm::Regex HeaderFile::getFrameworkIncludeRule() {
   return llvm::Regex("/(.+)\\.framework/(.+)?Headers/(.+)");
 }
 
@@ -26,7 +26,7 @@ std::optional<std::string> createIncludeHeaderName(const StringRef FullPath) {
 
   // Framework Headers.
   SmallVector<StringRef, 4> Matches;
-  getFrameworkIncludeRule().match(FullPath, &Matches);
+  HeaderFile::getFrameworkIncludeRule().match(FullPath, &Matches);
   // Returned matches are always in stable order.
   if (Matches.size() != 4)
     return std::nullopt;

>From d34370745c2c4719b7592eb4d0359d4fc52de518 Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Wed, 14 Feb 2024 16:39:45 -0800
Subject: [PATCH 3/3] Cleanup Filelist Reader Interface

---
 clang/include/clang/InstallAPI/FileList.h | 13 +------
 clang/lib/InstallAPI/FileList.cpp         | 41 +++++++++--------------
 2 files changed, 17 insertions(+), 37 deletions(-)

diff --git a/clang/include/clang/InstallAPI/FileList.h b/clang/include/clang/InstallAPI/FileList.h
index 01ec9b35e31490..460af003b6a0ac 100644
--- a/clang/include/clang/InstallAPI/FileList.h
+++ b/clang/include/clang/InstallAPI/FileList.h
@@ -22,15 +22,7 @@
 namespace clang {
 namespace installapi {
 
-/// JSON decoder for InstallAPI Inputs.
 class FileListReader {
-
-  class Implementation;
-  Implementation &Impl;
-
-  FileListReader(std::unique_ptr<llvm::MemoryBuffer> InputBuffer,
-                 llvm::Error &Err);
-
 public:
   /// Decode JSON input and append header input into destination container.
   /// Headers are loaded in the order they appear in the JSON input.
@@ -41,10 +33,7 @@ class FileListReader {
   loadHeaders(std::unique_ptr<llvm::MemoryBuffer> InputBuffer,
               HeaderSeq &Destination);
 
-  ~FileListReader();
-
-  FileListReader(const FileListReader &) = delete;
-  FileListReader &operator=(const FileListReader &) = delete;
+  FileListReader() = delete;
 };
 
 } // namespace installapi
diff --git a/clang/lib/InstallAPI/FileList.cpp b/clang/lib/InstallAPI/FileList.cpp
index 6b307682346e2d..ae5a4a47c0bb55 100644
--- a/clang/lib/InstallAPI/FileList.cpp
+++ b/clang/lib/InstallAPI/FileList.cpp
@@ -39,7 +39,8 @@ using namespace llvm::json;
 using namespace llvm::MachO;
 using namespace clang::installapi;
 
-class FileListReader::Implementation {
+namespace {
+class Implementation {
 private:
   Expected<StringRef> parseString(const Object *Obj, StringRef Key,
                                   StringRef Error);
@@ -57,16 +58,14 @@ class FileListReader::Implementation {
 };
 
 Expected<StringRef>
-FileListReader::Implementation::parseString(const Object *Obj, StringRef Key,
-                                            StringRef Error) {
+Implementation::parseString(const Object *Obj, StringRef Key, StringRef Error) {
   auto Str = Obj->getString(Key);
   if (!Str)
     return make_error<StringError>(Error, inconvertibleErrorCode());
   return *Str;
 }
 
-Expected<HeaderType>
-FileListReader::Implementation::parseType(const Object *Obj) {
+Expected<HeaderType> Implementation::parseType(const Object *Obj) {
   auto TypeStr =
       parseString(Obj, "type", "required field 'type' not specified");
   if (!TypeStr)
@@ -83,8 +82,7 @@ FileListReader::Implementation::parseType(const Object *Obj) {
                                   "unsupported header type");
 }
 
-Expected<StringRef>
-FileListReader::Implementation::parsePath(const Object *Obj) {
+Expected<StringRef> Implementation::parsePath(const Object *Obj) {
   auto Path = parseString(Obj, "path", "required field 'path' not specified");
   if (!Path)
     return Path.takeError();
@@ -93,7 +91,7 @@ FileListReader::Implementation::parsePath(const Object *Obj) {
 }
 
 std::optional<clang::Language>
-FileListReader::Implementation::parseLanguage(const Object *Obj) {
+Implementation::parseLanguage(const Object *Obj) {
   auto Language = Obj->getString("language");
   if (!Language)
     return std::nullopt;
@@ -106,7 +104,7 @@ FileListReader::Implementation::parseLanguage(const Object *Obj) {
       .Default(clang::Language::Unknown);
 }
 
-Error FileListReader::Implementation::parseHeaders(Array &Headers) {
+Error Implementation::parseHeaders(Array &Headers) {
   for (const auto &H : Headers) {
     auto *Obj = H.getAsObject();
     if (!Obj)
@@ -135,7 +133,7 @@ Error FileListReader::Implementation::parseHeaders(Array &Headers) {
   return Error::success();
 }
 
-Error FileListReader::Implementation::parse(StringRef Input) {
+Error Implementation::parse(StringRef Input) {
   auto Val = json::parse(Input);
   if (!Val)
     return Val.takeError();
@@ -168,29 +166,22 @@ Error FileListReader::Implementation::parse(StringRef Input) {
 
   return Error::success();
 }
-
-FileListReader::FileListReader(std::unique_ptr<MemoryBuffer> InputBuffer,
-                               Error &Error)
-    : Impl(*new FileListReader::Implementation()) {
-  ErrorAsOutParameter ErrorAsOutParam(&Error);
-  Impl.InputBuffer = std::move(InputBuffer);
-
-  Error = Impl.parse(Impl.InputBuffer->getBuffer());
-}
+} // namespace
 
 llvm::Error
 FileListReader::loadHeaders(std::unique_ptr<MemoryBuffer> InputBuffer,
                             HeaderSeq &Destination) {
   llvm::Error Err = Error::success();
-  std::unique_ptr<FileListReader> Reader(
-      new FileListReader(std::move(InputBuffer), Err));
+  Implementation Impl;
+  ErrorAsOutParameter ErrorAsOutParam(&Err);
+  Impl.InputBuffer = std::move(InputBuffer);
+
+  Err = Impl.parse(Impl.InputBuffer->getBuffer());
   if (Err)
     return Err;
 
-  Destination.reserve(Destination.size() + Reader->Impl.HeaderList.size());
-  llvm::move(Reader->Impl.HeaderList, std::back_inserter(Destination));
+  Destination.reserve(Destination.size() + Impl.HeaderList.size());
+  llvm::move(Impl.HeaderList, std::back_inserter(Destination));
 
   return Err;
 }
-
-FileListReader::~FileListReader() { delete &Impl; }



More information about the cfe-commits mailing list