[clang] [InstallAPI] Add support for C++ headers (PR #84403)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 7 15:52:40 PST 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Cyndy Ishida (cyndyishida)
<details>
<summary>Changes</summary>
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.
---
Patch is 33.40 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/84403.diff
6 Files Affected:
- (modified) clang/include/clang/InstallAPI/Visitor.h (+14)
- (modified) clang/lib/InstallAPI/Frontend.cpp (+2-2)
- (modified) clang/lib/InstallAPI/Visitor.cpp (+423-3)
- (added) clang/test/InstallAPI/cpp.test (+530)
- (modified) clang/tools/clang-installapi/Options.cpp (+32-1)
- (modified) clang/tools/clang-installapi/Options.h (+7)
``````````diff
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/inclu...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/84403
More information about the cfe-commits
mailing list