10 changes: 8 additions & 2 deletions clang/lib/Basic/DiagnosticIDs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct StaticDiagInfoDescriptionStringTable {
#include "clang/Basic/DiagnosticSemaKinds.inc"
#include "clang/Basic/DiagnosticAnalysisKinds.inc"
#include "clang/Basic/DiagnosticRefactoringKinds.inc"
#include "clang/Basic/DiagnosticInstallAPIKinds.inc"
// clang-format on
#undef DIAG
};
Expand All @@ -70,7 +71,8 @@ const StaticDiagInfoDescriptionStringTable StaticDiagInfoDescriptions = {
#include "clang/Basic/DiagnosticSemaKinds.inc"
#include "clang/Basic/DiagnosticAnalysisKinds.inc"
#include "clang/Basic/DiagnosticRefactoringKinds.inc"
// clang-format on
#include "clang/Basic/DiagnosticInstallAPIKinds.inc"
// clang-format on
#undef DIAG
};

Expand All @@ -95,7 +97,8 @@ const uint32_t StaticDiagInfoDescriptionOffsets[] = {
#include "clang/Basic/DiagnosticSemaKinds.inc"
#include "clang/Basic/DiagnosticAnalysisKinds.inc"
#include "clang/Basic/DiagnosticRefactoringKinds.inc"
// clang-format on
#include "clang/Basic/DiagnosticInstallAPIKinds.inc"
// clang-format on
#undef DIAG
};

Expand Down Expand Up @@ -173,6 +176,7 @@ VALIDATE_DIAG_SIZE(CROSSTU)
VALIDATE_DIAG_SIZE(SEMA)
VALIDATE_DIAG_SIZE(ANALYSIS)
VALIDATE_DIAG_SIZE(REFACTORING)
VALIDATE_DIAG_SIZE(INSTALLAPI)
#undef VALIDATE_DIAG_SIZE
#undef STRINGIFY_NAME

Expand Down Expand Up @@ -204,6 +208,7 @@ const StaticDiagInfoRec StaticDiagInfo[] = {
#include "clang/Basic/DiagnosticSemaKinds.inc"
#include "clang/Basic/DiagnosticAnalysisKinds.inc"
#include "clang/Basic/DiagnosticRefactoringKinds.inc"
#include "clang/Basic/DiagnosticInstallAPIKinds.inc"
// clang-format on
#undef DIAG
};
Expand Down Expand Up @@ -246,6 +251,7 @@ CATEGORY(CROSSTU, COMMENT)
CATEGORY(SEMA, CROSSTU)
CATEGORY(ANALYSIS, SEMA)
CATEGORY(REFACTORING, ANALYSIS)
CATEGORY(INSTALLAPI, REFACTORING)
#undef CATEGORY

// Avoid out of bounds reads.
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/InstallAPI/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
set(LLVM_LINK_COMPONENTS
Support
TextAPI
Demangle
Core
)

add_clang_library(clangInstallAPI
DylibVerifier.cpp
FileList.cpp
Frontend.cpp
HeaderFile.cpp
Expand Down
212 changes: 212 additions & 0 deletions clang/lib/InstallAPI/DylibVerifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#include "clang/InstallAPI/DylibVerifier.h"
#include "clang/InstallAPI/FrontendRecords.h"
#include "llvm/Demangle/Demangle.h"

using namespace llvm::MachO;

namespace clang {
namespace installapi {

/// Metadata stored about a mapping of a declaration to a symbol.
struct DylibVerifier::SymbolContext {
// Name to use for printing in diagnostics.
std::string PrettyPrintName{""};

// Name to use for all querying and verification
// purposes.
std::string SymbolName{""};

// Kind to map symbol type against record.
EncodeKind Kind = EncodeKind::GlobalSymbol;

// Frontend Attributes tied to the AST.
const FrontendAttrs *FA = nullptr;

// The ObjCInterface symbol type, if applicable.
ObjCIFSymbolKind ObjCIFKind = ObjCIFSymbolKind::None;
};

static std::string
getAnnotatedName(const Record *R, EncodeKind Kind, StringRef Name,
bool ValidSourceLoc = true,
ObjCIFSymbolKind ObjCIF = ObjCIFSymbolKind::None) {
assert(!Name.empty() && "Need symbol name for printing");

std::string Annotation;
if (R->isWeakDefined())
Annotation += "(weak-def) ";
if (R->isWeakReferenced())
Annotation += "(weak-ref) ";
if (R->isThreadLocalValue())
Annotation += "(tlv) ";

// Check if symbol represents only part of a @interface declaration.
const bool IsAnnotatedObjCClass = ((ObjCIF != ObjCIFSymbolKind::None) &&
(ObjCIF <= ObjCIFSymbolKind::EHType));

if (IsAnnotatedObjCClass) {
if (ObjCIF == ObjCIFSymbolKind::EHType)
Annotation += "Exception Type of ";
if (ObjCIF == ObjCIFSymbolKind::MetaClass)
Annotation += "Metaclass of ";
if (ObjCIF == ObjCIFSymbolKind::Class)
Annotation += "Class of ";
}

// Only print symbol type prefix or leading "_" if there is no source location
// tied to it. This can only ever happen when the location has to come from
// debug info.
if (ValidSourceLoc) {
if ((Kind == EncodeKind::GlobalSymbol) && Name.starts_with("_"))
return Annotation + Name.drop_front(1).str();
return Annotation + Name.str();
}

if (IsAnnotatedObjCClass)
return Annotation + Name.str();

switch (Kind) {
case EncodeKind::GlobalSymbol:
return Annotation + Name.str();
case EncodeKind::ObjectiveCInstanceVariable:
return Annotation + "(ObjC IVar) " + Name.str();
case EncodeKind::ObjectiveCClass:
return Annotation + "(ObjC Class) " + Name.str();
case EncodeKind::ObjectiveCClassEHType:
return Annotation + "(ObjC Class EH) " + Name.str();
}

llvm_unreachable("unexpected case for EncodeKind");
}

static std::string demangle(StringRef Name) {
// Itanium encoding requires 1 or 3 leading underscores, followed by 'Z'.
if (!(Name.starts_with("_Z") || Name.starts_with("___Z")))
return Name.str();
char *Result = llvm::itaniumDemangle(Name.data());
if (!Result)
return Name.str();

std::string Demangled(Result);
free(Result);
return Demangled;
}

static DylibVerifier::Result updateResult(const DylibVerifier::Result Prev,
const DylibVerifier::Result Curr) {
if (Prev == Curr)
return Prev;

// Never update from invalid or noverify state.
if ((Prev == DylibVerifier::Result::Invalid) ||
(Prev == DylibVerifier::Result::NoVerify))
return Prev;

// Don't let an ignored verification remove a valid one.
if (Prev == DylibVerifier::Result::Valid &&
Curr == DylibVerifier::Result::Ignore)
return Prev;

return Curr;
}

void DylibVerifier::updateState(Result State) {
Ctx.FrontendState = updateResult(Ctx.FrontendState, State);
}

void DylibVerifier::addSymbol(const Record *R, SymbolContext &SymCtx,
TargetList &&Targets) {
if (Targets.empty())
Targets = {Ctx.Target};

Exports->addGlobal(SymCtx.Kind, SymCtx.SymbolName, R->getFlags(), Targets);
}

DylibVerifier::Result DylibVerifier::verifyImpl(Record *R,
SymbolContext &SymCtx) {
R->setVerify();
if (!canVerify()) {
// Accumulate symbols when not in verifying against dylib.
if (R->isExported() && !SymCtx.FA->Avail.isUnconditionallyUnavailable() &&
!SymCtx.FA->Avail.isObsoleted()) {
addSymbol(R, SymCtx);
}
return Ctx.FrontendState;
}
return Ctx.FrontendState;
}

bool DylibVerifier::canVerify() {
return Ctx.FrontendState != Result::NoVerify;
}

void DylibVerifier::setTarget(const Target &T) {
Ctx.Target = T;
Ctx.DiscoveredFirstError = false;
updateState(Dylib.empty() ? Result::NoVerify : Result::Ignore);
}

DylibVerifier::Result DylibVerifier::verify(ObjCIVarRecord *R,
const FrontendAttrs *FA,
const StringRef SuperClass) {
if (R->isVerified())
return getState();

std::string FullName =
ObjCIVarRecord::createScopedName(SuperClass, R->getName());
SymbolContext SymCtx{
getAnnotatedName(R, EncodeKind::ObjectiveCInstanceVariable,
Demangle ? demangle(FullName) : FullName),
FullName, EncodeKind::ObjectiveCInstanceVariable, FA};
return verifyImpl(R, SymCtx);
}

static ObjCIFSymbolKind assignObjCIFSymbolKind(const ObjCInterfaceRecord *R) {
ObjCIFSymbolKind Result = ObjCIFSymbolKind::None;
if (R->getLinkageForSymbol(ObjCIFSymbolKind::Class) != RecordLinkage::Unknown)
Result |= ObjCIFSymbolKind::Class;
if (R->getLinkageForSymbol(ObjCIFSymbolKind::MetaClass) !=
RecordLinkage::Unknown)
Result |= ObjCIFSymbolKind::MetaClass;
if (R->getLinkageForSymbol(ObjCIFSymbolKind::EHType) !=
RecordLinkage::Unknown)
Result |= ObjCIFSymbolKind::EHType;
return Result;
}

DylibVerifier::Result DylibVerifier::verify(ObjCInterfaceRecord *R,
const FrontendAttrs *FA) {
if (R->isVerified())
return getState();
SymbolContext SymCtx;
SymCtx.SymbolName = R->getName();
SymCtx.ObjCIFKind = assignObjCIFSymbolKind(R);

std::string DisplayName =
Demangle ? demangle(SymCtx.SymbolName) : SymCtx.SymbolName;
SymCtx.Kind = R->hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType
: EncodeKind::ObjectiveCClass;
SymCtx.PrettyPrintName = getAnnotatedName(R, SymCtx.Kind, DisplayName);
SymCtx.FA = FA;

return verifyImpl(R, SymCtx);
}

DylibVerifier::Result DylibVerifier::verify(GlobalRecord *R,
const FrontendAttrs *FA) {
if (R->isVerified())
return getState();

// Global classifications could be obfusciated with `asm`.
SimpleSymbol Sym = parseSymbol(R->getName());
SymbolContext SymCtx;
SymCtx.SymbolName = Sym.Name;
SymCtx.PrettyPrintName =
getAnnotatedName(R, Sym.Kind, Demangle ? demangle(Sym.Name) : Sym.Name);
SymCtx.Kind = Sym.Kind;
SymCtx.FA = FA;
return verifyImpl(R, SymCtx);
}

} // namespace installapi
} // namespace clang
49 changes: 28 additions & 21 deletions clang/lib/InstallAPI/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,47 @@ using namespace llvm;
using namespace llvm::MachO;

namespace clang::installapi {

GlobalRecord *FrontendRecordsSlice::addGlobal(
std::pair<GlobalRecord *, FrontendAttrs *> FrontendRecordsSlice::addGlobal(
StringRef Name, RecordLinkage Linkage, GlobalRecord::Kind GV,
const clang::AvailabilityInfo Avail, const Decl *D, const HeaderType Access,
SymbolFlags Flags, bool Inlined) {

auto *GR =
GlobalRecord *GR =
llvm::MachO::RecordsSlice::addGlobal(Name, Linkage, GV, Flags, Inlined);
FrontendRecords.insert({GR, FrontendAttrs{Avail, D, Access}});
return GR;
auto Result = FrontendRecords.insert({GR, FrontendAttrs{Avail, D, Access}});
return {GR, &(Result.first->second)};
}

ObjCInterfaceRecord *FrontendRecordsSlice::addObjCInterface(
StringRef Name, RecordLinkage Linkage, const clang::AvailabilityInfo Avail,
const Decl *D, HeaderType Access, bool IsEHType) {
std::pair<ObjCInterfaceRecord *, FrontendAttrs *>
FrontendRecordsSlice::addObjCInterface(StringRef Name, RecordLinkage Linkage,
const clang::AvailabilityInfo Avail,
const Decl *D, HeaderType Access,
bool IsEHType) {
ObjCIFSymbolKind SymType =
ObjCIFSymbolKind::Class | ObjCIFSymbolKind::MetaClass;
if (IsEHType)
SymType |= ObjCIFSymbolKind::EHType;
auto *ObjCR =

ObjCInterfaceRecord *ObjCR =
llvm::MachO::RecordsSlice::addObjCInterface(Name, Linkage, SymType);
FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}});
return ObjCR;
auto Result =
FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}});
return {ObjCR, &(Result.first->second)};
}

ObjCCategoryRecord *FrontendRecordsSlice::addObjCCategory(
StringRef ClassToExtend, StringRef CategoryName,
const clang::AvailabilityInfo Avail, const Decl *D, HeaderType Access) {
auto *ObjCR =
std::pair<ObjCCategoryRecord *, FrontendAttrs *>
FrontendRecordsSlice::addObjCCategory(StringRef ClassToExtend,
StringRef CategoryName,
const clang::AvailabilityInfo Avail,
const Decl *D, HeaderType Access) {
ObjCCategoryRecord *ObjCR =
llvm::MachO::RecordsSlice::addObjCCategory(ClassToExtend, CategoryName);
FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}});
return ObjCR;
auto Result =
FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}});
return {ObjCR, &(Result.first->second)};
}

ObjCIVarRecord *FrontendRecordsSlice::addObjCIVar(
std::pair<ObjCIVarRecord *, FrontendAttrs *> FrontendRecordsSlice::addObjCIVar(
ObjCContainerRecord *Container, StringRef IvarName, RecordLinkage Linkage,
const clang::AvailabilityInfo Avail, const Decl *D, HeaderType Access,
const clang::ObjCIvarDecl::AccessControl AC) {
Expand All @@ -59,11 +65,12 @@ ObjCIVarRecord *FrontendRecordsSlice::addObjCIVar(
if ((Linkage == RecordLinkage::Exported) &&
((AC == ObjCIvarDecl::Private) || (AC == ObjCIvarDecl::Package)))
Linkage = RecordLinkage::Internal;
auto *ObjCR =
ObjCIVarRecord *ObjCR =
llvm::MachO::RecordsSlice::addObjCIVar(Container, IvarName, Linkage);
FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}});
auto Result =
FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}});

return nullptr;
return {ObjCR, &(Result.first->second)};
}

std::optional<HeaderType>
Expand Down
101 changes: 61 additions & 40 deletions clang/lib/InstallAPI/Visitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "clang/AST/ParentMapContext.h"
#include "clang/AST/VTableBuilder.h"
#include "clang/Basic/Linkage.h"
#include "clang/InstallAPI/DylibVerifier.h"
#include "clang/InstallAPI/FrontendRecords.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
Expand Down Expand Up @@ -156,7 +157,9 @@ void InstallAPIVisitor::recordObjCInstanceVariables(
StringRef Name = IV->getName();
const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(IV);
auto AC = IV->getCanonicalAccessControl();
Ctx.Slice->addObjCIVar(Record, Name, Linkage, Avail, IV, *Access, AC);
auto [ObjCIVR, FA] =
Ctx.Slice->addObjCIVar(Record, Name, Linkage, Avail, IV, *Access, AC);
Ctx.Verifier->verify(ObjCIVR, FA, SuperClass);
}
}

Expand All @@ -178,15 +181,16 @@ bool InstallAPIVisitor::VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D) {
(!D->getASTContext().getLangOpts().ObjCRuntime.isFragile() &&
hasObjCExceptionAttribute(D));

ObjCInterfaceRecord *Class =
auto [Class, FA] =
Ctx.Slice->addObjCInterface(Name, Linkage, Avail, D, *Access, IsEHType);
Ctx.Verifier->verify(Class, FA);

// Get base class.
StringRef SuperClassName;
if (const auto *SuperClass = D->getSuperClass())
SuperClassName = SuperClass->getObjCRuntimeNameAsString();

recordObjCInstanceVariables(D->getASTContext(), Class, SuperClassName,
recordObjCInstanceVariables(D->getASTContext(), Class, Class->getName(),
D->ivars());
return true;
}
Expand All @@ -201,8 +205,8 @@ bool InstallAPIVisitor::VisitObjCCategoryDecl(const ObjCCategoryDecl *D) {
const ObjCInterfaceDecl *InterfaceD = D->getClassInterface();
const StringRef InterfaceName = InterfaceD->getName();

ObjCCategoryRecord *Category = Ctx.Slice->addObjCCategory(
InterfaceName, CategoryName, Avail, D, *Access);
auto [Category, FA] = Ctx.Slice->addObjCCategory(InterfaceName, CategoryName,
Avail, D, *Access);
recordObjCInstanceVariables(D->getASTContext(), Category, InterfaceName,
D->ivars());
return true;
Expand Down Expand Up @@ -236,8 +240,10 @@ bool InstallAPIVisitor::VisitVarDecl(const VarDecl *D) {
const bool WeakDef = D->hasAttr<WeakAttr>();
const bool ThreadLocal = D->getTLSKind() != VarDecl::TLS_None;
const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D);
Ctx.Slice->addGlobal(getMangledName(D), Linkage, GlobalRecord::Kind::Variable,
Avail, D, *Access, getFlags(WeakDef, ThreadLocal));
auto [GR, FA] = Ctx.Slice->addGlobal(getMangledName(D), Linkage,
GlobalRecord::Kind::Variable, Avail, D,
*Access, getFlags(WeakDef, ThreadLocal));
Ctx.Verifier->verify(GR, FA);
return true;
}

Expand Down Expand Up @@ -287,8 +293,10 @@ bool InstallAPIVisitor::VisitFunctionDecl(const FunctionDecl *D) {
const RecordLinkage Linkage = (Inlined || !isExported(D))
? RecordLinkage::Internal
: RecordLinkage::Exported;
Ctx.Slice->addGlobal(Name, Linkage, GlobalRecord::Kind::Function, Avail, D,
*Access, getFlags(WeakDef), Inlined);
auto [GR, FA] =
Ctx.Slice->addGlobal(Name, Linkage, GlobalRecord::Kind::Function, Avail,
D, *Access, getFlags(WeakDef), Inlined);
Ctx.Verifier->verify(GR, FA);
return true;
}

Expand Down Expand Up @@ -478,9 +486,10 @@ void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D,
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));
auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Variable, Avail,
D, Access, getFlags(WeakDef));
Ctx.Verifier->verify(GR, FA);
if (!D->getDescribedClassTemplate() && !D->isInvalidDecl()) {
VTableContextBase *VTable = D->getASTContext().getVTableContext();
auto AddThunk = [&](GlobalDecl GD) {
Expand All @@ -491,9 +500,10 @@ void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D,

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);
auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function,
Avail, GD.getDecl(), Access);
Ctx.Verifier->verify(GR, FA);
}
};

Expand All @@ -519,12 +529,16 @@ void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D,

if (hasRTTI(D)) {
std::string Name = getMangledCXXRTTI(D);
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Variable, Avail, D, Access);
auto [GR, FA] =
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Variable, Avail, D, Access);
Ctx.Verifier->verify(GR, FA);

Name = getMangledCXXRTTIName(D);
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Variable, Avail, D, Access);
auto [NamedGR, NamedFA] =
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Variable, Avail, D, Access);
Ctx.Verifier->verify(NamedGR, NamedFA);
}

for (const auto &It : D->bases()) {
Expand Down Expand Up @@ -615,15 +629,17 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) {
continue;

std::string Name = getMangledCtorDtor(M, Ctor_Base);
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail, D, *Access,
getFlags(WeakDef));
auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail,
D, *Access, getFlags(WeakDef));
Ctx.Verifier->verify(GR, FA);

if (!D->isAbstract()) {
std::string Name = getMangledCtorDtor(M, Ctor_Complete);
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail, D, *Access,
getFlags(WeakDef));
auto [GR, FA] = Ctx.Slice->addGlobal(
Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail,
D, *Access, getFlags(WeakDef));
Ctx.Verifier->verify(GR, FA);
}

continue;
Expand All @@ -635,20 +651,23 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) {
continue;

std::string Name = getMangledCtorDtor(M, Dtor_Base);
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail, D, *Access,
getFlags(WeakDef));
auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail,
D, *Access, getFlags(WeakDef));
Ctx.Verifier->verify(GR, FA);

Name = getMangledCtorDtor(M, Dtor_Complete);
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail, D, *Access,
getFlags(WeakDef));
auto [CompleteGR, CompleteFA] = Ctx.Slice->addGlobal(
Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, D,
*Access, getFlags(WeakDef));
Ctx.Verifier->verify(CompleteGR, CompleteFA);

if (Dtor->isVirtual()) {
Name = getMangledCtorDtor(M, Dtor_Deleting);
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail, D, *Access,
getFlags(WeakDef));
auto [VirtualGR, VirtualFA] = Ctx.Slice->addGlobal(
Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail,
D, *Access, getFlags(WeakDef));
Ctx.Verifier->verify(VirtualGR, VirtualFA);
}

continue;
Expand All @@ -661,9 +680,10 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) {
continue;

std::string Name = getMangledName(M);
Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail, D, *Access,
getFlags(WeakDef));
auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail, D,
*Access, getFlags(WeakDef));
Ctx.Verifier->verify(GR, FA);
}

if (auto *Templ = dyn_cast<ClassTemplateSpecializationDecl>(D)) {
Expand Down Expand Up @@ -694,9 +714,10 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) {
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));
auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Variable, Avail, D,
*Access, getFlags(WeakDef));
Ctx.Verifier->verify(GR, FA);
}

return true;
Expand Down
90 changes: 90 additions & 0 deletions clang/test/InstallAPI/asm.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
// RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json

// RUN: clang-installapi -target arm64-apple-macos13.1 \
// RUN: -I%t/usr/include \
// RUN: -install_name @rpath/lib/libasm.dylib \
// RUN: %t/inputs.json -o %t/output.tbd 2>&1 | FileCheck %s --allow-empty
// RUN: llvm-readtapi -compare %t/output.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty

// CHECK-NOT: error:
// CHECK-NOT: warning:

//--- usr/include/asm.h
#ifndef ASM_H
#define ASM_H

extern int ivar __asm("_OBJC_IVAR_$_SomeClass._ivar1");
extern int objcClass1 __asm("_OBJC_CLASS_$_SomeClass");
extern int objcClass2 __asm("_OBJC_METACLASS_$_SomeClass");
extern int objcClass3 __asm("_OBJC_EHTYPE_$_SomeClass");
extern int objcClass4 __asm(".objc_class_name_SomeClass");

__attribute__((visibility("hidden")))
@interface NSString {
}
@end

extern int ivarExtra __asm("_OBJC_IVAR_$_NSString._ivar1");
#endif // ASM_H

//--- inputs.json.in
{
"headers": [ {
"path" : "DSTROOT/usr/include/asm.h",
"type" : "public"
}],
"version": "3"
}

//--- expected.tbd
{
"main_library": {
"compatibility_versions": [
{
"version": "0"
}
],
"current_versions": [
{
"version": "0"
}
],
"exported_symbols": [
{
"data": {
"objc_class": [
"SomeClass"
],
"objc_eh_type": [
"SomeClass"
],
"objc_ivar": [
"NSString._ivar1",
"SomeClass._ivar1"
]
}
}
],
"flags": [
{
"attributes": [
"not_app_extension_safe"
]
}
],
"install_names": [
{
"name": "@rpath/lib/libasm.dylib"
}
],
"target_info": [
{
"min_deployment": "13.1",
"target": "arm64-macos"
}
]
},
"tapi_tbd_version": 5
}
7 changes: 6 additions & 1 deletion clang/test/InstallAPI/driver-invalid-options.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/// Check non-darwin triple is rejected.
// RUN: not clang-installapi -target x86_64-unknown-unknown %s 2> %t
// RUN: not clang-installapi -target x86_64-unknown-unknown %s -o tmp.tbd 2> %t
// RUN: FileCheck --check-prefix INVALID_INSTALLAPI -input-file %t %s
// INVALID_INSTALLAPI: error: unsupported option 'installapi' for target 'x86_64-unknown-unknown'

/// Check that missing install_name is reported.
// RUN: not clang-installapi -target x86_64-apple-ios-simulator %s -o tmp.tbd 2> %t
// RUN: FileCheck --check-prefix INVALID_INSTALL_NAME -input-file %t %s
// INVALID_INSTALL_NAME: error: no install name specified: add -install_name <path>
2 changes: 1 addition & 1 deletion clang/test/InstallAPI/functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

// RUN: clang-installapi -target arm64-apple-macos13.1 \
// RUN: -I%t/usr/include -I%t/usr/local/include \
// RUN: -install_name @rpath/lib/libfunctions.dylib \
// RUN: -install_name @rpath/lib/libfunctions.dylib --filetype=tbd-v4 \
// RUN: %t/inputs.json -o %t/outputs.tbd 2>&1 | FileCheck %s --allow-empty
// RUN: llvm-readtapi -compare %t/outputs.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty

Expand Down
10 changes: 10 additions & 0 deletions clang/test/SemaCXX/cxx23-assume.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,13 @@ static_assert(f5<D>() == 1); // expected-note 3 {{while checking constraint sati
static_assert(f5<double>() == 2);
static_assert(f5<E>() == 1); // expected-note {{while checking constraint satisfaction}} expected-note {{in instantiation of}}
static_assert(f5<F>() == 2); // expected-note {{while checking constraint satisfaction}} expected-note {{in instantiation of}}

// Do not validate assumptions whose evaluation would have side-effects.
constexpr int foo() {
int a = 0;
[[assume(a++)]] [[assume(++a)]]; // expected-warning 2 {{has side effects that will be discarded}} ext-warning 2 {{C++23 extension}}
[[assume((a+=1))]]; // expected-warning {{has side effects that will be discarded}} ext-warning {{C++23 extension}}
return a;
}

static_assert(foo() == 0);
8 changes: 8 additions & 0 deletions clang/tools/clang-installapi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ set(LLVM_LINK_COMPONENTS
Support
TargetParser
TextAPI
TextAPIBinaryReader
Option
)

set(LLVM_TARGET_DEFINITIONS InstallAPIOpts.td)
tablegen(LLVM InstallAPIOpts.inc -gen-opt-parser-defs)
add_public_tablegen_target(InstallAPIDriverOptions)

add_clang_tool(clang-installapi
ClangInstallAPI.cpp
Options.cpp

DEPENDS
InstallAPIDriverOptions
GENERATE_DRIVER
)

Expand All @@ -22,3 +29,4 @@ clang_target_link_libraries(clang-installapi
clangTooling
clangSerialization
)

35 changes: 10 additions & 25 deletions clang/tools/clang-installapi/ClangInstallAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
#include "Options.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Tool.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/InstallAPI/Frontend.h"
#include "clang/InstallAPI/FrontendRecords.h"
#include "clang/InstallAPI/InstallAPIDiagnostic.h"
#include "clang/InstallAPI/MachO.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/ArrayRef.h"
Expand Down Expand Up @@ -92,22 +92,8 @@ static bool run(ArrayRef<const char *> Args, const char *ProgName) {
IntrusiveRefCntPtr<clang::FileManager> FM(
new FileManager(clang::FileSystemOptions(), OverlayFileSystem));

// Set up driver to parse input arguments.
auto DriverArgs = llvm::ArrayRef(Args).slice(1);
clang::driver::Driver Driver(ProgName, llvm::sys::getDefaultTargetTriple(),
*Diag, "clang installapi tool");
auto TargetAndMode =
clang::driver::ToolChain::getTargetAndModeFromProgramName(ProgName);
Driver.setTargetAndMode(TargetAndMode);
bool HasError = false;
llvm::opt::InputArgList ArgList =
Driver.ParseArgStrings(DriverArgs, /*UseDriverMode=*/true, HasError);
if (HasError)
return EXIT_FAILURE;
Driver.setCheckInputsExist(false);

// Capture InstallAPI specific options and diagnose any option errors.
Options Opts(*Diag, FM.get(), ArgList);
// Capture all options and diagnose any errors.
Options Opts(*Diag, FM.get(), Args, ProgName);
if (Diag->hasErrorOccurred())
return EXIT_FAILURE;

Expand All @@ -130,6 +116,7 @@ static bool run(ArrayRef<const char *> Args, const char *ProgName) {
for (const HeaderType Type :
{HeaderType::Public, HeaderType::Private, HeaderType::Project}) {
Ctx.Slice = std::make_shared<FrontendRecordsSlice>(Trip);
Ctx.Verifier->setTarget(Targ);
Ctx.Type = Type;
if (!runFrontend(ProgName, Opts.DriverOpts.Verbose, Ctx,
InMemoryFileSystem.get(), Opts.getClangFrontendArgs()))
Expand All @@ -138,6 +125,9 @@ static bool run(ArrayRef<const char *> Args, const char *ProgName) {
}
}

if (Ctx.Verifier->getState() == DylibVerifier::Result::Invalid)
return EXIT_FAILURE;

// After symbols have been collected, prepare to write output.
auto Out = CI->createOutputFile(Ctx.OutputLoc, /*Binary=*/false,
/*RemoveFileOnSignal=*/false,
Expand All @@ -147,21 +137,16 @@ static bool run(ArrayRef<const char *> Args, const char *ProgName) {
return EXIT_FAILURE;

// Assign attributes for serialization.
auto Symbols = std::make_unique<SymbolSet>();
for (const auto &FR : FrontendResults) {
SymbolConverter Converter(Symbols.get(), FR->getTarget());
FR->visit(Converter);
}

InterfaceFile IF(std::move(Symbols));
InterfaceFile IF(Ctx.Verifier->getExports());
for (const auto &TargetInfo : Opts.DriverOpts.Targets) {
IF.addTarget(TargetInfo.first);
IF.setFromBinaryAttrs(Ctx.BA, TargetInfo.first);
}

// Write output file and perform CI cleanup.
if (auto Err = TextAPIWriter::writeToStream(*Out, IF, Ctx.FT)) {
Diag->Report(diag::err_cannot_open_file) << Ctx.OutputLoc;
Diag->Report(diag::err_cannot_write_file)
<< Ctx.OutputLoc << std::move(Err);
CI->clearOutputFiles(/*EraseFiles=*/true);
return EXIT_FAILURE;
}
Expand Down
31 changes: 31 additions & 0 deletions clang/tools/clang-installapi/InstallAPIOpts.td
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//===--- InstallAPIOpts.td ------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file defines the specific options for InstallAPI.
//
//===----------------------------------------------------------------------===//

// Include the common option parsing interfaces.
include "llvm/Option/OptParser.td"


/////////
// Options

// TextAPI options.
def filetype : Joined<["--"], "filetype=">,
HelpText<"Specify the output file type (tbd-v4 or tbd-v5)">;

// Verification options.
def verify_against : Separate<["-"], "verify-against">,
HelpText<"Verify the specified dynamic library/framework against the headers">;
def verify_against_EQ : Joined<["--"], "verify-against=">, Alias<verify_against>;
def verify_mode_EQ : Joined<["--"], "verify-mode=">,
HelpText<"Specify the severity and extend of the validation. Valid modes are ErrorsOnly, ErrorsAndWarnings, and Pedantic.">;
def demangle : Flag<["--", "-"], "demangle">,
HelpText<"Demangle symbols when printing warnings and errors">;
197 changes: 170 additions & 27 deletions clang/tools/clang-installapi/Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,93 @@
#include "clang/Driver/Driver.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/InstallAPI/FileList.h"
#include "clang/InstallAPI/InstallAPIDiagnostic.h"
#include "llvm/Support/Program.h"
#include "llvm/TargetParser/Host.h"
#include "llvm/TextAPI/DylibReader.h"
#include "llvm/TextAPI/TextAPIWriter.h"

using namespace clang::driver;
using namespace clang::driver::options;
using namespace llvm;
using namespace llvm::opt;
using namespace llvm::MachO;

namespace drv = clang::driver::options;

namespace clang {
namespace installapi {

/// Create prefix string literals used in InstallAPIOpts.td.
#define PREFIX(NAME, VALUE) \
static constexpr llvm::StringLiteral NAME##_init[] = VALUE; \
static constexpr llvm::ArrayRef<llvm::StringLiteral> NAME( \
NAME##_init, std::size(NAME##_init) - 1);
#include "InstallAPIOpts.inc"
#undef PREFIX

static constexpr const llvm::StringLiteral PrefixTable_init[] =
#define PREFIX_UNION(VALUES) VALUES
#include "InstallAPIOpts.inc"
#undef PREFIX_UNION
;
static constexpr const ArrayRef<StringLiteral>
PrefixTable(PrefixTable_init, std::size(PrefixTable_init) - 1);

/// Create table mapping all options defined in InstallAPIOpts.td.
static constexpr OptTable::Info InfoTable[] = {
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \
VISIBILITY, PARAM, HELPTEXT, METAVAR, VALUES) \
{PREFIX, NAME, HELPTEXT, METAVAR, OPT_##ID, Option::KIND##Class, \
PARAM, FLAGS, VISIBILITY, OPT_##GROUP, OPT_##ALIAS, ALIASARGS, \
VALUES},
#include "InstallAPIOpts.inc"
#undef OPTION
};

namespace {

/// \brief Create OptTable class for parsing actual command line arguments.
class DriverOptTable : public opt::PrecomputedOptTable {
public:
DriverOptTable() : PrecomputedOptTable(InfoTable, PrefixTable) {}
};

} // end anonymous namespace.

static llvm::opt::OptTable *createDriverOptTable() {
return new DriverOptTable();
}

bool Options::processDriverOptions(InputArgList &Args) {
// Handle inputs.
llvm::append_range(DriverOpts.FileLists, Args.getAllArgValues(OPT_INPUT));
llvm::append_range(DriverOpts.FileLists,
Args.getAllArgValues(drv::OPT_INPUT));

// Handle output.
SmallString<PATH_MAX> OutputPath;
if (auto *Arg = Args.getLastArg(OPT_o)) {
if (auto *Arg = Args.getLastArg(drv::OPT_o)) {
OutputPath = Arg->getValue();
if (OutputPath != "-")
FM->makeAbsolutePath(OutputPath);
DriverOpts.OutputPath = std::string(OutputPath);
}
if (DriverOpts.OutputPath.empty()) {
Diags->Report(diag::err_no_output_file);
return false;
}

// Do basic error checking first for mixing -target and -arch options.
auto *ArgArch = Args.getLastArgNoClaim(OPT_arch);
auto *ArgTarget = Args.getLastArgNoClaim(OPT_target);
auto *ArgArch = Args.getLastArgNoClaim(drv::OPT_arch);
auto *ArgTarget = Args.getLastArgNoClaim(drv::OPT_target);
auto *ArgTargetVariant =
Args.getLastArgNoClaim(OPT_darwin_target_variant_triple);
Args.getLastArgNoClaim(drv::OPT_darwin_target_variant_triple);
if (ArgArch && (ArgTarget || ArgTargetVariant)) {
Diags->Report(clang::diag::err_drv_argument_not_allowed_with)
<< ArgArch->getAsString(Args)
<< (ArgTarget ? ArgTarget : ArgTargetVariant)->getAsString(Args);
return false;
}

auto *ArgMinTargetOS = Args.getLastArgNoClaim(OPT_mtargetos_EQ);
auto *ArgMinTargetOS = Args.getLastArgNoClaim(drv::OPT_mtargetos_EQ);
if ((ArgTarget || ArgTargetVariant) && ArgMinTargetOS) {
Diags->Report(clang::diag::err_drv_cannot_mix_options)
<< ArgTarget->getAsString(Args) << ArgMinTargetOS->getAsString(Args);
Expand All @@ -55,7 +105,7 @@ bool Options::processDriverOptions(InputArgList &Args) {

// Capture target triples first.
if (ArgTarget) {
for (const Arg *A : Args.filtered(OPT_target)) {
for (const Arg *A : Args.filtered(drv::OPT_target)) {
A->claim();
llvm::Triple TargetTriple(A->getValue());
Target TAPITarget = Target(TargetTriple);
Expand All @@ -69,30 +119,32 @@ bool Options::processDriverOptions(InputArgList &Args) {
}
}

DriverOpts.Verbose = Args.hasArgNoClaim(OPT_v);
DriverOpts.Verbose = Args.hasArgNoClaim(drv::OPT_v);

return true;
}

bool Options::processLinkerOptions(InputArgList &Args) {
// TODO: add error handling.

// Required arguments.
if (const Arg *A = Args.getLastArg(options::OPT_install__name))
// Handle required arguments.
if (const Arg *A = Args.getLastArg(drv::OPT_install__name))
LinkerOpts.InstallName = A->getValue();
if (LinkerOpts.InstallName.empty()) {
Diags->Report(diag::err_no_install_name);
return false;
}

// Defaulted or optional arguments.
if (auto *Arg = Args.getLastArg(OPT_current__version))
if (auto *Arg = Args.getLastArg(drv::OPT_current__version))
LinkerOpts.CurrentVersion.parse64(Arg->getValue());

if (auto *Arg = Args.getLastArg(OPT_compatibility__version))
if (auto *Arg = Args.getLastArg(drv::OPT_compatibility__version))
LinkerOpts.CompatVersion.parse64(Arg->getValue());

LinkerOpts.IsDylib = Args.hasArg(OPT_dynamiclib);
LinkerOpts.IsDylib = Args.hasArg(drv::OPT_dynamiclib);

LinkerOpts.AppExtensionSafe =
Args.hasFlag(OPT_fapplication_extension, OPT_fno_application_extension,
/*Default=*/LinkerOpts.AppExtensionSafe);
LinkerOpts.AppExtensionSafe = Args.hasFlag(
drv::OPT_fapplication_extension, drv::OPT_fno_application_extension,
/*Default=*/LinkerOpts.AppExtensionSafe);

if (::getenv("LD_NO_ENCRYPT") != nullptr)
LinkerOpts.AppExtensionSafe = true;
Expand All @@ -105,7 +157,7 @@ bool Options::processLinkerOptions(InputArgList &Args) {
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)) {
if (auto *A = Args.getLastArgNoClaim(drv::OPT_x)) {
FEOpts.LangMode = llvm::StringSwitch<clang::Language>(A->getValue())
.Case("c", clang::Language::C)
.Case("c++", clang::Language::CXX)
Expand All @@ -119,8 +171,8 @@ bool Options::processFrontendOptions(InputArgList &Args) {
return false;
}
}
for (auto *A : Args.filtered(OPT_ObjC, OPT_ObjCXX)) {
if (A->getOption().matches(OPT_ObjC))
for (auto *A : Args.filtered(drv::OPT_ObjC, drv::OPT_ObjCXX)) {
if (A->getOption().matches(drv::OPT_ObjC))
FEOpts.LangMode = clang::Language::ObjC;
else
FEOpts.LangMode = clang::Language::ObjCXX;
Expand All @@ -129,9 +181,77 @@ bool Options::processFrontendOptions(InputArgList &Args) {
return true;
}

std::vector<const char *>
Options::processAndFilterOutInstallAPIOptions(ArrayRef<const char *> Args) {
std::unique_ptr<llvm::opt::OptTable> Table;
Table.reset(createDriverOptTable());

unsigned MissingArgIndex, MissingArgCount;
auto ParsedArgs = Table->ParseArgs(Args.slice(1), MissingArgIndex,
MissingArgCount, Visibility());

// Capture InstallAPI only driver options.
DriverOpts.Demangle = ParsedArgs.hasArg(OPT_demangle);

if (auto *A = ParsedArgs.getLastArg(OPT_filetype)) {
DriverOpts.OutFT = TextAPIWriter::parseFileType(A->getValue());
if (DriverOpts.OutFT == FileType::Invalid) {
Diags->Report(clang::diag::err_drv_invalid_value)
<< A->getAsString(ParsedArgs) << A->getValue();
return {};
}
}

if (const Arg *A = ParsedArgs.getLastArg(OPT_verify_mode_EQ)) {
DriverOpts.VerifyMode =
StringSwitch<VerificationMode>(A->getValue())
.Case("ErrorsOnly", VerificationMode::ErrorsOnly)
.Case("ErrorsAndWarnings", VerificationMode::ErrorsAndWarnings)
.Case("Pedantic", VerificationMode::Pedantic)
.Default(VerificationMode::Invalid);

if (DriverOpts.VerifyMode == VerificationMode::Invalid) {
Diags->Report(clang::diag::err_drv_invalid_value)
<< A->getAsString(ParsedArgs) << A->getValue();
return {};
}
}

if (const Arg *A = ParsedArgs.getLastArg(OPT_verify_against))
DriverOpts.DylibToVerify = A->getValue();

/// Any unclaimed arguments should be forwarded to the clang driver.
std::vector<const char *> ClangDriverArgs(ParsedArgs.size());
for (const Arg *A : ParsedArgs) {
if (A->isClaimed())
continue;
llvm::copy(A->getValues(), std::back_inserter(ClangDriverArgs));
}
return ClangDriverArgs;
}

Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
InputArgList &ArgList)
ArrayRef<const char *> Args, const StringRef ProgName)
: Diags(&Diag), FM(FM) {

// First process InstallAPI specific options.
auto DriverArgs = processAndFilterOutInstallAPIOptions(Args);
if (Diags->hasErrorOccurred())
return;

// Set up driver to parse remaining input arguments.
clang::driver::Driver Driver(ProgName, llvm::sys::getDefaultTargetTriple(),
*Diags, "clang installapi tool");
auto TargetAndMode =
clang::driver::ToolChain::getTargetAndModeFromProgramName(ProgName);
Driver.setTargetAndMode(TargetAndMode);
bool HasError = false;
llvm::opt::InputArgList ArgList =
Driver.ParseArgStrings(DriverArgs, /*UseDriverMode=*/true, HasError);
if (HasError)
return;
Driver.setCheckInputsExist(false);

if (!processDriverOptions(ArgList))
return;

Expand All @@ -145,7 +265,6 @@ Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
for (const Arg *A : ArgList) {
if (A->isClaimed())
continue;

FrontendArgs.emplace_back(A->getSpelling());
llvm::copy(A->getValues(), std::back_inserter(FrontendArgs));
}
Expand All @@ -172,16 +291,40 @@ InstallAPIContext Options::createContext() {
for (const std::string &ListPath : DriverOpts.FileLists) {
auto Buffer = FM->getBufferForFile(ListPath);
if (auto Err = Buffer.getError()) {
Diags->Report(diag::err_cannot_open_file) << ListPath;
Diags->Report(diag::err_cannot_open_file) << ListPath << Err.message();
return Ctx;
}
if (auto Err = FileListReader::loadHeaders(std::move(Buffer.get()),
Ctx.InputHeaders)) {
Diags->Report(diag::err_cannot_open_file) << ListPath;
Diags->Report(diag::err_cannot_open_file) << ListPath << std::move(Err);
return Ctx;
}
}

// Parse binary dylib and initialize verifier.
if (DriverOpts.DylibToVerify.empty()) {
Ctx.Verifier = std::make_unique<DylibVerifier>();
return Ctx;
}

auto Buffer = FM->getBufferForFile(DriverOpts.DylibToVerify);
if (auto Err = Buffer.getError()) {
Diags->Report(diag::err_cannot_open_file)
<< DriverOpts.DylibToVerify << Err.message();
return Ctx;
}

DylibReader::ParseOption PO;
PO.Undefineds = false;
Expected<Records> Slices =
DylibReader::readFile((*Buffer)->getMemBufferRef(), PO);
if (auto Err = Slices.takeError()) {
Diags->Report(diag::err_cannot_open_file) << DriverOpts.DylibToVerify;
return Ctx;
}

Ctx.Verifier = std::make_unique<DylibVerifier>(
std::move(*Slices), Diags, DriverOpts.VerifyMode, DriverOpts.Demangle);
return Ctx;
}

Expand Down
25 changes: 24 additions & 1 deletion clang/tools/clang-installapi/Options.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/FileManager.h"
#include "clang/Driver/Driver.h"
#include "clang/Frontend/FrontendOptions.h"
#include "clang/InstallAPI/Context.h"
#include "clang/InstallAPI/DylibVerifier.h"
#include "clang/InstallAPI/MachO.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
Expand All @@ -32,12 +34,21 @@ struct DriverOptions {
/// \brief Mappings of target triples & tapi targets to build for.
std::map<llvm::MachO::Target, llvm::Triple> Targets;

/// \brief Path to binary dylib for comparing.
std::string DylibToVerify;

/// \brief Output path.
std::string OutputPath;

/// \brief File encoding to print.
FileType OutFT = FileType::TBD_V5;

/// \brief Verification mode for comparing symbols.
VerificationMode VerifyMode = VerificationMode::Pedantic;

/// \brief Print demangled symbols when reporting errors.
bool Demangle = false;

/// \brief Print verbose output.
bool Verbose = false;
};
Expand Down Expand Up @@ -69,6 +80,8 @@ class Options {
bool processDriverOptions(llvm::opt::InputArgList &Args);
bool processLinkerOptions(llvm::opt::InputArgList &Args);
bool processFrontendOptions(llvm::opt::InputArgList &Args);
std::vector<const char *>
processAndFilterOutInstallAPIOptions(ArrayRef<const char *> Args);

public:
/// The various options grouped together.
Expand All @@ -83,7 +96,7 @@ class Options {

/// \brief Constructor for options.
Options(clang::DiagnosticsEngine &Diag, FileManager *FM,
llvm::opt::InputArgList &Args);
ArrayRef<const char *> Args, const StringRef ProgName);

/// \brief Get CC1 arguments after extracting out the irrelevant
/// ones.
Expand All @@ -95,6 +108,16 @@ class Options {
std::vector<std::string> FrontendArgs;
};

enum ID {
OPT_INVALID = 0, // This is not an option ID.
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \
VISIBILITY, PARAM, HELPTEXT, METAVAR, VALUES) \
OPT_##ID,
#include "InstallAPIOpts.inc"
LastOption
#undef OPTION
};

} // namespace installapi
} // namespace clang
#endif
4 changes: 2 additions & 2 deletions libcxx/docs/Status/Cxx20Issues.csv
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@
"`3355 <https://wg21.link/LWG3355>`__","The memory algorithms should support move-only input iterators introduced by P1207","Prague","|Complete|","15.0","|ranges|"
"`3356 <https://wg21.link/LWG3356>`__","``__cpp_lib_nothrow_convertible``\ should be ``__cpp_lib_is_nothrow_convertible``\ ","Prague","|Complete|","12.0"
"`3358 <https://wg21.link/LWG3358>`__","|sect|\ [span.cons] is mistaken that ``to_address``\ can throw","Prague","|Complete|","17.0"
"`3359 <https://wg21.link/LWG3359>`__","``<chrono>``\ leap second support should allow for negative leap seconds","Prague","","","|chrono|"
"`3359 <https://wg21.link/LWG3359>`__","``<chrono>``\ leap second support should allow for negative leap seconds","Prague","|Complete|","19.0","|chrono|"
"`3360 <https://wg21.link/LWG3360>`__","``three_way_comparable_with``\ is inconsistent with similar concepts","Prague","|Nothing To Do|","","|spaceship|"
"`3362 <https://wg21.link/LWG3362>`__","Strike ``stop_source``\ 's ``operator!=``\ ","Prague","",""
"`3363 <https://wg21.link/LWG3363>`__","``drop_while_view``\ should opt-out of ``sized_range``\ ","Prague","|Nothing To Do|","","|ranges|"
Expand All @@ -286,7 +286,7 @@
"`3380 <https://wg21.link/LWG3380>`__","``common_type``\ and comparison categories","Prague","|Complete|","15.0","|spaceship|"
"`3381 <https://wg21.link/LWG3381>`__","``begin``\ and ``data``\ must agree for ``contiguous_range``\ ","Prague","|Nothing To Do|","","|ranges|"
"`3382 <https://wg21.link/LWG3382>`__","NTTP for ``pair``\ and ``array``\ ","Prague","",""
"`3383 <https://wg21.link/LWG3383>`__","|sect|\ [time.zone.leap.nonmembers] ``sys_seconds``\ should be replaced with ``seconds``\ ","Prague","","","|chrono|"
"`3383 <https://wg21.link/LWG3383>`__","|sect|\ [time.zone.leap.nonmembers] ``sys_seconds``\ should be replaced with ``seconds``\ ","Prague","|Complete|","19.0","|chrono|"
"`3384 <https://wg21.link/LWG3384>`__","``transform_view::*sentinel*``\ has an incorrect ``operator-``\ ","Prague","|Complete|","15.0","|ranges|"
"`3385 <https://wg21.link/LWG3385>`__","``common_iterator``\ is not sufficiently constrained for non-copyable iterators","Prague","|Complete|","15.0","|ranges|"
"`3387 <https://wg21.link/LWG3387>`__","|sect|\ [range.reverse.view] ``reverse_view<V>``\ unintentionally requires ``range<const V>``\ ","Prague","|Complete|","15.0","|ranges|"
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx20Papers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
"`P1970R2 <https://wg21.link/P1970R2>`__","LWG","Consistency for size() functions: Add ranges::ssize","Prague","|Complete|","15.0","|ranges|"
"`P1973R1 <https://wg21.link/P1973R1>`__","LWG","Rename ""_default_init"" Functions, Rev1","Prague","|Complete|","16.0"
"`P1976R2 <https://wg21.link/P1976R2>`__","LWG","Fixed-size span construction from dynamic range","Prague","|Complete|","11.0","|ranges|"
"`P1981R0 <https://wg21.link/P1981R0>`__","LWG","Rename leap to leap_second","Prague","* *",""
"`P1981R0 <https://wg21.link/P1981R0>`__","LWG","Rename leap to leap_second","Prague","|Complete|","19.0","|chrono|"
"`P1982R0 <https://wg21.link/P1982R0>`__","LWG","Rename link to time_zone_link","Prague","|Complete|","19.0","|chrono|"
"`P1983R0 <https://wg21.link/P1983R0>`__","LWG","Wording for GB301, US296, US292, US291, and US283","Prague","|Complete|","15.0","|ranges|"
"`P1994R1 <https://wg21.link/P1994R1>`__","LWG","elements_view needs its own sentinel","Prague","|Complete|","16.0","|ranges|"
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/SpaceshipProjects.csv
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Section,Description,Dependencies,Assignee,Complete
| `year_month_weekday_last <https://reviews.llvm.org/D152699>`_",None,Hristo Hristov,|Complete|
`[time.zone.nonmembers] <https://wg21.link/time.zone.nonmembers>`_,"`chrono::time_zone`",A ``<chrono>`` implementation,Mark de Wever,|Complete|
`[time.zone.zonedtime.nonmembers] <https://wg21.link/time.zone.zonedtime.nonmembers>`_,"`chrono::zoned_time`",A ``<chrono>`` implementation,Mark de Wever,|In Progress|
`[time.zone.leap.nonmembers] <https://wg21.link/time.zone.leap.nonmembers>`_,"`chrono::time_leap_seconds`",A ``<chrono>`` implementation,Mark de Wever,|In Progress|
`[time.zone.leap.nonmembers] <https://wg21.link/time.zone.leap.nonmembers>`_,"`chrono::time_leap_seconds`",A ``<chrono>`` implementation,Mark de Wever,|Complete|
`[time.zone.link.nonmembers] <https://wg21.link/time.zone.link.nonmembers>`_,"`chrono::time_zone_link`",A ``<chrono>`` implementation,Mark de Wever,|Complete|
- `5.13 Clause 28: Localization library <https://wg21.link/p1614r2#clause-28-localization-library>`_,,,,
"| `[locale] <https://wg21.link/locale>`_
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ set(files
__chrono/formatter.h
__chrono/hh_mm_ss.h
__chrono/high_resolution_clock.h
__chrono/leap_second.h
__chrono/literals.h
__chrono/month.h
__chrono/month_weekday.h
Expand Down
112 changes: 112 additions & 0 deletions libcxx/include/__chrono/leap_second.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// -*- 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
//
//===----------------------------------------------------------------------===//

// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html

#ifndef _LIBCPP___CHRONO_LEAP_SECOND_H
#define _LIBCPP___CHRONO_LEAP_SECOND_H

#include <version>
// Enable the contents of the header only when libc++ was built with experimental features enabled.
#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB)

# include <__chrono/duration.h>
# include <__chrono/system_clock.h>
# include <__chrono/time_point.h>
# include <__compare/ordering.h>
# include <__compare/three_way_comparable.h>
# include <__config>

# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
# endif

_LIBCPP_BEGIN_NAMESPACE_STD

# if _LIBCPP_STD_VER >= 20

namespace chrono {

class leap_second {
public:
struct __constructor_tag;
[[nodiscard]]
_LIBCPP_HIDE_FROM_ABI explicit constexpr leap_second(__constructor_tag&&, sys_seconds __date, seconds __value)
: __date_(__date), __value_(__value) {}

_LIBCPP_HIDE_FROM_ABI leap_second(const leap_second&) = default;
_LIBCPP_HIDE_FROM_ABI leap_second& operator=(const leap_second&) = default;

_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr sys_seconds date() const noexcept { return __date_; }

_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr seconds value() const noexcept { return __value_; }

private:
sys_seconds __date_;
seconds __value_;
};

_LIBCPP_HIDE_FROM_ABI inline constexpr bool operator==(const leap_second& __x, const leap_second& __y) {
return __x.date() == __y.date();
}
_LIBCPP_HIDE_FROM_ABI inline constexpr strong_ordering operator<=>(const leap_second& __x, const leap_second& __y) {
return __x.date() <=> __y.date();
}

template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const leap_second& __x, const sys_time<_Duration>& __y) {
return __x.date() == __y;
}
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const leap_second& __x, const sys_time<_Duration>& __y) {
return __x.date() < __y;
}
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const sys_time<_Duration>& __x, const leap_second& __y) {
return __x < __y.date();
}
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const leap_second& __x, const sys_time<_Duration>& __y) {
return __y < __x;
}
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const sys_time<_Duration>& __x, const leap_second& __y) {
return __y < __x;
}
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const leap_second& __x, const sys_time<_Duration>& __y) {
return !(__y < __x);
}
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const sys_time<_Duration>& __x, const leap_second& __y) {
return !(__y < __x);
}
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator>=(const leap_second& __x, const sys_time<_Duration>& __y) {
return !(__x < __y);
}
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI constexpr bool operator>=(const sys_time<_Duration>& __x, const leap_second& __y) {
return !(__x < __y);
}
template <class _Duration>
requires three_way_comparable_with<sys_seconds, sys_time<_Duration>>
_LIBCPP_HIDE_FROM_ABI constexpr auto operator<=>(const leap_second& __x, const sys_time<_Duration>& __y) {
return __x.date() <=> __y;
}

} // namespace chrono

# endif //_LIBCPP_STD_VER >= 20

_LIBCPP_END_NAMESPACE_STD

#endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB)

#endif // _LIBCPP___CHRONO_LEAP_SECOND_H
3 changes: 3 additions & 0 deletions libcxx/include/__chrono/tzdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// Enable the contents of the header only when libc++ was built with experimental features enabled.
#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB)

# include <__chrono/leap_second.h>
# include <__chrono/time_zone.h>
# include <__chrono/time_zone_link.h>
# include <__config>
Expand All @@ -40,6 +41,8 @@ struct tzdb {
string version;
vector<time_zone> zones;
vector<time_zone_link> links;

vector<leap_second> leap_seconds;
};

} // namespace chrono
Expand Down
40 changes: 40 additions & 0 deletions libcxx/include/chrono
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ struct tzdb {
string version;
vector<time_zone> zones;
vector<time_zone_link> links;
vector<leap_second> leap_seconds;
};
class tzdb_list { // C++20
Expand Down Expand Up @@ -731,6 +732,43 @@ class time_zone {
bool operator==(const time_zone& x, const time_zone& y) noexcept; // C++20
strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept; // C++20
// [time.zone.leap], leap second support
class leap_second { // C++20
public:
leap_second(const leap_second&) = default;
leap_second& operator=(const leap_second&) = default;
// unspecified additional constructors
constexpr sys_seconds date() const noexcept;
constexpr seconds value() const noexcept;
};
constexpr bool operator==(const leap_second& x, const leap_second& y); // C++20
constexpr strong_ordering operator<=>(const leap_second& x, const leap_second& y);
template<class Duration> // C++20
constexpr bool operator==(const leap_second& x, const sys_time<Duration>& y);
template<class Duration> // C++20
constexpr bool operator< (const leap_second& x, const sys_time<Duration>& y);
template<class Duration> // C++20
constexpr bool operator< (const sys_time<Duration>& x, const leap_second& y);
template<class Duration> // C++20
constexpr bool operator> (const leap_second& x, const sys_time<Duration>& y);
template<class Duration> // C++20
constexpr bool operator> (const sys_time<Duration>& x, const leap_second& y);
template<class Duration> // C++20
constexpr bool operator<=(const leap_second& x, const sys_time<Duration>& y);
template<class Duration> // C++20
constexpr bool operator<=(const sys_time<Duration>& x, const leap_second& y);
template<class Duration> // C++20
constexpr bool operator>=(const leap_second& x, const sys_time<Duration>& y);
template<class Duration> // C++20
constexpr bool operator>=(const sys_time<Duration>& x, const leap_second& y);
template<class Duration> // C++20
requires three_way_comparable_with<sys_seconds, sys_time<Duration>>
constexpr auto operator<=>(const leap_second& x, const sys_time<Duration>& y);
// [time.zone.link], class time_zone_link
class time_zone_link { // C++20
public:
Expand Down Expand Up @@ -862,6 +900,7 @@ constexpr chrono::year operator ""y(unsigned lo

#if !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \
!defined(_LIBCPP_HAS_NO_LOCALIZATION)
# include <__chrono/leap_second.h>
# include <__chrono/time_zone.h>
# include <__chrono/time_zone_link.h>
# include <__chrono/tzdb.h>
Expand All @@ -883,6 +922,7 @@ constexpr chrono::year operator ""y(unsigned lo

#if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) && _LIBCPP_STD_VER == 20
# include <charconv>
# include <locale>
#endif

#endif // _LIBCPP_CHRONO
7 changes: 1 addition & 6 deletions libcxx/include/complex
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ template<class T> complex<T> tanh (const complex<T>&);

#include <__config>
#include <__fwd/complex.h>
#include <__fwd/tuple.h>
#include <__tuple/tuple_element.h>
#include <__tuple/tuple_size.h>
#include <__utility/move.h>
Expand Down Expand Up @@ -1443,15 +1444,9 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const complex<_Tp>& __x) {

// [complex.tuple], tuple interface

template <class _Tp>
struct tuple_size;

template <class _Tp>
struct tuple_size<complex<_Tp>> : integral_constant<size_t, 2> {};

template <size_t _Ip, class _Tp>
struct tuple_element;

template <size_t _Ip, class _Tp>
struct tuple_element<_Ip, complex<_Tp>> {
static_assert(_Ip < 2, "Index value is out of range.");
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/libcxx.imp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@
{ include: [ "<__chrono/formatter.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/hh_mm_ss.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/high_resolution_clock.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/leap_second.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/literals.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/month.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/month_weekday.h>", "private", "<chrono>", "public" ] },
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,7 @@ module std_private_chrono_high_resolution_clock [system] {
export std_private_chrono_steady_clock
export std_private_chrono_system_clock
}
module std_private_chrono_leap_second [system] { header "__chrono/leap_second.h" }
module std_private_chrono_literals [system] { header "__chrono/literals.h" }
module std_private_chrono_month [system] { header "__chrono/month.h" }
module std_private_chrono_month_weekday [system] { header "__chrono/month_weekday.h" }
Expand Down
20 changes: 1 addition & 19 deletions libcxx/include/mutex
Original file line number Diff line number Diff line change
Expand Up @@ -418,24 +418,6 @@ inline _LIBCPP_HIDE_FROM_ABI void lock(_L0& __l0, _L1& __l1, _L2& __l2, _L3&...
std::__lock_first(0, __l0, __l1, __l2, __l3...);
}

template <class _L0>
inline _LIBCPP_HIDE_FROM_ABI void __unlock(_L0& __l0) {
__l0.unlock();
}

template <class _L0, class _L1>
inline _LIBCPP_HIDE_FROM_ABI void __unlock(_L0& __l0, _L1& __l1) {
__l0.unlock();
__l1.unlock();
}

template <class _L0, class _L1, class _L2, class... _L3>
inline _LIBCPP_HIDE_FROM_ABI void __unlock(_L0& __l0, _L1& __l1, _L2& __l2, _L3&... __l3) {
__l0.unlock();
__l1.unlock();
std::__unlock(__l2, __l3...);
}

# endif // _LIBCPP_CXX03_LANG

# if _LIBCPP_STD_VER >= 17
Expand Down Expand Up @@ -498,7 +480,7 @@ public:
private:
template <size_t... _Indx>
_LIBCPP_HIDE_FROM_ABI static void __unlock_unpack(__tuple_indices<_Indx...>, _MutexTuple& __mt) {
std::__unlock(std::get<_Indx>(__mt)...);
(std::get<_Indx>(__mt).unlock(), ...);
}

_MutexTuple __t_;
Expand Down
28 changes: 13 additions & 15 deletions libcxx/modules/std/chrono.inc
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,7 @@ export namespace std {
using std::chrono::reload_tzdb;
using std::chrono::remote_version;

# endif // !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) &&
// !defined(_LIBCPP_HAS_NO_LOCALIZATION)

# if 0
# if 0
// [time.zone.exception], exception classes
using std::chrono::ambiguous_local_time;
using std::chrono::nonexistent_local_time;
Expand All @@ -221,11 +218,11 @@ export namespace std {

// [time.zone.timezone], class time_zone
using std::chrono::choose;
# endif
# ifdef _LIBCPP_ENABLE_EXPERIMENTAL
# endif // if 0

using std::chrono::time_zone;
# endif
# if 0

# if 0

// [time.zone.zonedtraits], class template zoned_traits
using std::chrono::zoned_traits;
Expand All @@ -234,22 +231,23 @@ export namespace std {
using std::chrono::zoned_time;

using std::chrono::zoned_seconds;
# endif // if 0

// [time.zone.leap], leap second support
using std::chrono::leap_second;
# endif

# ifdef _LIBCPP_ENABLE_EXPERIMENTAL
// [time.zone.link], class time_zone_link
using std::chrono::time_zone_link;
# endif

# if 0
# if 0
// [time.format], formatting
using std::chrono::local_time_format;
# endif
#endif // _LIBCPP_ENABLE_EXPERIMENTAL
} // namespace chrono
# endif
# endif // _LIBCPP_ENABLE_EXPERIMENTAL
#endif // !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) &&
// !defined(_LIBCPP_HAS_NO_LOCALIZATION)

} // namespace chrono

#ifndef _LIBCPP_HAS_NO_LOCALIZATION
using std::formatter;
Expand Down
1 change: 1 addition & 0 deletions libcxx/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ endif()

if (LIBCXX_ENABLE_LOCALIZATION AND LIBCXX_ENABLE_FILESYSTEM AND LIBCXX_ENABLE_TIME_ZONE_DATABASE)
list(APPEND LIBCXX_EXPERIMENTAL_SOURCES
include/tzdb/leap_second_private.h
include/tzdb/time_zone_link_private.h
include/tzdb/time_zone_private.h
include/tzdb/types_private.h
Expand Down
27 changes: 27 additions & 0 deletions libcxx/src/include/tzdb/leap_second_private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// -*- 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
//
//===----------------------------------------------------------------------===//

// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html

#ifndef _LIBCPP_SRC_INCLUDE_TZDB_LEAP_SECOND_PRIVATE_H
#define _LIBCPP_SRC_INCLUDE_TZDB_LEAP_SECOND_PRIVATE_H

#include <chrono>

_LIBCPP_BEGIN_NAMESPACE_STD

namespace chrono {

struct leap_second::__constructor_tag {};

} // namespace chrono

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP_SRC_INCLUDE_TZDB_LEAP_SECOND_PRIVATE_H
42 changes: 42 additions & 0 deletions libcxx/src/tzdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <stdexcept>
#include <string>

#include "include/tzdb/leap_second_private.h"
#include "include/tzdb/time_zone_link_private.h"
#include "include/tzdb/time_zone_private.h"
#include "include/tzdb/types_private.h"
Expand Down Expand Up @@ -622,6 +623,36 @@ static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istr
}
}

static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, istream&& __input) {
// The file stores dates since 1 January 1900, 00:00:00, we want
// seconds since 1 January 1970.
constexpr auto __offset = sys_days{1970y / January / 1} - sys_days{1900y / January / 1};

while (true) {
switch (__input.peek()) {
case istream::traits_type::eof():
return;

case ' ':
case '\t':
case '\n':
__input.get();
continue;

case '#':
chrono::__skip_line(__input);
continue;
}

sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, false)}} - __offset;
chrono::__skip_mandatory_whitespace(__input);
seconds __value{chrono::__parse_integral(__input, false)};
chrono::__skip_line(__input);

__leap_seconds.emplace_back(leap_second::__constructor_tag{}, __date, __value);
}
}

void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
filesystem::path __root = chrono::__libcpp_tzdb_directory();
ifstream __tzdata{__root / "tzdata.zi"};
Expand All @@ -631,6 +662,17 @@ void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
std::ranges::sort(__tzdb.zones);
std::ranges::sort(__tzdb.links);
std::ranges::sort(__rules, {}, [](const auto& p) { return p.first; });

// There are two files with the leap second information
// - leapseconds as specified by zic
// - leap-seconds.list the source data
// The latter is much easier to parse, it seems Howard shares that
// opinion.
chrono::__parse_leap_seconds(__tzdb.leap_seconds, ifstream{__root / "leap-seconds.list"});
// The Standard requires the leap seconds to be sorted. The file
// leap-seconds.list usually provides them in sorted order, but that is not
// guaranteed so we ensure it here.
std::ranges::sort(__tzdb.leap_seconds);
}

//===----------------------------------------------------------------------===//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
// These types have "private" constructors.
extern std::chrono::time_zone tz;
extern std::chrono::time_zone_link link;
extern std::chrono::leap_second leap;

void test() {
std::chrono::tzdb_list& list = std::chrono::get_tzdb_list();
Expand All @@ -51,4 +52,9 @@ void test() {
operator==(link, link);
operator<=>(link, link);
}

{
leap.date();
leap.value();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
// These types have "private" constructors.
extern std::chrono::time_zone tz;
extern std::chrono::time_zone_link link;
extern std::chrono::leap_second leap;

void test() {
std::chrono::tzdb_list& list = std::chrono::get_tzdb_list();
Expand Down Expand Up @@ -51,4 +52,9 @@ void test() {
// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
operator<=>(link, link);
}

{
leap.date(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
leap.value(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
}
}
107 changes: 107 additions & 0 deletions libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb

// XFAIL: libcpp-has-no-incomplete-tzdb
// XFAIL: availability-tzdb-missing

// <chrono>

// Tests the IANA database leap seconds parsing and operations.
// This is not part of the public tzdb interface.

#include <cassert>
#include <chrono>
#include <fstream>
#include <string>
#include <string_view>

#include "assert_macros.h"
#include "concat_macros.h"
#include "filesystem_test_helper.h"
#include "test_tzdb.h"

scoped_test_env env;
[[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo");
const std::filesystem::path tzdata = env.create_file("zoneinfo/tzdata.zi");
const std::filesystem::path leap_seconds = env.create_file("zoneinfo/leap-seconds.list");

std::string_view std::chrono::__libcpp_tzdb_directory() {
static std::string result = dir.string();
return result;
}

void write(std::string_view input) {
static int version = 0;

std::ofstream f{tzdata};
f << "# version " << version++ << '\n';
std::ofstream{leap_seconds}.write(input.data(), input.size());
}

static const std::chrono::tzdb& parse(std::string_view input) {
write(input);
return std::chrono::reload_tzdb();
}

static void test_exception(std::string_view input, [[maybe_unused]] std::string_view what) {
write(input);

TEST_VALIDATE_EXCEPTION(
std::runtime_error,
[&]([[maybe_unused]] const std::runtime_error& e) {
TEST_LIBCPP_REQUIRE(
e.what() == what,
TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n'));
},
TEST_IGNORE_NODISCARD std::chrono::reload_tzdb());
}

static void test_invalid() {
test_exception("0", "corrupt tzdb: expected a non-zero digit");

test_exception("1", "corrupt tzdb: expected whitespace");

test_exception("1 ", "corrupt tzdb: expected a non-zero digit");
}

static void test_leap_seconds() {
using namespace std::chrono;

// Test whether loading also sorts the entries in the proper order.
const tzdb& result = parse(
R"(
2303683200 12 # 1 Jan 1973
2287785600 11 # 1 Jul 1972
2272060800 10 # 1 Jan 1972
86400 1 # 2 Jan 1900 Dummy entry to test before 1970
)");

assert(result.leap_seconds.size() == 4);

assert(result.leap_seconds[0].date() == sys_seconds{sys_days{1900y / January / 2}});
assert(result.leap_seconds[0].value() == 1s);

assert(result.leap_seconds[1].date() == sys_seconds{sys_days{1972y / January / 1}});
assert(result.leap_seconds[1].value() == 10s);

assert(result.leap_seconds[2].date() == sys_seconds{sys_days{1972y / July / 1}});
assert(result.leap_seconds[2].value() == 11s);

assert(result.leap_seconds[3].date() == sys_seconds{sys_days{1973y / January / 1}});
assert(result.leap_seconds[3].value() == 12s);
}

int main(int, const char**) {
test_invalid();
test_leap_seconds();

return 0;
}
1 change: 1 addition & 0 deletions libcxx/test/libcxx/transitive_includes/cxx20.csv
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ chrono ctime
chrono cwchar
chrono forward_list
chrono limits
chrono locale
chrono optional
chrono ostream
chrono ratio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,14 @@ int main(int, char**)
errno = E2BIG; // something that message will never generate
const std::error_category& e_cat1 = std::generic_category();
const std::string msg = e_cat1.message(-1);
// Exact message format varies by platform. We can't detect
// some of these (Musl in particular) using the preprocessor,
// so accept a few sensible messages. Newlib unfortunately
// responds with an empty message, which we probably want to
// treat as a failure code otherwise, but we can detect that
// with the preprocessor.
LIBCPP_ASSERT(msg.rfind("Error -1 occurred", 0) == 0 // AIX
|| msg.rfind("No error information", 0) == 0 // Musl
|| msg.rfind("Unknown error", 0) == 0 // Glibc
#if defined(_NEWLIB_VERSION)
|| msg.empty()
// Exact message format varies by platform.
#if defined(_AIX)
LIBCPP_ASSERT(msg.rfind("Error -1 occurred", 0) == 0);
#elif defined(_NEWLIB_VERSION)
LIBCPP_ASSERT(msg.empty());
#else
LIBCPP_ASSERT(msg.rfind("Unknown error", 0) == 0);
#endif
);
assert(errno == E2BIG);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,14 @@ int main(int, char**) {
errno = E2BIG; // something that message will never generate
const std::error_category& e_cat1 = std::system_category();
const std::string msg = e_cat1.message(-1);
// Exact message format varies by platform. We can't detect
// some of these (Musl in particular) using the preprocessor,
// so accept a few sensible messages. Newlib unfortunately
// responds with an empty message, which we probably want to
// treat as a failure code otherwise, but we can detect that
// with the preprocessor.
LIBCPP_ASSERT(msg.rfind("Error -1 occurred", 0) == 0 // AIX
|| msg.rfind("No error information", 0) == 0 // Musl
|| msg.rfind("Unknown error", 0) == 0 // Glibc
#if defined(_NEWLIB_VERSION)
|| msg.empty()
// Exact message format varies by platform.
#if defined(_AIX)
LIBCPP_ASSERT(msg.rfind("Error -1 occurred", 0) == 0);
#elif defined(_NEWLIB_VERSION)
LIBCPP_ASSERT(msg.empty());
#else
LIBCPP_ASSERT(msg.rfind("Unknown error", 0) == 0);
#endif
);
assert(errno == E2BIG);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
#include <locale>
#include <ios>
#include <cassert>
#include <cstdio>
#include <streambuf>
#include <cmath>
#include "test_macros.h"
Expand Down Expand Up @@ -8935,12 +8934,11 @@ void test4()
char str[200];
std::locale lc = std::locale::classic();
std::locale lg(lc, new my_numpunct);

std::string inf;

// This should match the underlying C library
std::sprintf(str, "%f", INFINITY);
inf = str;
#ifdef _AIX
std::string inf = "INF";
#else
std::string inf = "inf";
#endif

const my_facet f(1);
{
Expand Down Expand Up @@ -10729,27 +10727,24 @@ void test5()
std::locale lc = std::locale::classic();
std::locale lg(lc, new my_numpunct);
const my_facet f(1);

std::string nan;
std::string NaN;
std::string pnan_sign;

// The output here depends on the underlying C library, so work out what
// that does.
std::sprintf(str, "%f", std::nan(""));
nan = str;

std::sprintf(str, "%F", std::nan(""));
NaN = str;

std::sprintf(str, "%+f", std::nan(""));
if (str[0] == '+') {
pnan_sign = "+";
}

std::string nan_padding25 = std::string(25 - nan.length(), '*');
std::string pnan_padding25 = std::string(25 - nan.length() - pnan_sign.length(), '*');

#if defined(_AIX)
std::string nan= "NaNQ";
std::string NaN = "NaNQ";
std::string nan_padding25 = "*********************";
std::string pnan_sign = "+";
std::string pnan_padding25 = "********************";
#else
std::string nan= "nan";
std::string NaN = "NAN";
std::string nan_padding25 = "**********************";
#if defined(TEST_HAS_GLIBC) || defined(_WIN32)
std::string pnan_sign = "+";
std::string pnan_padding25 = "*********************";
#else
std::string pnan_sign = "";
std::string pnan_padding25 = "**********************";
#endif
#endif
{
long double v = std::nan("");
std::ios ios(0);
Expand Down
75 changes: 75 additions & 0 deletions libcxx/test/std/time/time.zone/time.zone.db/leap_seconds.pass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb

// XFAIL: libcpp-has-no-incomplete-tzdb
// XFAIL: availability-tzdb-missing

// <chrono>

// Tests the loaded leap seconds match
// https://eel.is/c++draft/time.zone.leap.overview#2
//
// At the moment of writing that list is the actual list.
// If in the future more leap seconds are added, the returned list may have more

#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <ranges>

using namespace std::literals::chrono_literals;

// The list of leap seconds matching
// https://eel.is/c++draft/time.zone.leap.overview#2
// At the moment of writing that list is the actual list in the IANA database.
// If in the future more leap seconds can be added. Testng th
static const std::array /*<std::chrono::leap_second>*/ leap_seconds = {
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1972y / std::chrono::January / 1}}, 10s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1972y / std::chrono::July / 1}}, 11s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1973y / std::chrono::January / 1}}, 12s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1974y / std::chrono::January / 1}}, 13s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1975y / std::chrono::January / 1}}, 14s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1976y / std::chrono::January / 1}}, 15s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1977y / std::chrono::January / 1}}, 16s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1978y / std::chrono::January / 1}}, 17s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1979y / std::chrono::January / 1}}, 18s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1980y / std::chrono::January / 1}}, 19s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1981y / std::chrono::July / 1}}, 20s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1982y / std::chrono::July / 1}}, 21s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1983y / std::chrono::July / 1}}, 22s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1985y / std::chrono::July / 1}}, 23s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1988y / std::chrono::January / 1}}, 24s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1990y / std::chrono::January / 1}}, 25s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1991y / std::chrono::January / 1}}, 26s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1992y / std::chrono::July / 1}}, 27s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1993y / std::chrono::July / 1}}, 28s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1994y / std::chrono::July / 1}}, 29s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1996y / std::chrono::January / 1}}, 30s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1997y / std::chrono::July / 1}}, 31s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{1999y / std::chrono::January / 1}}, 32s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2006y / std::chrono::January / 1}}, 33s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2009y / std::chrono::January / 1}}, 34s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2012y / std::chrono::July / 1}}, 35s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2015y / std::chrono::July / 1}}, 36s),
std::make_pair(std::chrono::sys_seconds{std::chrono::sys_days{2017y / std::chrono::January / 1}}, 37s)};

int main(int, const char**) {
const std::chrono::tzdb& tzdb = std::chrono::get_tzdb();

assert(tzdb.leap_seconds.size() >= leap_seconds.size());
assert((std::ranges::equal(
leap_seconds,
tzdb.leap_seconds | std::ranges::views::take(leap_seconds.size()),
[](const auto& lhs, const auto& rhs) { return lhs.first == rhs.date() && lhs.second == rhs.value(); })));

return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@ int main(int, const char**) {
assert(std::ranges::is_sorted(db.links));
assert(std::ranges::adjacent_find(db.links) == db.links.end()); // is unique?

assert(!db.leap_seconds.empty());
assert(std::ranges::is_sorted(db.leap_seconds));

return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,9 @@ int main(int, const char**) {
tzdb.version = "version";
assert(tzdb.version == "version");

[[maybe_unused]] std::vector<std::chrono::time_zone>& zones = tzdb.zones;

[[maybe_unused]] std::vector<std::chrono::time_zone>& zones = tzdb.zones;
[[maybe_unused]] std::vector<std::chrono::time_zone_link>& links = tzdb.links;

// TODO TZDB add the leap data member
[[maybe_unused]] std::vector<std::chrono::leap_second>& leap_seconds = tzdb.leap_seconds;

return 0;
}
70 changes: 70 additions & 0 deletions libcxx/test/std/time/time.zone/time.zone.leap/assign.copy.pass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb

// XFAIL: libcpp-has-no-incomplete-tzdb
// XFAIL: availability-tzdb-missing

// <chrono>

// class leap_second
// {
// leap_second& operator=(const leap_second&) = default;
//
// ...
// };

#include <chrono>
#include <memory>
#include <type_traits>
#include <cassert>

// Add the include path required by test_chrono_leap_second.h when using libc++.
// ADDITIONAL_COMPILE_FLAGS(stdlib=libc++): -I %S/../../../../../src/include
#include "test_chrono_leap_second.h"

constexpr bool test() {
std::chrono::leap_second a =
test_leap_second_create(std::chrono::sys_seconds{std::chrono::seconds{0}}, std::chrono::seconds{1});
std::chrono::leap_second b =
test_leap_second_create(std::chrono::sys_seconds{std::chrono::seconds{10}}, std::chrono::seconds{15});

// operator== only compares the date member.
assert(a.date() != b.date());
assert(a.value() != b.value());

{
std::same_as<std::chrono::leap_second&> decltype(auto) result(b = a);
assert(std::addressof(result) == std::addressof(b));

assert(a.date() == b.date());
assert(a.value() == b.value());
}

{
// Tests an rvalue uses the copy assignment.
std::same_as<std::chrono::leap_second&> decltype(auto) result(b = std::move(a));
assert(std::addressof(result) == std::addressof(b));

assert(a.date() == b.date());
assert(a.value() == b.value());
}

return true;
}

int main(int, const char**) {
static_assert(std::is_copy_assignable_v<std::chrono::leap_second>);

test();
static_assert(test());

return 0;
}
67 changes: 67 additions & 0 deletions libcxx/test/std/time/time.zone/time.zone.leap/cons.copy.pass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb

// XFAIL: libcpp-has-no-incomplete-tzdb
// XFAIL: availability-tzdb-missing

// <chrono>

// class leap_second
// {
// leap_second(const leap_second&) = default;
//
// ...
// };

#include <chrono>
#include <concepts>
#include <cassert>

// Add the include path required by test_chrono_leap_second.h when using libc++.
// ADDITIONAL_COMPILE_FLAGS(stdlib=libc++): -I %S/../../../../../src/include
#include "test_chrono_leap_second.h"

constexpr bool test() {
std::chrono::leap_second a =
test_leap_second_create(std::chrono::sys_seconds{std::chrono::seconds{0}}, std::chrono::seconds{1});

{
std::chrono::leap_second b = a;

// operator== only compares the date member.
assert(a.date() == b.date());
assert(a.value() == b.value());
}

#ifdef _LIBCPP_VERSION
{
// Tests an rvalue uses the copy constructor.
// Since implementations are allowed to add additional constructors this is
// a libc++ specific test.
std::chrono::leap_second b = std::move(a);

// operator== only compares the date member.
assert(a.date() == b.date());
assert(a.value() == b.value());
}
#endif // _LIBCPP_VERSION

return true;
}

int main(int, const char**) {
static_assert(std::copy_constructible<std::chrono::leap_second>);

test();
static_assert(test());

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb

// XFAIL: libcpp-has-no-incomplete-tzdb
// XFAIL: availability-tzdb-missing

// <chrono>

// class leap_second;

// constexpr sys_seconds date() const noexcept;

#include <cassert>
#include <chrono>

#include "test_macros.h"

// Add the include path required by test_chrono_leap_second.h when using libc++.
// ADDITIONAL_COMPILE_FLAGS(stdlib=libc++): -I %S/../../../../../../src/include
#include "test_chrono_leap_second.h"

constexpr void test(const std::chrono::leap_second leap_second, std::chrono::sys_seconds expected) {
std::same_as<std::chrono::sys_seconds> auto date = leap_second.date();
assert(date == expected);
static_assert(noexcept(leap_second.date()));
}

constexpr bool test() {
test(test_leap_second_create(std::chrono::sys_seconds{std::chrono::seconds{0}}, std::chrono::seconds{1}),
std::chrono::sys_seconds{std::chrono::seconds{0}});

return true;
}

int main(int, const char**) {
test();
static_assert(test());

// test with the real tzdb
const std::chrono::tzdb& tzdb = std::chrono::get_tzdb();
assert(!tzdb.leap_seconds.empty());
test(tzdb.leap_seconds[0], tzdb.leap_seconds[0].date());

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb

// XFAIL: libcpp-has-no-incomplete-tzdb
// XFAIL: availability-tzdb-missing

// <chrono>

// class leap_second;

// constexpr seconds value() const noexcept;

#include <cassert>
#include <chrono>

#include "test_macros.h"

// Add the include path required by test_chrono_leap_second.h when using libc++.
// ADDITIONAL_COMPILE_FLAGS(stdlib=libc++): -I %S/../../../../../../src/include
#include "test_chrono_leap_second.h"

constexpr void test(const std::chrono::leap_second leap_second, std::chrono::seconds expected) {
std::same_as<std::chrono::seconds> auto value = leap_second.value();
assert(value == expected);
static_assert(noexcept(leap_second.value()));
}

constexpr bool test() {
test(test_leap_second_create(std::chrono::sys_seconds{std::chrono::seconds{0}}, std::chrono::seconds{1}),
std::chrono::seconds{1});

return true;
}

int main(int, const char**) {
test();
static_assert(test());

// test with the real tzdb
const std::chrono::tzdb& tzdb = std::chrono::get_tzdb();
assert(!tzdb.leap_seconds.empty());
test(tzdb.leap_seconds[0], tzdb.leap_seconds[0].value());

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb

// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME

// XFAIL: libcpp-has-no-incomplete-tzdb
// XFAIL: availability-tzdb-missing

// <chrono>

// class leap_second;

//constexpr bool operator==(const leap_second& x, const leap_second& y); // C++20
//constexpr strong_ordering operator<=>(const leap_second& x, const leap_second& y);
//
//template<class Duration>
// constexpr bool operator==(const leap_second& x, const sys_time<Duration>& y);
//template<class Duration>
// constexpr bool operator< (const leap_second& x, const sys_time<Duration>& y);
//template<class Duration>
// constexpr bool operator< (const sys_time<Duration>& x, const leap_second& y);
//template<class Duration>
// constexpr bool operator> (const leap_second& x, const sys_time<Duration>& y);
//template<class Duration>
// constexpr bool operator> (const sys_time<Duration>& x, const leap_second& y);
//template<class Duration>
// constexpr bool operator<=(const leap_second& x, const sys_time<Duration>& y);
//template<class Duration>
// constexpr bool operator<=(const sys_time<Duration>& x, const leap_second& y);
//template<class Duration>
// constexpr bool operator>=(const leap_second& x, const sys_time<Duration>& y);
//template<class Duration>
// constexpr bool operator>=(const sys_time<Duration>& x, const leap_second& y);
//template<class Duration>
// requires three_way_comparable_with<sys_seconds, sys_time<Duration>>
// constexpr auto operator<=>(const leap_second& x, const sys_time<Duration>& y);

#include <cassert>
#include <chrono>

#include "test_macros.h"
#include "test_comparisons.h"

// Add the include path required by test_chrono_leap_second.h when using libc++.
// ADDITIONAL_COMPILE_FLAGS(stdlib=libc++): -I %S/../../../../../../src/include
#include "test_chrono_leap_second.h"

constexpr void test(const std::chrono::leap_second lhs, const std::chrono::leap_second rhs) {
AssertOrderReturn<std::strong_ordering, std::chrono::leap_second>();
assert(testOrder(lhs, rhs, std::strong_ordering::less));

AssertOrderReturn<std::strong_ordering, std::chrono::leap_second, std::chrono::sys_seconds>();
assert(testOrder(lhs, rhs.date(), std::strong_ordering::less));

AssertOrderReturn<std::strong_ordering, std::chrono::sys_seconds, std::chrono::leap_second>();
assert(testOrder(lhs.date(), rhs, std::strong_ordering::less));
}

constexpr bool test() {
test(test_leap_second_create(std::chrono::sys_seconds{std::chrono::seconds{0}}, std::chrono::seconds{1}),
test_leap_second_create(std::chrono::sys_seconds{std::chrono::seconds{1}}, std::chrono::seconds{2}));

return true;
}

int main(int, const char**) {
test();
static_assert(test());

// test with the real tzdb
const std::chrono::tzdb& tzdb = std::chrono::get_tzdb();
assert(tzdb.leap_seconds.size() > 2);
test(tzdb.leap_seconds[0], tzdb.leap_seconds[1]);

return 0;
}
52 changes: 52 additions & 0 deletions libcxx/test/support/test_chrono_leap_second.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// -*- 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
//
//===----------------------------------------------------------------------===//

#ifndef SUPPORT_TEST_CHRONO_LEAP_SECOND_HPP
#define SUPPORT_TEST_CHRONO_LEAP_SECOND_HPP

// Contains helper functions to create a std::chrono::leap_second.
//
// Since the standard doesn't specify how a @ref std::chrono::leap_second is
// constructed this is implementation defined. To make the public API tests of
// the class generic this header defines helper functions to create the
// required object.
//
// Note This requires every standard library implementation to write their own
// helper function. Vendors are encouraged to create a pull request at
// https://github.com/llvm/llvm-project so their specific implementation can be
// part of this file.

#include "test_macros.h"

#if TEST_STD_VER < 20
# error "The format header requires at least C++20"
#endif

#include <chrono>

#ifdef _LIBCPP_VERSION

// In order to find this include the calling test needs to provide this path in
// the search path. Typically this looks like:
// ADDITIONAL_COMPILE_FLAGS(stdlib=libc++): -I %S/../../../../../../src/include
// where the number of `../` sequences depends on the subdirectory level of the
// test.
# include "tzdb/leap_second_private.h" // Header in the dylib

inline constexpr std::chrono::leap_second
test_leap_second_create(const std::chrono::sys_seconds& date, const std::chrono::seconds& value) {
return std::chrono::leap_second{std::chrono::leap_second::__constructor_tag{}, date, value};
}

#else // _LIBCPP_VERSION
# error \
"Please create a vendor specific version of the test typedef and file a PR at https://github.com/llvm/llvm-project"
#endif // _LIBCPP_VERSION

#endif // SUPPORT_TEST_CHRONO_LEAP_SECOND_HPP
7 changes: 6 additions & 1 deletion llvm/include/llvm/TextAPI/Record.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class Record {
public:
Record() = default;
Record(StringRef Name, RecordLinkage Linkage, SymbolFlags Flags)
: Name(Name), Linkage(Linkage), Flags(mergeFlags(Flags, Linkage)) {}
: Name(Name), Linkage(Linkage), Flags(mergeFlags(Flags, Linkage)),
Verified(false) {}

bool isWeakDefined() const {
return (Flags & SymbolFlags::WeakDefined) == SymbolFlags::WeakDefined;
Expand Down Expand Up @@ -79,6 +80,9 @@ class Record {
bool isExported() const { return Linkage >= RecordLinkage::Rexported; }
bool isRexported() const { return Linkage == RecordLinkage::Rexported; }

bool isVerified() const { return Verified; }
void setVerify(bool V = true) { Verified = V; }

StringRef getName() const { return Name; }
SymbolFlags getFlags() const { return Flags; }

Expand All @@ -89,6 +93,7 @@ class Record {
StringRef Name;
RecordLinkage Linkage;
SymbolFlags Flags;
bool Verified;

friend class RecordsSlice;
};
Expand Down
8 changes: 4 additions & 4 deletions llvm/lib/TextAPI/TextStub.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,16 +1079,16 @@ Expected<FileType> TextAPIReader::canRead(MemoryBufferRef InputBuffer) {
if (!TAPIFile.ends_with("..."))
return createStringError(std::errc::not_supported, "unsupported file type");

if (TAPIFile.starts_with("--- !tapi-tbd\n"))
if (TAPIFile.starts_with("--- !tapi-tbd"))
return FileType::TBD_V4;

if (TAPIFile.starts_with("--- !tapi-tbd-v3\n"))
if (TAPIFile.starts_with("--- !tapi-tbd-v3"))
return FileType::TBD_V3;

if (TAPIFile.starts_with("--- !tapi-tbd-v2\n"))
if (TAPIFile.starts_with("--- !tapi-tbd-v2"))
return FileType::TBD_V2;

if (TAPIFile.starts_with("--- !tapi-tbd-v1\n") ||
if (TAPIFile.starts_with("--- !tapi-tbd-v1") ||
TAPIFile.starts_with("---\narchs:"))
return FileType::TBD_V1;

Expand Down
41 changes: 31 additions & 10 deletions llvm/lib/Transforms/Scalar/DFAJumpThreading.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ static cl::opt<bool>
cl::desc("View the CFG before DFA Jump Threading"),
cl::Hidden, cl::init(false));

static cl::opt<bool> EarlyExitHeuristic(
"dfa-early-exit-heuristic",
cl::desc("Exit early if an unpredictable value come from the same loop"),
cl::Hidden, cl::init(true));

static cl::opt<unsigned> MaxPathLength(
"dfa-max-path-length",
cl::desc("Max number of blocks searched to find a threading path"),
Expand Down Expand Up @@ -405,7 +410,7 @@ struct MainSwitch {
///
/// Also, collect select instructions to unfold.
bool isCandidate(const SwitchInst *SI) {
std::deque<Value *> Q;
std::deque<std::pair<Value *, BasicBlock *>> Q;
SmallSet<Value *, 16> SeenValues;
SelectInsts.clear();

Expand All @@ -415,25 +420,28 @@ struct MainSwitch {
return false;

// The switch must be in a loop.
if (!LI->getLoopFor(SI->getParent()))
const Loop *L = LI->getLoopFor(SI->getParent());
if (!L)
return false;

addToQueue(SICond, Q, SeenValues);
addToQueue(SICond, nullptr, Q, SeenValues);

while (!Q.empty()) {
Value *Current = Q.front();
Value *Current = Q.front().first;
BasicBlock *CurrentIncomingBB = Q.front().second;
Q.pop_front();

if (auto *Phi = dyn_cast<PHINode>(Current)) {
for (Value *Incoming : Phi->incoming_values()) {
addToQueue(Incoming, Q, SeenValues);
for (BasicBlock *IncomingBB : Phi->blocks()) {
Value *Incoming = Phi->getIncomingValueForBlock(IncomingBB);
addToQueue(Incoming, IncomingBB, Q, SeenValues);
}
LLVM_DEBUG(dbgs() << "\tphi: " << *Phi << "\n");
} else if (SelectInst *SelI = dyn_cast<SelectInst>(Current)) {
if (!isValidSelectInst(SelI))
return false;
addToQueue(SelI->getTrueValue(), Q, SeenValues);
addToQueue(SelI->getFalseValue(), Q, SeenValues);
addToQueue(SelI->getTrueValue(), CurrentIncomingBB, Q, SeenValues);
addToQueue(SelI->getFalseValue(), CurrentIncomingBB, Q, SeenValues);
LLVM_DEBUG(dbgs() << "\tselect: " << *SelI << "\n");
if (auto *SelIUse = dyn_cast<PHINode>(SelI->user_back()))
SelectInsts.push_back(SelectInstToUnfold(SelI, SelIUse));
Expand All @@ -446,18 +454,31 @@ struct MainSwitch {
// initial switch values that can be ignored (they will hit the
// unthreaded switch) but this assumption will get checked later after
// paths have been enumerated (in function getStateDefMap).

// If the unpredictable value comes from the same inner loop it is
// likely that it will also be on the enumerated paths, causing us to
// exit after we have enumerated all the paths. This heuristic save
// compile time because a search for all the paths can become expensive.
if (EarlyExitHeuristic &&
L->contains(LI->getLoopFor(CurrentIncomingBB))) {
LLVM_DEBUG(dbgs()
<< "\tExiting early due to unpredictability heuristic.\n");
return false;
}

continue;
}
}

return true;
}

void addToQueue(Value *Val, std::deque<Value *> &Q,
void addToQueue(Value *Val, BasicBlock *BB,
std::deque<std::pair<Value *, BasicBlock *>> &Q,
SmallSet<Value *, 16> &SeenValues) {
if (SeenValues.contains(Val))
return;
Q.push_back(Val);
Q.push_back({Val, BB});
SeenValues.insert(Val);
}

Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/DFAJumpThreading/dfa-unfold-select.ll
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -S -passes=dfa-jump-threading %s | FileCheck %s
; RUN: opt -S -passes=dfa-jump-threading -dfa-early-exit-heuristic=false %s | FileCheck %s

; These tests check if selects are unfolded properly for jump threading
; opportunities. There are three different patterns to consider:
Expand Down
Loading