[clang] [InstallAPI] Add support for C++ headers (PR #84403)

Cyndy Ishida via cfe-commits cfe-commits at lists.llvm.org
Thu Mar 7 15:52:08 PST 2024


https://github.com/cyndyishida created https://github.com/llvm/llvm-project/pull/84403

This includes capturing symbols for global variables, functions, classes, and templated defintions. As pre-determing what symbols are generated from C++ declarations can be non-trivial, InstallAPI only parses select declarations for symbol generation when parsing c++.

For example, installapi only looks at explicit template instantiations or full template specializations, instead of general function or class templates, for symbol emittion.

>From 9068f8283df18ca8b3e39da67b9afa494847b6bf Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Mon, 4 Mar 2024 14:44:56 -0800
Subject: [PATCH] [InstallAPI] Add support for C++ headers

This includes capturing symbols for global variables, functions, classes, and templated defintions.
As pre-determing what symbols are generated from C++ declarations can be
non-trivial, InstallAPI only parses select declarations for symbol
generation when parsing c++.

For example, installapi only looks at explicit template instantiations
or full template specializations, instead of general function or class templates, for
symbol emittion.
---
 clang/include/clang/InstallAPI/Visitor.h |  14 +
 clang/lib/InstallAPI/Frontend.cpp        |   4 +-
 clang/lib/InstallAPI/Visitor.cpp         | 426 +++++++++++++++++-
 clang/test/InstallAPI/cpp.test           | 530 +++++++++++++++++++++++
 clang/tools/clang-installapi/Options.cpp |  33 +-
 clang/tools/clang-installapi/Options.h   |   7 +
 6 files changed, 1008 insertions(+), 6 deletions(-)
 create mode 100644 clang/test/InstallAPI/cpp.test

diff --git a/clang/include/clang/InstallAPI/Visitor.h b/clang/include/clang/InstallAPI/Visitor.h
index 71d4d9894f4205..9ac948ded3e332 100644
--- a/clang/include/clang/InstallAPI/Visitor.h
+++ b/clang/include/clang/InstallAPI/Visitor.h
@@ -21,6 +21,7 @@
 #include "llvm/ADT/Twine.h"
 
 namespace clang {
+struct AvailabilityInfo;
 namespace installapi {
 
 /// ASTVisitor for collecting declarations that represent global symbols.
@@ -33,6 +34,7 @@ class InstallAPIVisitor final : public ASTConsumer,
         MC(ItaniumMangleContext::create(ASTCtx, ASTCtx.getDiagnostics())),
         Layout(ASTCtx.getTargetInfo().getDataLayoutString()) {}
   void HandleTranslationUnit(ASTContext &ASTCtx) override;
+  bool shouldVisitTemplateInstantiations() const { return true; }
 
   /// Collect global variables.
   bool VisitVarDecl(const VarDecl *D);
@@ -51,9 +53,19 @@ class InstallAPIVisitor final : public ASTConsumer,
   /// is therefore itself not collected.
   bool VisitObjCCategoryDecl(const ObjCCategoryDecl *D);
 
+  /// Collect global c++ declarations.
+  bool VisitCXXRecordDecl(const CXXRecordDecl *D);
+
 private:
   std::string getMangledName(const NamedDecl *D) const;
   std::string getBackendMangledName(llvm::Twine Name) const;
+  std::string getMangledCXXVTableName(const CXXRecordDecl *D) const;
+  std::string getMangledCXXThunk(const GlobalDecl &D,
+                                 const ThunkInfo &Thunk) const;
+  std::string getMangledCXXRTTI(const CXXRecordDecl *D) const;
+  std::string getMangledCXXRTTIName(const CXXRecordDecl *D) const;
+  std::string getMangledCtorDtor(const CXXMethodDecl *D, int Type) const;
+
   std::optional<HeaderType> getAccessForDecl(const NamedDecl *D) const;
   void recordObjCInstanceVariables(
       const ASTContext &ASTCtx, llvm::MachO::ObjCContainerRecord *Record,
@@ -61,6 +73,8 @@ class InstallAPIVisitor final : public ASTConsumer,
       const llvm::iterator_range<
           DeclContext::specific_decl_iterator<ObjCIvarDecl>>
           Ivars);
+  void emitVTableSymbols(const CXXRecordDecl *D, const AvailabilityInfo &Avail,
+                         const HeaderType Access, bool EmittedVTable = false);
 
   InstallAPIContext &Ctx;
   SourceManager &SrcMgr;
diff --git a/clang/lib/InstallAPI/Frontend.cpp b/clang/lib/InstallAPI/Frontend.cpp
index 1edbdf5bb98360..0d526fe1da6667 100644
--- a/clang/lib/InstallAPI/Frontend.cpp
+++ b/clang/lib/InstallAPI/Frontend.cpp
@@ -137,9 +137,9 @@ std::unique_ptr<MemoryBuffer> createInputBuffer(InstallAPIContext &Ctx) {
     else
       OS << "#import ";
     if (H.useIncludeName())
-      OS << "<" << H.getIncludeName() << ">";
+      OS << "<" << H.getIncludeName() << ">\n";
     else
-      OS << "\"" << H.getPath() << "\"";
+      OS << "\"" << H.getPath() << "\"\n";
 
     Ctx.addKnownHeader(H);
   }
diff --git a/clang/lib/InstallAPI/Visitor.cpp b/clang/lib/InstallAPI/Visitor.cpp
index 1f2ef08e5aa252..aded94f7a94a32 100644
--- a/clang/lib/InstallAPI/Visitor.cpp
+++ b/clang/lib/InstallAPI/Visitor.cpp
@@ -7,7 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/InstallAPI/Visitor.h"
+#include "clang/AST/Availability.h"
 #include "clang/AST/ParentMapContext.h"
+#include "clang/AST/VTableBuilder.h"
 #include "clang/Basic/Linkage.h"
 #include "clang/InstallAPI/Frontend.h"
 #include "llvm/ADT/SmallString.h"
@@ -18,6 +20,15 @@
 using namespace llvm;
 using namespace llvm::MachO;
 
+namespace {
+enum class CXXLinkage {
+  ExternalLinkage,
+  LinkOnceODRLinkage,
+  WeakODRLinkage,
+  PrivateLinkage,
+};
+}
+
 namespace clang::installapi {
 
 // Exported NamedDecl needs to have external linkage and
@@ -53,7 +64,7 @@ static bool isInlined(const FunctionDecl *D) {
   return true;
 }
 
-static SymbolFlags getFlags(bool WeakDef, bool ThreadLocal) {
+static SymbolFlags getFlags(bool WeakDef, bool ThreadLocal = false) {
   SymbolFlags Result = SymbolFlags::None;
   if (WeakDef)
     Result |= SymbolFlags::WeakDefined;
@@ -277,8 +288,417 @@ bool InstallAPIVisitor::VisitFunctionDecl(const FunctionDecl *D) {
                                     ? RecordLinkage::Internal
                                     : RecordLinkage::Exported;
   Ctx.Slice->addGlobal(Name, Linkage, GlobalRecord::Kind::Function, Avail, D,
-                       *Access, getFlags(WeakDef, /*ThreadLocal=*/false),
-                       Inlined);
+                       *Access, getFlags(WeakDef), Inlined);
+  return true;
+}
+
+static bool hasVTable(const CXXRecordDecl *D) {
+  // Check if vtable symbols should be emitted, only dynamic classes need
+  // vtables.
+  if (!D->hasDefinition() || !D->isDynamicClass())
+    return false;
+
+  assert(D->isExternallyVisible() && "Should be externally visible");
+  assert(D->isCompleteDefinition() && "Only works on complete definitions");
+
+  const CXXMethodDecl *KeyFunctionD =
+      D->getASTContext().getCurrentKeyFunction(D);
+  // If this class has a key function, then there is a vtable, possibly internal
+  // though.
+  if (KeyFunctionD) {
+    switch (KeyFunctionD->getTemplateSpecializationKind()) {
+    case TSK_Undeclared:
+    case TSK_ExplicitSpecialization:
+    case TSK_ImplicitInstantiation:
+    case TSK_ExplicitInstantiationDefinition:
+      return true;
+    case TSK_ExplicitInstantiationDeclaration:
+      llvm_unreachable(
+          "Unexpected TemplateSpecializationKind for key function");
+    }
+  } else if (D->isAbstract()) {
+    // If the class is abstract and it doesn't have a key function, it is a
+    // 'pure' virtual class. It doesn't need a vtable.
+    return false;
+  }
+
+  switch (D->getTemplateSpecializationKind()) {
+  case TSK_Undeclared:
+  case TSK_ExplicitSpecialization:
+  case TSK_ImplicitInstantiation:
+    return false;
+
+  case TSK_ExplicitInstantiationDeclaration:
+  case TSK_ExplicitInstantiationDefinition:
+    return true;
+  }
+
+  llvm_unreachable("Invalid TemplateSpecializationKind!");
+}
+
+static CXXLinkage getVTableLinkage(const CXXRecordDecl *D) {
+  assert((D->hasDefinition() && D->isDynamicClass()) && "Record has no vtable");
+  assert(D->isExternallyVisible() && "Record should be externally visible");
+  if (D->getVisibility() == HiddenVisibility)
+    return CXXLinkage::PrivateLinkage;
+
+  const CXXMethodDecl *KeyFunctionD =
+      D->getASTContext().getCurrentKeyFunction(D);
+  if (KeyFunctionD) {
+    // If this class has a key function, use that to determine the
+    // linkage of the vtable.
+    switch (KeyFunctionD->getTemplateSpecializationKind()) {
+    case TSK_Undeclared:
+    case TSK_ExplicitSpecialization:
+      if (isInlined(KeyFunctionD))
+        return CXXLinkage::LinkOnceODRLinkage;
+      return CXXLinkage::ExternalLinkage;
+    case TSK_ImplicitInstantiation:
+      llvm_unreachable("No external vtable for implicit instantiations");
+    case TSK_ExplicitInstantiationDefinition:
+      return CXXLinkage::WeakODRLinkage;
+    case TSK_ExplicitInstantiationDeclaration:
+      llvm_unreachable(
+          "Unexpected TemplateSpecializationKind for key function");
+    }
+  }
+
+  switch (D->getTemplateSpecializationKind()) {
+  case TSK_Undeclared:
+  case TSK_ExplicitSpecialization:
+  case TSK_ImplicitInstantiation:
+    return CXXLinkage::LinkOnceODRLinkage;
+  case TSK_ExplicitInstantiationDeclaration:
+  case TSK_ExplicitInstantiationDefinition:
+    return CXXLinkage::WeakODRLinkage;
+  }
+
+  llvm_unreachable("Invalid TemplateSpecializationKind!");
+}
+
+static bool isRTTIWeakDef(const CXXRecordDecl *D) {
+  if (D->hasAttr<WeakAttr>())
+    return true;
+
+  if (D->isAbstract() && D->getASTContext().getCurrentKeyFunction(D) == nullptr)
+    return true;
+
+  if (D->isDynamicClass())
+    return getVTableLinkage(D) != CXXLinkage::ExternalLinkage;
+
+  return false;
+}
+
+static bool hasRTTI(const CXXRecordDecl *D) {
+  if (!D->getASTContext().getLangOpts().RTTI)
+    return false;
+
+  if (!D->hasDefinition())
+    return false;
+
+  if (!D->isDynamicClass())
+    return false;
+
+  // Don't emit weak-def RTTI information. InstallAPI cannot reliably determine
+  // if the final binary will have those weak defined RTTI symbols. This depends
+  // on the optimization level and if the class has been instantiated and used.
+  //
+  // Luckily, the Apple static linker doesn't need those weak defined RTTI
+  // symbols for linking. They are only needed by the runtime linker. That means
+  // they can be safely dropped.
+  if (isRTTIWeakDef(D))
+    return false;
+
+  return true;
+}
+
+std::string
+InstallAPIVisitor::getMangledCXXRTTIName(const CXXRecordDecl *D) const {
+  SmallString<256> Name;
+  raw_svector_ostream NameStream(Name);
+  MC->mangleCXXRTTIName(QualType(D->getTypeForDecl(), 0), NameStream);
+
+  return getBackendMangledName(Name);
+}
+
+std::string InstallAPIVisitor::getMangledCXXRTTI(const CXXRecordDecl *D) const {
+  SmallString<256> Name;
+  raw_svector_ostream NameStream(Name);
+  MC->mangleCXXRTTI(QualType(D->getTypeForDecl(), 0), NameStream);
+
+  return getBackendMangledName(Name);
+}
+
+std::string
+InstallAPIVisitor::getMangledCXXVTableName(const CXXRecordDecl *D) const {
+  SmallString<256> Name;
+  raw_svector_ostream NameStream(Name);
+  MC->mangleCXXVTable(D, NameStream);
+
+  return getBackendMangledName(Name);
+}
+
+std::string
+InstallAPIVisitor::getMangledCXXThunk(const GlobalDecl &D,
+                                      const ThunkInfo &Thunk) const {
+  SmallString<256> Name;
+  raw_svector_ostream NameStream(Name);
+  const auto *Method = cast<CXXMethodDecl>(D.getDecl());
+  if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(Method))
+    MC->mangleCXXDtorThunk(Dtor, D.getDtorType(), Thunk.This, NameStream);
+  else
+    MC->mangleThunk(Method, Thunk, NameStream);
+
+  return getBackendMangledName(Name);
+}
+
+std::string InstallAPIVisitor::getMangledCtorDtor(const CXXMethodDecl *D,
+                                                  int Type) const {
+  SmallString<256> Name;
+  raw_svector_ostream NameStream(Name);
+  GlobalDecl GD;
+  if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(D))
+    GD = GlobalDecl(Ctor, CXXCtorType(Type));
+  else {
+    const auto *Dtor = cast<CXXDestructorDecl>(D);
+    GD = GlobalDecl(Dtor, CXXDtorType(Type));
+  }
+  MC->mangleName(GD, NameStream);
+  return getBackendMangledName(Name);
+}
+
+void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D,
+                                          const AvailabilityInfo &Avail,
+                                          const HeaderType Access,
+                                          bool EmittedVTable) {
+  if (hasVTable(D)) {
+    EmittedVTable = true;
+    const CXXLinkage VTableLinkage = getVTableLinkage(D);
+    if (VTableLinkage == CXXLinkage::ExternalLinkage ||
+        VTableLinkage == CXXLinkage::WeakODRLinkage) {
+      const std::string Name = getMangledCXXVTableName(D);
+      const bool WeakDef = VTableLinkage == CXXLinkage::WeakODRLinkage;
+      Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                           GlobalRecord::Kind::Variable, Avail, D, Access,
+                           getFlags(WeakDef));
+      if (!D->getDescribedClassTemplate() && !D->isInvalidDecl()) {
+        VTableContextBase *VTable = D->getASTContext().getVTableContext();
+        auto AddThunk = [&](GlobalDecl GD) {
+          const ItaniumVTableContext::ThunkInfoVectorTy *Thunks =
+              VTable->getThunkInfo(GD);
+          if (!Thunks)
+            return;
+
+          for (const auto &Thunk : *Thunks) {
+            const std::string Name = getMangledCXXThunk(GD, Thunk);
+            Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                                 GlobalRecord::Kind::Function, Avail,
+                                 GD.getDecl(), Access);
+          }
+        };
+
+        for (const auto *Method : D->methods()) {
+          if (isa<CXXConstructorDecl>(Method) || !Method->isVirtual())
+            continue;
+
+          if (auto Dtor = dyn_cast<CXXDestructorDecl>(Method)) {
+            // Skip default destructor.
+            if (Dtor->isDefaulted())
+              continue;
+            AddThunk({Dtor, Dtor_Deleting});
+            AddThunk({Dtor, Dtor_Complete});
+          } else
+            AddThunk(Method);
+        }
+      }
+    }
+  }
+
+  if (!EmittedVTable)
+    return;
+
+  if (hasRTTI(D)) {
+    std::string Name = getMangledCXXRTTI(D);
+    Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                         GlobalRecord::Kind::Variable, Avail, D, Access);
+
+    Name = getMangledCXXRTTIName(D);
+    Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                         GlobalRecord::Kind::Variable, Avail, D, Access);
+  }
+
+  for (const auto &It : D->bases()) {
+    const CXXRecordDecl *Base =
+        cast<CXXRecordDecl>(It.getType()->castAs<RecordType>()->getDecl());
+    const auto BaseAccess = getAccessForDecl(Base);
+    if (!BaseAccess)
+      continue;
+    const AvailabilityInfo BaseAvail = AvailabilityInfo::createFromDecl(Base);
+    emitVTableSymbols(Base, BaseAvail, *BaseAccess, /*EmittedVTable=*/true);
+  }
+}
+
+bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) {
+  if (!D->isCompleteDefinition())
+    return true;
+
+  // Skip templated classes.
+  if (D->getDescribedClassTemplate() != nullptr)
+    return true;
+
+  // Skip partial templated classes too.
+  if (isa<ClassTemplatePartialSpecializationDecl>(D))
+    return true;
+
+  auto Access = getAccessForDecl(D);
+  if (!Access)
+    return true;
+  const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D);
+
+  // Check whether to emit the vtable/rtti symbols.
+  if (isExported(D))
+    emitVTableSymbols(D, Avail, *Access);
+
+  TemplateSpecializationKind ClassSK = TSK_Undeclared;
+  bool KeepInlineAsWeak = false;
+  if (auto *Templ = dyn_cast<ClassTemplateSpecializationDecl>(D)) {
+    ClassSK = Templ->getTemplateSpecializationKind();
+    if (ClassSK == TSK_ExplicitInstantiationDeclaration)
+      KeepInlineAsWeak = true;
+  }
+
+  // Record the class methods.
+  for (const auto *M : D->methods()) {
+    // Inlined methods are usually not emitted, except when it comes from a
+    // specialized template.
+    bool WeakDef = false;
+    if (isInlined(M)) {
+      if (!KeepInlineAsWeak)
+        continue;
+
+      WeakDef = true;
+    }
+
+    if (!isExported(M))
+      continue;
+
+    switch (M->getTemplateSpecializationKind()) {
+    case TSK_Undeclared:
+    case TSK_ExplicitSpecialization:
+      break;
+    case TSK_ImplicitInstantiation:
+      continue;
+    case TSK_ExplicitInstantiationDeclaration:
+      if (ClassSK == TSK_ExplicitInstantiationDeclaration)
+        WeakDef = true;
+      break;
+    case TSK_ExplicitInstantiationDefinition:
+      WeakDef = true;
+      break;
+    }
+
+    if (!M->isUserProvided())
+      continue;
+
+    // Methods that are deleted are not exported.
+    if (M->isDeleted())
+      continue;
+
+    const auto Access = getAccessForDecl(M);
+    if (!Access)
+      return true;
+    const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(M);
+
+    if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(M)) {
+      // Defaulted constructors are not exported.
+      if (Ctor->isDefaulted())
+        continue;
+
+      std::string Name = getMangledCtorDtor(M, Ctor_Base);
+      Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                           GlobalRecord::Kind::Function, Avail, D, *Access,
+                           getFlags(WeakDef));
+
+      if (!D->isAbstract()) {
+        std::string Name = getMangledCtorDtor(M, Ctor_Complete);
+        Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                             GlobalRecord::Kind::Function, Avail, D, *Access,
+                             getFlags(WeakDef));
+      }
+
+      continue;
+    }
+
+    if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(M)) {
+      // Defaulted destructors are not exported.
+      if (Dtor->isDefaulted())
+        continue;
+
+      std::string Name = getMangledCtorDtor(M, Dtor_Base);
+      Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                           GlobalRecord::Kind::Function, Avail, D, *Access,
+                           getFlags(WeakDef));
+
+      Name = getMangledCtorDtor(M, Dtor_Complete);
+      Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                           GlobalRecord::Kind::Function, Avail, D, *Access,
+                           getFlags(WeakDef));
+
+      if (Dtor->isVirtual()) {
+        Name = getMangledCtorDtor(M, Dtor_Deleting);
+        Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                             GlobalRecord::Kind::Function, Avail, D, *Access,
+                             getFlags(WeakDef));
+      }
+
+      continue;
+    }
+
+    // Though abstract methods can map to exports, this is generally unexpected.
+    // Except in the case of destructors. Only ignore pure virtuals after
+    // checking if the member function was a destructor.
+    if (M->isPureVirtual())
+      continue;
+
+    std::string Name = getMangledName(M);
+    Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                         GlobalRecord::Kind::Function, Avail, D, *Access,
+                         getFlags(WeakDef));
+  }
+
+  if (auto *Templ = dyn_cast<ClassTemplateSpecializationDecl>(D)) {
+    if (!Templ->isExplicitInstantiationOrSpecialization())
+      return true;
+  }
+
+  using var_iter = CXXRecordDecl::specific_decl_iterator<VarDecl>;
+  using var_range = iterator_range<var_iter>;
+  for (const auto *Var : var_range(D->decls())) {
+    // Skip const static member variables.
+    // \code
+    // struct S {
+    //   static const int x = 0;
+    // };
+    // \endcode
+    if (Var->isStaticDataMember() && Var->hasInit())
+      continue;
+
+    // Skip unexported var decls.
+    if (!isExported(Var))
+      continue;
+
+    const std::string Name = getMangledName(Var);
+    const auto Access = getAccessForDecl(Var);
+    if (!Access)
+      return true;
+    const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(Var);
+    const bool WeakDef = Var->hasAttr<WeakAttr>() || KeepInlineAsWeak;
+
+    Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
+                         GlobalRecord::Kind::Variable, Avail, D, *Access,
+                         getFlags(WeakDef));
+  }
+
   return true;
 }
 
diff --git a/clang/test/InstallAPI/cpp.test b/clang/test/InstallAPI/cpp.test
new file mode 100644
index 00000000000000..4817899095302b
--- /dev/null
+++ b/clang/test/InstallAPI/cpp.test
@@ -0,0 +1,530 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json
+
+// Invoke C++ with no-rtti.
+// RUN: clang-installapi -target arm64-apple-macos13.1 \
+// RUN: -I%t/usr/include -I%t/usr/local/include -x c++ \
+// RUN: -install_name @rpath/lib/libcpp.dylib -fno-rtti \
+// RUN: %t/inputs.json -o %t/no-rtti.tbd 2>&1 | FileCheck %s --allow-empty
+
+// RUN: llvm-readtapi -compare %t/no-rtti.tbd \
+// RUN: %t/expected-no-rtti.tbd 2>&1 | FileCheck %s --allow-empty
+
+// Invoke C++ with rtti.
+// RUN: clang-installapi -target arm64-apple-macos13.1 \
+// RUN: -I%t/usr/include -I%t/usr/local/include -x c++ \
+// RUN: -install_name @rpath/lib/libcpp.dylib -frtti \
+// RUN: %t/inputs.json -o %t/rtti.tbd 2>&1 | FileCheck %s --allow-empty
+// RUN: llvm-readtapi -compare %t/rtti.tbd \
+// RUN: %t/expected-rtti.tbd 2>&1 | FileCheck %s --allow-empty
+
+// CHECK-NOT: error: 
+// CHECK-NOT: warning: 
+
+//--- usr/include/basic.h
+#ifndef CPP_H
+#define CPP_H
+
+inline int foo(int x) { return x + 1; }
+
+extern int bar(int x) { return x + 1; }
+
+inline int baz(int x) {
+  static const int a[] = {1, 2, 3};
+  return a[x];
+}
+
+extern "C" {
+  int cFunc(const char*);
+}
+
+class Bar {
+public:
+  static const int x = 0;
+  static int y;
+
+  inline int func1(int x) { return x + 2; }
+  inline int func2(int x);
+  int func3(int x);
+};
+
+class __attribute__((visibility("hidden"))) BarI {
+  static const int x = 0;
+  static int y;
+
+  inline int func1(int x) { return x + 2; }
+  inline int func2(int x);
+  int func3(int x);
+};
+
+int Bar::func2(int x) { return x + 3; }
+inline int Bar::func3(int x) { return x + 4; }
+
+int BarI::func2(int x) { return x + 3; }
+inline int BarI::func3(int x) { return x + 4; }
+#endif
+
+//--- usr/local/include/vtable.h
+// Simple test class with no virtual functions. There should be no vtable or
+// RTTI.
+namespace test1 {
+class Simple {
+public:
+  void run();
+};
+} // end namespace test1
+
+// Simple test class with virtual function. There should be an external vtable
+// and RTTI.
+namespace test2 {
+class Simple {
+public:
+  virtual void run();
+};
+} // end namespace test2
+
+// Abstract class with no sub classes. There should be no vtable or RTTI.
+namespace test3 {
+class Abstract {
+public:
+  virtual ~Abstract() {}
+  virtual void run() = 0;
+};
+} // end namespace test3
+
+// Abstract base class with a sub class. There should be weak-def RTTI for the
+// abstract base class.
+// The sub-class should have vtable and RTTI.
+namespace test4 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run() = 0;
+};
+
+class Sub : public Base {
+public:
+  void run() override;
+};
+} // end namespace test4
+
+// Abstract base class with a sub class. Same as above, but with a user defined
+// inlined destructor.
+namespace test5 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run() = 0;
+};
+
+class Sub : public Base {
+public:
+  virtual ~Sub() {}
+  void run() override;
+};
+} // end namespace test5
+
+// Abstract base class with a sub class. Same as above, but with a different
+// inlined key method.
+namespace test6 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run() = 0;
+};
+
+class Sub : public Base {
+public:
+  virtual void foo() {}
+  void run() override;
+};
+} // end namespace test6
+
+// Abstract base class with a sub class. Overloaded method is implemented
+// inline. No vtable or RTTI.
+namespace test7 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual bool run() = 0;
+};
+
+class Sub : public Base {
+public:
+  bool run() override { return true; }
+};
+} // end namespace test7
+
+// Abstract base class with a sub class. Overloaded method has no inline
+// attribute and is recognized as key method,
+// but is later implemented inline. Weak-def RTTI only.
+namespace test8 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run() = 0;
+};
+
+class Sub : public Base {
+public:
+  void run() override;
+};
+
+inline void Sub::run() {}
+} // end namespace test8
+
+namespace test9 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run1() = 0;
+  virtual void run2() = 0;
+};
+
+class Sub : public Base {
+public:
+  void run1() override {}
+  void run2() override;
+};
+
+inline void Sub::run2() {}
+} // end namespace test9
+
+namespace test10 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run1() = 0;
+  virtual void run2() = 0;
+};
+
+class Sub : public Base {
+public:
+  void run1() override {}
+  inline void run2() override;
+};
+
+void Sub::run2() {}
+} // end namespace test10
+
+namespace test11 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run1() = 0;
+  virtual void run2() = 0;
+  virtual void run3() = 0;
+};
+
+class Sub : public Base {
+public:
+  void run1() override {}
+  void run2() override;
+  void run3() override;
+};
+
+inline void Sub::run2() {}
+} // end namespace test11
+
+namespace test12 {
+template <class T> class Simple {
+public:
+  virtual void foo() {}
+};
+extern template class Simple<int>;
+} // end namespace test12
+
+namespace test13 {
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run1() = 0;
+  virtual void run2() {};
+  virtual void run3(); // key function.
+};
+
+class Sub : public Base {
+public:
+  void run1() override {}
+  void run2() override {}
+};
+
+} // end namespace test13
+
+namespace test14 {
+
+class __attribute__((visibility("hidden"))) Base
+{
+public:
+    Base() {}
+    virtual ~Base(); // keyfunction.
+    virtual void run1() const = 0;
+};
+
+class Sub : public Base
+{
+public:
+    Sub();
+    virtual ~Sub();
+    virtual void run1() const;
+    void run2() const {}
+};
+
+} // end namespace test14
+
+namespace test15 {
+
+class Base {
+public:
+  virtual ~Base() {}
+  virtual void run() {};
+};
+
+class Base1 {
+public:
+  virtual ~Base1() {}
+  virtual void run1() {};
+};
+
+class Sub : public Base, public Base1 {
+public:
+  Sub() {}
+  ~Sub();
+  void run() override;
+  void run1() override;
+};
+
+class Sub1 : public Base, public Base1 {
+public:
+  Sub1() {}
+  ~Sub1() = default;
+  void run() override;
+  void run1() override;
+};
+
+} // end namespace test15
+
+//--- usr/local/include/templates.h
+#ifndef TEMPLATES_H
+#define TEMPLATES_H
+
+namespace templates {
+
+// Full specialization.
+template <class T> int foo1(T a) { return 1; }
+template <> int foo1<int>(int a);
+extern template int foo1<short>(short a);
+
+template <class T> int foo2(T a);
+
+// Partial specialization.
+template <class A, class B> class Partial {
+  static int run(A a, B b) { return a + b; }
+};
+
+template <class A> class Partial<A, int> {
+  static int run(A a, int b) { return a - b; }
+};
+
+template <class T> class Foo {
+public:
+  Foo();
+  ~Foo();
+};
+
+template <class T> class Bar {
+public:
+  Bar();
+  ~Bar() {}
+
+  inline int bazinga() { return 7; }
+};
+
+extern template class Bar<int>;
+
+class Bazz {
+public:
+  Bazz() {}
+
+  template <class T> int buzz(T a);
+
+  float implicit() const { return foo1(0.0f); }
+};
+
+template <class T> int Bazz::buzz(T a) { return sizeof(T); }
+
+template <class T> struct S { static int x; };
+
+template <class T> int S<T>::x = 0;
+
+} // end namespace templates.
+
+#endif
+
+
+//--- inputs.json.in
+{
+  "headers": [ {
+    "path" : "DSTROOT/usr/include/basic.h",
+    "type" : "public"
+  }, 
+  {
+    "path" : "DSTROOT/usr/local/include/vtable.h",
+    "type" : "private"
+  },
+  {
+    "path" : "DSTROOT/usr/local/include/templates.h",
+    "type" : "private"
+  }
+  ],
+  "version": "3"
+}
+
+//--- expected-no-rtti.tbd
+{
+  "main_library": {
+    "compatibility_versions": [
+      {
+        "version": "0"
+      }
+    ],
+    "current_versions": [
+      {
+        "version": "0"
+      }
+    ],
+    "exported_symbols": [
+      {
+        "data": {
+          "global": [
+            "__ZTVN6test143SubE", "__ZTVN6test113SubE", "__ZTVN5test26SimpleE",
+            "__ZTVN5test53SubE", "__ZTVN6test154Sub1E", "__ZTVN6test153SubE",
+            "__ZN3Bar1yE", "__ZTVN5test43SubE", "__ZTVN5test63SubE",
+            "__ZTVN6test134BaseE"
+          ],
+          "weak": [
+            "__ZTVN6test126SimpleIiEE"
+          ]
+        },
+        "text": {
+          "global": [
+            "__ZN6test153Sub3runEv", "__ZN6test154Sub13runEv",
+            "__Z3bari", "__ZThn8_N6test153SubD1Ev",
+            "__ZNK6test143Sub4run1Ev", "__ZN6test154Sub14run1Ev",
+            "__ZThn8_N6test153Sub4run1Ev", "__ZN6test143SubD1Ev",
+            "__ZN6test134Base4run3Ev", "__ZN5test16Simple3runEv",
+            "__ZN5test43Sub3runEv", "__ZN6test113Sub4run3Ev", "__ZN6test153SubD2Ev",
+            "__ZN5test53Sub3runEv", "__ZN6test153SubD1Ev", "__ZN6test143SubC1Ev",
+            "__ZN9templates4foo1IiEEiT_", "__ZN6test143SubC2Ev", "__ZN5test63Sub3runEv",
+            "__ZN5test26Simple3runEv", "__ZN6test153SubD0Ev",
+            "__ZN6test143SubD2Ev", "__ZN6test153Sub4run1Ev", "__ZN6test143SubD0Ev",
+            "__ZThn8_N6test153SubD0Ev", "__ZThn8_N6test154Sub14run1Ev", "_cFunc"
+          ],
+          "weak": [
+            "__ZN9templates3BarIiED2Ev", "__ZN9templates3BarIiEC2Ev",
+            "__ZN9templates3BarIiEC1Ev", "__ZN9templates3BarIiED1Ev",
+            "__ZN6test126SimpleIiE3fooEv", "__ZN9templates3BarIiE7bazingaEv",
+            "__ZN9templates4foo1IsEEiT_"
+          ]
+        }
+      }
+    ],
+    "flags": [
+      {
+        "attributes": [
+          "not_app_extension_safe"
+        ]
+      }
+    ],
+    "install_names": [
+      {
+        "name": "@rpath/lib/libcpp.dylib"
+      }
+    ],
+    "target_info": [
+      {
+        "min_deployment": "13.1",
+        "target": "arm64-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
+//--- expected-rtti.tbd
+{
+  "main_library": {
+    "compatibility_versions": [
+      {
+        "version": "0"
+      }
+    ],
+    "current_versions": [
+      {
+        "version": "0"
+      }
+    ],
+    "exported_symbols": [
+      {
+        "data": {
+          "global": [
+            "__ZTVN6test143SubE", "__ZTIN5test63SubE", "__ZTSN5test26SimpleE",
+            "__ZTIN6test153SubE", "__ZTVN6test113SubE", "__ZTIN5test43SubE",
+            "__ZTIN6test134BaseE", "__ZTVN5test26SimpleE", "__ZTIN5test26SimpleE",
+            "__ZTSN6test134BaseE", "__ZTVN6test154Sub1E", "__ZTVN5test43SubE",
+            "__ZTVN5test63SubE", "__ZTSN5test43SubE", "__ZTSN6test113SubE",
+            "__ZTIN6test154Sub1E", "__ZTSN6test153SubE", "__ZTSN5test63SubE",
+            "__ZTSN6test154Sub1E", "__ZTIN6test113SubE", "__ZTSN6test143SubE",
+            "__ZTVN5test53SubE", "__ZTIN6test143SubE", "__ZTVN6test153SubE",
+            "__ZTIN5test53SubE", "__ZN3Bar1yE", "__ZTVN6test134BaseE",
+            "__ZTSN5test53SubE"
+          ],
+          "weak": [
+            "__ZTVN6test126SimpleIiEE"
+          ]
+        },
+        "text": {
+          "global": [
+            "__ZN6test154Sub13runEv", "__ZN6test153Sub3runEv", "__ZNK6test143Sub4run1Ev",
+            "__ZN6test134Base4run3Ev", "__ZN5test16Simple3runEv", "__ZN6test153SubD2Ev",
+            "__ZN6test143SubC2Ev", "__ZN5test63Sub3runEv", "__ZN6test153SubD0Ev", 
+            "__ZN6test143SubD2Ev", "__ZThn8_N6test154Sub14run1Ev",
+            "__ZThn8_N6test153SubD0Ev", "__Z3bari", "__ZThn8_N6test153SubD1Ev",
+            "__ZN6test154Sub14run1Ev", "__ZThn8_N6test153Sub4run1Ev",
+            "__ZN6test143SubD1Ev", "__ZN5test43Sub3runEv",
+            "__ZN6test113Sub4run3Ev", "__ZN5test53Sub3runEv", "__ZN6test143SubC1Ev",
+            "__ZN6test153SubD1Ev", "__ZN9templates4foo1IiEEiT_", "__ZN5test26Simple3runEv",
+            "__ZN6test153Sub4run1Ev", "__ZN6test143SubD0Ev", "_cFunc"
+          ],
+          "weak": [
+            "__ZN9templates3BarIiEC2Ev", "__ZN9templates3BarIiEC1Ev",
+            "__ZN9templates3BarIiED1Ev", "__ZN6test126SimpleIiE3fooEv",
+            "__ZN9templates4foo1IsEEiT_", "__ZN9templates3BarIiED2Ev",
+            "__ZN9templates3BarIiE7bazingaEv"
+          ]
+        }
+      }
+    ],
+    "flags": [
+      {
+        "attributes": [
+          "not_app_extension_safe"
+        ]
+      }
+    ],
+    "install_names": [
+      {
+        "name": "@rpath/lib/libcpp.dylib"
+      }
+    ],
+    "target_info": [
+      {
+        "min_deployment": "13.1",
+        "target": "arm64-macos"
+      }
+    ]
+  },
+  "tapi_tbd_version": 5
+}
+
diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp
index b9c36eab2ad3b7..701ab81c57c3d0 100644
--- a/clang/tools/clang-installapi/Options.cpp
+++ b/clang/tools/clang-installapi/Options.cpp
@@ -99,6 +99,33 @@ bool Options::processLinkerOptions(InputArgList &Args) {
   return true;
 }
 
+bool Options::processFrontendOptions(InputArgList &Args) {
+  // Do not claim any arguments, as they will be passed along for CC1
+  // invocations.
+  if (auto *A = Args.getLastArgNoClaim(OPT_x)) {
+    FEOpts.LangMode = llvm::StringSwitch<clang::Language>(A->getValue())
+                          .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);
+
+    if (FEOpts.LangMode == clang::Language::Unknown) {
+      Diags->Report(clang::diag::err_drv_invalid_value)
+          << A->getAsString(Args) << A->getValue();
+      return false;
+    }
+  }
+  for (auto *A : Args.filtered(OPT_ObjC, OPT_ObjCXX)) {
+    if (A->getOption().matches(OPT_ObjC))
+      FEOpts.LangMode = clang::Language::ObjC;
+    else
+      FEOpts.LangMode = clang::Language::ObjCXX;
+  }
+
+  return true;
+}
+
 Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
                  InputArgList &ArgList)
     : Diags(&Diag), FM(FM) {
@@ -108,7 +135,10 @@ Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
   if (!processLinkerOptions(ArgList))
     return;
 
-  /// Any remaining arguments should be handled by invoking the clang frontend.
+  if (!processFrontendOptions(ArgList))
+    return;
+
+  /// Any unclaimed arguments should be handled by invoking the clang frontend.
   for (const Arg *A : ArgList) {
     if (A->isClaimed())
       continue;
@@ -132,6 +162,7 @@ InstallAPIContext Options::createContext() {
   Ctx.BA.AppExtensionSafe = LinkerOpts.AppExtensionSafe;
   Ctx.FT = DriverOpts.OutFT;
   Ctx.OutputLoc = DriverOpts.OutputPath;
+  Ctx.LangMode = FEOpts.LangMode;
 
   // Process inputs.
   for (const std::string &ListPath : DriverOpts.FileLists) {
diff --git a/clang/tools/clang-installapi/Options.h b/clang/tools/clang-installapi/Options.h
index f68addf1972880..9d4d841284fd11 100644
--- a/clang/tools/clang-installapi/Options.h
+++ b/clang/tools/clang-installapi/Options.h
@@ -62,15 +62,22 @@ struct LinkerOptions {
   bool IsDylib = false;
 };
 
+struct FrontendOptions {
+  /// \brief The language mode to parse headers in.
+  Language LangMode = Language::ObjC;
+};
+
 class Options {
 private:
   bool processDriverOptions(llvm::opt::InputArgList &Args);
   bool processLinkerOptions(llvm::opt::InputArgList &Args);
+  bool processFrontendOptions(llvm::opt::InputArgList &Args);
 
 public:
   /// The various options grouped together.
   DriverOptions DriverOpts;
   LinkerOptions LinkerOpts;
+  FrontendOptions FEOpts;
 
   Options() = delete;
 



More information about the cfe-commits mailing list