| //===- DylibVerifier.cpp ----------------------------------------*- C++--*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/InstallAPI/DylibVerifier.h" |
| #include "DiagnosticBuilderWrappers.h" |
| #include "clang/InstallAPI/FrontendRecords.h" |
| #include "clang/InstallAPI/InstallAPIDiagnostic.h" |
| #include "llvm/Demangle/Demangle.h" |
| #include "llvm/TextAPI/DylibReader.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 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; |
| |
| // Whether Decl is inlined. |
| bool Inlined = false; |
| }; |
| |
| struct DylibVerifier::DWARFContext { |
| // Track whether DSYM parsing has already been attempted to avoid re-parsing. |
| bool ParsedDSYM{false}; |
| |
| // Lookup table for source locations by symbol name. |
| DylibReader::SymbolToSourceLocMap SourceLocs{}; |
| }; |
| |
| static bool isCppMangled(StringRef Name) { |
| // InstallAPI currently only supports itanium manglings. |
| return (Name.starts_with("_Z") || Name.starts_with("__Z") || |
| Name.starts_with("___Z")); |
| } |
| |
| static std::string demangle(StringRef Name) { |
| // InstallAPI currently only supports itanium manglings. |
| if (!isCppMangled(Name)) |
| return Name.str(); |
| char *Result = llvm::itaniumDemangle(Name); |
| if (!Result) |
| return Name.str(); |
| |
| std::string Demangled(Result); |
| free(Result); |
| return Demangled; |
| } |
| |
| std::string DylibVerifier::getAnnotatedName(const Record *R, |
| SymbolContext &SymCtx, |
| bool ValidSourceLoc) { |
| assert(!SymCtx.SymbolName.empty() && "Expected symbol name"); |
| |
| const StringRef SymbolName = SymCtx.SymbolName; |
| std::string PrettyName = |
| (Demangle && (SymCtx.Kind == EncodeKind::GlobalSymbol)) |
| ? demangle(SymbolName) |
| : SymbolName.str(); |
| |
| 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. |
| switch (SymCtx.ObjCIFKind) { |
| default: |
| break; |
| case ObjCIFSymbolKind::EHType: |
| return Annotation + "Exception Type of " + PrettyName; |
| case ObjCIFSymbolKind::MetaClass: |
| return Annotation + "Metaclass of " + PrettyName; |
| case ObjCIFSymbolKind::Class: |
| return Annotation + "Class of " + PrettyName; |
| } |
| |
| // 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) { |
| StringRef PrettyNameRef(PrettyName); |
| if ((SymCtx.Kind == EncodeKind::GlobalSymbol) && |
| !isCppMangled(SymbolName) && PrettyNameRef.starts_with("_")) |
| return Annotation + PrettyNameRef.drop_front(1).str(); |
| return Annotation + PrettyName; |
| } |
| |
| switch (SymCtx.Kind) { |
| case EncodeKind::GlobalSymbol: |
| return Annotation + PrettyName; |
| case EncodeKind::ObjectiveCInstanceVariable: |
| return Annotation + "(ObjC IVar) " + PrettyName; |
| case EncodeKind::ObjectiveCClass: |
| return Annotation + "(ObjC Class) " + PrettyName; |
| case EncodeKind::ObjectiveCClassEHType: |
| return Annotation + "(ObjC Class EH) " + PrettyName; |
| } |
| |
| llvm_unreachable("unexpected case for EncodeKind"); |
| } |
| |
| 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; |
| } |
| // __private_extern__ is a deprecated specifier that clang does not |
| // respect in all contexts, it should just be considered hidden for InstallAPI. |
| static bool shouldIgnorePrivateExternAttr(const Decl *D) { |
| if (const FunctionDecl *FD = cast<FunctionDecl>(D)) |
| return FD->getStorageClass() == StorageClass::SC_PrivateExtern; |
| if (const VarDecl *VD = cast<VarDecl>(D)) |
| return VD->getStorageClass() == StorageClass::SC_PrivateExtern; |
| |
| return false; |
| } |
| |
| Record *findRecordFromSlice(const RecordsSlice *Slice, StringRef Name, |
| EncodeKind Kind) { |
| switch (Kind) { |
| case EncodeKind::GlobalSymbol: |
| return Slice->findGlobal(Name); |
| case EncodeKind::ObjectiveCInstanceVariable: |
| return Slice->findObjCIVar(Name.contains('.'), Name); |
| case EncodeKind::ObjectiveCClass: |
| case EncodeKind::ObjectiveCClassEHType: |
| return Slice->findObjCInterface(Name); |
| } |
| llvm_unreachable("unexpected end when finding record"); |
| } |
| |
| 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); |
| } |
| |
| bool DylibVerifier::shouldIgnoreObsolete(const Record *R, SymbolContext &SymCtx, |
| const Record *DR) { |
| if (!SymCtx.FA->Avail.isObsoleted()) |
| return false; |
| |
| if (Zippered) |
| DeferredZipperedSymbols[SymCtx.SymbolName].emplace_back(ZipperedDeclSource{ |
| SymCtx.FA, &Ctx.Diag->getSourceManager(), Ctx.Target}); |
| return true; |
| } |
| |
| bool DylibVerifier::shouldIgnoreReexport(const Record *R, |
| SymbolContext &SymCtx) const { |
| StringRef SymName = SymCtx.SymbolName; |
| // Linker directive symbols can never be ignored. |
| if (SymName.starts_with("$ld$")) |
| return false; |
| |
| if (Reexports.empty()) |
| return false; |
| |
| for (const InterfaceFile &Lib : Reexports) { |
| if (!Lib.hasTarget(Ctx.Target)) |
| continue; |
| if (auto Sym = Lib.getSymbol(SymCtx.Kind, SymName, SymCtx.ObjCIFKind)) |
| if ((*Sym)->hasTarget(Ctx.Target)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool DylibVerifier::shouldIgnoreInternalZipperedSymbol( |
| const Record *R, const SymbolContext &SymCtx) const { |
| if (!Zippered) |
| return false; |
| |
| return Exports->findSymbol(SymCtx.Kind, SymCtx.SymbolName, |
| SymCtx.ObjCIFKind) != nullptr; |
| } |
| |
| bool DylibVerifier::shouldIgnoreZipperedAvailability(const Record *R, |
| SymbolContext &SymCtx) { |
| if (!(Zippered && SymCtx.FA->Avail.isUnavailable())) |
| return false; |
| |
| // Collect source location incase there is an exported symbol to diagnose |
| // during `verifyRemainingSymbols`. |
| DeferredZipperedSymbols[SymCtx.SymbolName].emplace_back( |
| ZipperedDeclSource{SymCtx.FA, SourceManagers.back().get(), Ctx.Target}); |
| |
| return true; |
| } |
| |
| bool DylibVerifier::compareObjCInterfaceSymbols(const Record *R, |
| SymbolContext &SymCtx, |
| const ObjCInterfaceRecord *DR) { |
| const bool IsDeclVersionComplete = |
| ((SymCtx.ObjCIFKind & ObjCIFSymbolKind::Class) == |
| ObjCIFSymbolKind::Class) && |
| ((SymCtx.ObjCIFKind & ObjCIFSymbolKind::MetaClass) == |
| ObjCIFSymbolKind::MetaClass); |
| |
| const bool IsDylibVersionComplete = DR->isCompleteInterface(); |
| |
| // The common case, a complete ObjCInterface. |
| if (IsDeclVersionComplete && IsDylibVersionComplete) |
| return true; |
| |
| auto PrintDiagnostic = [&](auto SymLinkage, const Record *Record, |
| StringRef SymName, bool PrintAsWarning = false) { |
| if (SymLinkage == RecordLinkage::Unknown) |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, PrintAsWarning |
| ? diag::warn_library_missing_symbol |
| : diag::err_library_missing_symbol) |
| << SymName; |
| }); |
| else |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, PrintAsWarning |
| ? diag::warn_library_hidden_symbol |
| : diag::err_library_hidden_symbol) |
| << SymName; |
| }); |
| }; |
| |
| if (IsDeclVersionComplete) { |
| // The decl represents a complete ObjCInterface, but the symbols in the |
| // dylib do not. Determine which symbol is missing. To keep older projects |
| // building, treat this as a warning. |
| if (!DR->isExportedSymbol(ObjCIFSymbolKind::Class)) { |
| SymCtx.ObjCIFKind = ObjCIFSymbolKind::Class; |
| PrintDiagnostic(DR->getLinkageForSymbol(ObjCIFSymbolKind::Class), R, |
| getAnnotatedName(R, SymCtx), |
| /*PrintAsWarning=*/true); |
| } |
| if (!DR->isExportedSymbol(ObjCIFSymbolKind::MetaClass)) { |
| SymCtx.ObjCIFKind = ObjCIFSymbolKind::MetaClass; |
| PrintDiagnostic(DR->getLinkageForSymbol(ObjCIFSymbolKind::MetaClass), R, |
| getAnnotatedName(R, SymCtx), |
| /*PrintAsWarning=*/true); |
| } |
| return true; |
| } |
| |
| if (DR->isExportedSymbol(SymCtx.ObjCIFKind)) { |
| if (!IsDylibVersionComplete) { |
| // Both the declaration and dylib have a non-complete interface. |
| SymCtx.Kind = EncodeKind::GlobalSymbol; |
| SymCtx.SymbolName = R->getName(); |
| } |
| return true; |
| } |
| |
| // At this point that means there was not a matching class symbol |
| // to represent the one discovered as a declaration. |
| PrintDiagnostic(DR->getLinkageForSymbol(SymCtx.ObjCIFKind), R, |
| SymCtx.SymbolName); |
| return false; |
| } |
| |
| DylibVerifier::Result DylibVerifier::compareVisibility(const Record *R, |
| SymbolContext &SymCtx, |
| const Record *DR) { |
| |
| if (R->isExported()) { |
| if (!DR) { |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_library_missing_symbol) |
| << getAnnotatedName(R, SymCtx); |
| }); |
| return Result::Invalid; |
| } |
| if (DR->isInternal()) { |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_library_hidden_symbol) |
| << getAnnotatedName(R, SymCtx); |
| }); |
| return Result::Invalid; |
| } |
| } |
| |
| // Emit a diagnostic for hidden declarations with external symbols, except |
| // when theres an inlined attribute. |
| if ((R->isInternal() && !SymCtx.Inlined) && DR && DR->isExported()) { |
| |
| if (Mode == VerificationMode::ErrorsOnly) |
| return Result::Ignore; |
| |
| if (shouldIgnorePrivateExternAttr(SymCtx.FA->D)) |
| return Result::Ignore; |
| |
| if (shouldIgnoreInternalZipperedSymbol(R, SymCtx)) |
| return Result::Ignore; |
| |
| unsigned ID; |
| Result Outcome; |
| if (Mode == VerificationMode::ErrorsAndWarnings) { |
| ID = diag::warn_header_hidden_symbol; |
| Outcome = Result::Ignore; |
| } else { |
| ID = diag::err_header_hidden_symbol; |
| Outcome = Result::Invalid; |
| } |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, ID) << getAnnotatedName(R, SymCtx); |
| }); |
| return Outcome; |
| } |
| |
| if (R->isInternal()) |
| return Result::Ignore; |
| |
| return Result::Valid; |
| } |
| |
| DylibVerifier::Result DylibVerifier::compareAvailability(const Record *R, |
| SymbolContext &SymCtx, |
| const Record *DR) { |
| if (!SymCtx.FA->Avail.isUnavailable()) |
| return Result::Valid; |
| |
| if (shouldIgnoreZipperedAvailability(R, SymCtx)) |
| return Result::Ignore; |
| |
| const bool IsDeclAvailable = SymCtx.FA->Avail.isUnavailable(); |
| |
| switch (Mode) { |
| case VerificationMode::ErrorsAndWarnings: |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, diag::warn_header_availability_mismatch) |
| << getAnnotatedName(R, SymCtx) << IsDeclAvailable << IsDeclAvailable; |
| }); |
| return Result::Ignore; |
| case VerificationMode::Pedantic: |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_header_availability_mismatch) |
| << getAnnotatedName(R, SymCtx) << IsDeclAvailable << IsDeclAvailable; |
| }); |
| return Result::Invalid; |
| case VerificationMode::ErrorsOnly: |
| return Result::Ignore; |
| case VerificationMode::Invalid: |
| llvm_unreachable("Unexpected verification mode symbol verification"); |
| } |
| llvm_unreachable("Unexpected verification mode symbol verification"); |
| } |
| |
| bool DylibVerifier::compareSymbolFlags(const Record *R, SymbolContext &SymCtx, |
| const Record *DR) { |
| if (DR->isThreadLocalValue() && !R->isThreadLocalValue()) { |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_dylib_symbol_flags_mismatch) |
| << getAnnotatedName(DR, SymCtx) << DR->isThreadLocalValue(); |
| }); |
| return false; |
| } |
| if (!DR->isThreadLocalValue() && R->isThreadLocalValue()) { |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_header_symbol_flags_mismatch) |
| << getAnnotatedName(R, SymCtx) << R->isThreadLocalValue(); |
| }); |
| return false; |
| } |
| |
| if (DR->isWeakDefined() && !R->isWeakDefined()) { |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_dylib_symbol_flags_mismatch) |
| << getAnnotatedName(DR, SymCtx) << R->isWeakDefined(); |
| }); |
| return false; |
| } |
| if (!DR->isWeakDefined() && R->isWeakDefined()) { |
| Ctx.emitDiag([&]() { |
| Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_header_symbol_flags_mismatch) |
| << getAnnotatedName(R, SymCtx) << R->isWeakDefined(); |
| }); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| 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.isUnavailable() && |
| !SymCtx.FA->Avail.isObsoleted()) { |
| addSymbol(R, SymCtx); |
| } |
| return Ctx.FrontendState; |
| } |
| |
| if (shouldIgnoreReexport(R, SymCtx)) { |
| updateState(Result::Ignore); |
| return Ctx.FrontendState; |
| } |
| |
| Record *DR = |
| findRecordFromSlice(Ctx.DylibSlice, SymCtx.SymbolName, SymCtx.Kind); |
| if (DR) |
| DR->setVerify(); |
| |
| if (shouldIgnoreObsolete(R, SymCtx, DR)) { |
| updateState(Result::Ignore); |
| return Ctx.FrontendState; |
| } |
| |
| // Unavailable declarations don't need matching symbols. |
| if (SymCtx.FA->Avail.isUnavailable() && (!DR || DR->isInternal())) { |
| updateState(Result::Valid); |
| return Ctx.FrontendState; |
| } |
| |
| Result VisibilityCheck = compareVisibility(R, SymCtx, DR); |
| if (VisibilityCheck != Result::Valid) { |
| updateState(VisibilityCheck); |
| return Ctx.FrontendState; |
| } |
| |
| // All missing symbol cases to diagnose have been handled now. |
| if (!DR) { |
| updateState(Result::Ignore); |
| return Ctx.FrontendState; |
| } |
| |
| // Check for mismatching ObjC interfaces. |
| if (SymCtx.ObjCIFKind != ObjCIFSymbolKind::None) { |
| if (!compareObjCInterfaceSymbols( |
| R, SymCtx, Ctx.DylibSlice->findObjCInterface(DR->getName()))) { |
| updateState(Result::Invalid); |
| return Ctx.FrontendState; |
| } |
| } |
| |
| Result AvailabilityCheck = compareAvailability(R, SymCtx, DR); |
| if (AvailabilityCheck != Result::Valid) { |
| updateState(AvailabilityCheck); |
| return Ctx.FrontendState; |
| } |
| |
| if (!compareSymbolFlags(R, SymCtx, DR)) { |
| updateState(Result::Invalid); |
| return Ctx.FrontendState; |
| } |
| |
| addSymbol(R, SymCtx); |
| updateState(Result::Valid); |
| return Ctx.FrontendState; |
| } |
| |
| bool DylibVerifier::canVerify() { |
| return Ctx.FrontendState != Result::NoVerify; |
| } |
| |
| void DylibVerifier::assignSlice(const Target &T) { |
| assert(T == Ctx.Target && "Active targets should match."); |
| if (Dylib.empty()) |
| return; |
| |
| // Note: there are no reexport slices with binaries, as opposed to TBD files, |
| // so it can be assumed that the target match is the active top-level library. |
| auto It = find_if( |
| Dylib, [&T](const auto &Slice) { return T == Slice->getTarget(); }); |
| |
| assert(It != Dylib.end() && "Target slice should always exist."); |
| Ctx.DylibSlice = It->get(); |
| } |
| |
| void DylibVerifier::setTarget(const Target &T) { |
| Ctx.Target = T; |
| Ctx.DiscoveredFirstError = false; |
| if (Dylib.empty()) { |
| updateState(Result::NoVerify); |
| return; |
| } |
| updateState(Result::Ignore); |
| assignSlice(T); |
| } |
| |
| void DylibVerifier::setSourceManager( |
| IntrusiveRefCntPtr<SourceManager> SourceMgr) { |
| if (!Ctx.Diag) |
| return; |
| SourceManagers.push_back(std::move(SourceMgr)); |
| Ctx.Diag->setSourceManager(SourceManagers.back().get()); |
| } |
| |
| 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{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); |
| |
| SymCtx.Kind = R->hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType |
| : EncodeKind::ObjectiveCClass; |
| 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.Kind = Sym.Kind; |
| SymCtx.FA = FA; |
| SymCtx.Inlined = R->isInlined(); |
| return verifyImpl(R, SymCtx); |
| } |
| |
| void DylibVerifier::VerifierContext::emitDiag(llvm::function_ref<void()> Report, |
| RecordLoc *Loc) { |
| if (!DiscoveredFirstError) { |
| Diag->Report(diag::warn_target) |
| << (PrintArch ? getArchitectureName(Target.Arch) |
| : getTargetTripleName(Target)); |
| DiscoveredFirstError = true; |
| } |
| if (Loc && Loc->isValid()) |
| llvm::errs() << Loc->File << ":" << Loc->Line << ":" << 0 << ": "; |
| |
| Report(); |
| } |
| |
| // The existence of weak-defined RTTI can not always be inferred from the |
| // header files because they can be generated as part of an implementation |
| // file. |
| // InstallAPI doesn't warn about weak-defined RTTI, because this doesn't affect |
| // static linking and so can be ignored for text-api files. |
| static bool shouldIgnoreCpp(StringRef Name, bool IsWeakDef) { |
| return (IsWeakDef && |
| (Name.starts_with("__ZTI") || Name.starts_with("__ZTS"))); |
| } |
| void DylibVerifier::visitSymbolInDylib(const Record &R, SymbolContext &SymCtx) { |
| // Undefined symbols should not be in InstallAPI generated text-api files. |
| if (R.isUndefined()) { |
| updateState(Result::Valid); |
| return; |
| } |
| |
| // Internal symbols should not be in InstallAPI generated text-api files. |
| if (R.isInternal()) { |
| updateState(Result::Valid); |
| return; |
| } |
| |
| // Allow zippered symbols with potentially mismatching availability |
| // between macOS and macCatalyst in the final text-api file. |
| const StringRef SymbolName(SymCtx.SymbolName); |
| if (const Symbol *Sym = Exports->findSymbol(SymCtx.Kind, SymCtx.SymbolName, |
| SymCtx.ObjCIFKind)) { |
| if (Sym->hasArchitecture(Ctx.Target.Arch)) { |
| updateState(Result::Ignore); |
| return; |
| } |
| } |
| |
| const bool IsLinkerSymbol = SymbolName.starts_with("$ld$"); |
| |
| if (R.isVerified()) { |
| // Check for unavailable symbols. |
| // This should only occur in the zippered case where we ignored |
| // availability until all headers have been parsed. |
| auto It = DeferredZipperedSymbols.find(SymCtx.SymbolName); |
| if (It == DeferredZipperedSymbols.end()) { |
| updateState(Result::Valid); |
| return; |
| } |
| |
| ZipperedDeclSources Locs; |
| for (const ZipperedDeclSource &ZSource : It->second) { |
| if (ZSource.FA->Avail.isObsoleted()) { |
| updateState(Result::Ignore); |
| return; |
| } |
| if (ZSource.T.Arch != Ctx.Target.Arch) |
| continue; |
| Locs.emplace_back(ZSource); |
| } |
| assert(Locs.size() == 2 && "Expected two decls for zippered symbol"); |
| |
| // Print violating declarations per platform. |
| for (const ZipperedDeclSource &ZSource : Locs) { |
| unsigned DiagID = 0; |
| if (Mode == VerificationMode::Pedantic || IsLinkerSymbol) { |
| updateState(Result::Invalid); |
| DiagID = diag::err_header_availability_mismatch; |
| } else if (Mode == VerificationMode::ErrorsAndWarnings) { |
| updateState(Result::Ignore); |
| DiagID = diag::warn_header_availability_mismatch; |
| } else { |
| updateState(Result::Ignore); |
| return; |
| } |
| // Bypass emitDiag banner and print the target everytime. |
| Ctx.Diag->setSourceManager(ZSource.SrcMgr); |
| Ctx.Diag->Report(diag::warn_target) << getTargetTripleName(ZSource.T); |
| Ctx.Diag->Report(ZSource.FA->Loc, DiagID) |
| << getAnnotatedName(&R, SymCtx) << ZSource.FA->Avail.isUnavailable() |
| << ZSource.FA->Avail.isUnavailable(); |
| } |
| return; |
| } |
| |
| if (shouldIgnoreCpp(SymbolName, R.isWeakDefined())) { |
| updateState(Result::Valid); |
| return; |
| } |
| |
| if (Aliases.count({SymbolName.str(), SymCtx.Kind})) { |
| updateState(Result::Valid); |
| return; |
| } |
| |
| // All checks at this point classify as some kind of violation. |
| // The different verification modes dictate whether they are reported to the |
| // user. |
| if (IsLinkerSymbol || (Mode > VerificationMode::ErrorsOnly)) |
| accumulateSrcLocForDylibSymbols(); |
| RecordLoc Loc = DWARFCtx->SourceLocs.lookup(SymCtx.SymbolName); |
| |
| // Regardless of verification mode, error out on mismatched special linker |
| // symbols. |
| if (IsLinkerSymbol) { |
| Ctx.emitDiag( |
| [&]() { |
| Ctx.Diag->Report(diag::err_header_symbol_missing) |
| << getAnnotatedName(&R, SymCtx, Loc.isValid()); |
| }, |
| &Loc); |
| updateState(Result::Invalid); |
| return; |
| } |
| |
| // Missing declarations for exported symbols are hard errors on Pedantic mode. |
| if (Mode == VerificationMode::Pedantic) { |
| Ctx.emitDiag( |
| [&]() { |
| Ctx.Diag->Report(diag::err_header_symbol_missing) |
| << getAnnotatedName(&R, SymCtx, Loc.isValid()); |
| }, |
| &Loc); |
| updateState(Result::Invalid); |
| return; |
| } |
| |
| // Missing declarations for exported symbols are warnings on ErrorsAndWarnings |
| // mode. |
| if (Mode == VerificationMode::ErrorsAndWarnings) { |
| Ctx.emitDiag( |
| [&]() { |
| Ctx.Diag->Report(diag::warn_header_symbol_missing) |
| << getAnnotatedName(&R, SymCtx, Loc.isValid()); |
| }, |
| &Loc); |
| updateState(Result::Ignore); |
| return; |
| } |
| |
| // Missing declarations are dropped for ErrorsOnly mode. It is the last |
| // remaining mode. |
| updateState(Result::Ignore); |
| return; |
| } |
| |
| void DylibVerifier::visitGlobal(const GlobalRecord &R) { |
| SymbolContext SymCtx; |
| SimpleSymbol Sym = parseSymbol(R.getName()); |
| SymCtx.SymbolName = Sym.Name; |
| SymCtx.Kind = Sym.Kind; |
| visitSymbolInDylib(R, SymCtx); |
| } |
| |
| void DylibVerifier::visitObjCIVar(const ObjCIVarRecord &R, |
| const StringRef Super) { |
| SymbolContext SymCtx; |
| SymCtx.SymbolName = ObjCIVarRecord::createScopedName(Super, R.getName()); |
| SymCtx.Kind = EncodeKind::ObjectiveCInstanceVariable; |
| visitSymbolInDylib(R, SymCtx); |
| } |
| |
| void DylibVerifier::accumulateSrcLocForDylibSymbols() { |
| if (DSYMPath.empty()) |
| return; |
| |
| assert(DWARFCtx != nullptr && "Expected an initialized DWARFContext"); |
| if (DWARFCtx->ParsedDSYM) |
| return; |
| DWARFCtx->ParsedDSYM = true; |
| DWARFCtx->SourceLocs = |
| DylibReader::accumulateSourceLocFromDSYM(DSYMPath, Ctx.Target); |
| } |
| |
| void DylibVerifier::visitObjCInterface(const ObjCInterfaceRecord &R) { |
| SymbolContext SymCtx; |
| SymCtx.SymbolName = R.getName(); |
| SymCtx.ObjCIFKind = assignObjCIFSymbolKind(&R); |
| if (SymCtx.ObjCIFKind > ObjCIFSymbolKind::EHType) { |
| if (R.hasExceptionAttribute()) { |
| SymCtx.Kind = EncodeKind::ObjectiveCClassEHType; |
| visitSymbolInDylib(R, SymCtx); |
| } |
| SymCtx.Kind = EncodeKind::ObjectiveCClass; |
| visitSymbolInDylib(R, SymCtx); |
| } else { |
| SymCtx.Kind = R.hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType |
| : EncodeKind::ObjectiveCClass; |
| visitSymbolInDylib(R, SymCtx); |
| } |
| |
| for (const ObjCIVarRecord *IV : R.getObjCIVars()) |
| visitObjCIVar(*IV, R.getName()); |
| } |
| |
| void DylibVerifier::visitObjCCategory(const ObjCCategoryRecord &R) { |
| for (const ObjCIVarRecord *IV : R.getObjCIVars()) |
| visitObjCIVar(*IV, R.getSuperClassName()); |
| } |
| |
| DylibVerifier::Result DylibVerifier::verifyRemainingSymbols() { |
| if (getState() == Result::NoVerify) |
| return Result::NoVerify; |
| assert(!Dylib.empty() && "No binary to verify against"); |
| |
| DWARFContext DWARFInfo; |
| DWARFCtx = &DWARFInfo; |
| Ctx.Target = Target(Architecture::AK_unknown, PlatformType::PLATFORM_UNKNOWN); |
| for (std::shared_ptr<RecordsSlice> Slice : Dylib) { |
| if (Ctx.Target.Arch == Slice->getTarget().Arch) |
| continue; |
| Ctx.DiscoveredFirstError = false; |
| Ctx.PrintArch = true; |
| Ctx.Target = Slice->getTarget(); |
| Ctx.DylibSlice = Slice.get(); |
| Slice->visit(*this); |
| } |
| return getState(); |
| } |
| |
| bool DylibVerifier::verifyBinaryAttrs(const ArrayRef<Target> ProvidedTargets, |
| const BinaryAttrs &ProvidedBA, |
| const LibAttrs &ProvidedReexports, |
| const LibAttrs &ProvidedClients, |
| const LibAttrs &ProvidedRPaths, |
| const FileType &FT) { |
| assert(!Dylib.empty() && "Need dylib to verify."); |
| |
| // Pickup any load commands that can differ per slice to compare. |
| TargetList DylibTargets; |
| LibAttrs DylibReexports; |
| LibAttrs DylibClients; |
| LibAttrs DylibRPaths; |
| for (const std::shared_ptr<RecordsSlice> &RS : Dylib) { |
| DylibTargets.push_back(RS->getTarget()); |
| const BinaryAttrs &BinInfo = RS->getBinaryAttrs(); |
| for (const StringRef LibName : BinInfo.RexportedLibraries) |
| DylibReexports[LibName].set(DylibTargets.back().Arch); |
| for (const StringRef LibName : BinInfo.AllowableClients) |
| DylibClients[LibName].set(DylibTargets.back().Arch); |
| // Compare attributes that are only representable in >= TBD_V5. |
| if (FT >= FileType::TBD_V5) |
| for (const StringRef Name : BinInfo.RPaths) |
| DylibRPaths[Name].set(DylibTargets.back().Arch); |
| } |
| |
| // Check targets first. |
| ArchitectureSet ProvidedArchs = mapToArchitectureSet(ProvidedTargets); |
| ArchitectureSet DylibArchs = mapToArchitectureSet(DylibTargets); |
| if (ProvidedArchs != DylibArchs) { |
| Ctx.Diag->Report(diag::err_architecture_mismatch) |
| << ProvidedArchs << DylibArchs; |
| return false; |
| } |
| auto ProvidedPlatforms = mapToPlatformVersionSet(ProvidedTargets); |
| auto DylibPlatforms = mapToPlatformVersionSet(DylibTargets); |
| if (ProvidedPlatforms != DylibPlatforms) { |
| const bool DiffMinOS = |
| mapToPlatformSet(ProvidedTargets) == mapToPlatformSet(DylibTargets); |
| if (DiffMinOS) |
| Ctx.Diag->Report(diag::warn_platform_mismatch) |
| << ProvidedPlatforms << DylibPlatforms; |
| else { |
| Ctx.Diag->Report(diag::err_platform_mismatch) |
| << ProvidedPlatforms << DylibPlatforms; |
| return false; |
| } |
| } |
| |
| // Because InstallAPI requires certain attributes to match across architecture |
| // slices, take the first one to compare those with. |
| const BinaryAttrs &DylibBA = (*Dylib.begin())->getBinaryAttrs(); |
| |
| if (ProvidedBA.InstallName != DylibBA.InstallName) { |
| Ctx.Diag->Report(diag::err_install_name_mismatch) |
| << ProvidedBA.InstallName << DylibBA.InstallName; |
| return false; |
| } |
| |
| if (ProvidedBA.CurrentVersion != DylibBA.CurrentVersion) { |
| Ctx.Diag->Report(diag::err_current_version_mismatch) |
| << ProvidedBA.CurrentVersion << DylibBA.CurrentVersion; |
| return false; |
| } |
| |
| if (ProvidedBA.CompatVersion != DylibBA.CompatVersion) { |
| Ctx.Diag->Report(diag::err_compatibility_version_mismatch) |
| << ProvidedBA.CompatVersion << DylibBA.CompatVersion; |
| return false; |
| } |
| |
| if (ProvidedBA.AppExtensionSafe != DylibBA.AppExtensionSafe) { |
| Ctx.Diag->Report(diag::err_appextension_safe_mismatch) |
| << (ProvidedBA.AppExtensionSafe ? "true" : "false") |
| << (DylibBA.AppExtensionSafe ? "true" : "false"); |
| return false; |
| } |
| |
| if (!DylibBA.TwoLevelNamespace) { |
| Ctx.Diag->Report(diag::err_no_twolevel_namespace); |
| return false; |
| } |
| |
| if (ProvidedBA.OSLibNotForSharedCache != DylibBA.OSLibNotForSharedCache) { |
| Ctx.Diag->Report(diag::err_shared_cache_eligiblity_mismatch) |
| << (ProvidedBA.OSLibNotForSharedCache ? "true" : "false") |
| << (DylibBA.OSLibNotForSharedCache ? "true" : "false"); |
| return false; |
| } |
| |
| if (ProvidedBA.ParentUmbrella.empty() && !DylibBA.ParentUmbrella.empty()) { |
| Ctx.Diag->Report(diag::err_parent_umbrella_missing) |
| << "installAPI option" << DylibBA.ParentUmbrella; |
| return false; |
| } |
| |
| if (!ProvidedBA.ParentUmbrella.empty() && DylibBA.ParentUmbrella.empty()) { |
| Ctx.Diag->Report(diag::err_parent_umbrella_missing) |
| << "binary file" << ProvidedBA.ParentUmbrella; |
| return false; |
| } |
| |
| if ((!ProvidedBA.ParentUmbrella.empty()) && |
| (ProvidedBA.ParentUmbrella != DylibBA.ParentUmbrella)) { |
| Ctx.Diag->Report(diag::err_parent_umbrella_mismatch) |
| << ProvidedBA.ParentUmbrella << DylibBA.ParentUmbrella; |
| return false; |
| } |
| |
| auto CompareLibraries = [&](const LibAttrs &Provided, const LibAttrs &Dylib, |
| unsigned DiagID_missing, unsigned DiagID_mismatch, |
| bool Fatal = true) { |
| if (Provided == Dylib) |
| return true; |
| |
| for (const llvm::StringMapEntry<ArchitectureSet> &PAttr : Provided) { |
| const auto DAttrIt = Dylib.find(PAttr.getKey()); |
| if (DAttrIt == Dylib.end()) { |
| Ctx.Diag->Report(DiagID_missing) << "binary file" << PAttr; |
| if (Fatal) |
| return false; |
| } |
| |
| if (PAttr.getValue() != DAttrIt->getValue()) { |
| Ctx.Diag->Report(DiagID_mismatch) << PAttr << *DAttrIt; |
| if (Fatal) |
| return false; |
| } |
| } |
| |
| for (const llvm::StringMapEntry<ArchitectureSet> &DAttr : Dylib) { |
| const auto PAttrIt = Provided.find(DAttr.getKey()); |
| if (PAttrIt == Provided.end()) { |
| Ctx.Diag->Report(DiagID_missing) << "installAPI option" << DAttr; |
| if (!Fatal) |
| continue; |
| return false; |
| } |
| |
| if (PAttrIt->getValue() != DAttr.getValue()) { |
| if (Fatal) |
| llvm_unreachable("this case was already covered above."); |
| } |
| } |
| return true; |
| }; |
| |
| if (!CompareLibraries(ProvidedReexports, DylibReexports, |
| diag::err_reexported_libraries_missing, |
| diag::err_reexported_libraries_mismatch)) |
| return false; |
| |
| if (!CompareLibraries(ProvidedClients, DylibClients, |
| diag::err_allowable_clients_missing, |
| diag::err_allowable_clients_mismatch)) |
| return false; |
| |
| if (FT >= FileType::TBD_V5) { |
| // Ignore rpath differences if building an asan variant, since the |
| // compiler injects additional paths. |
| // FIXME: Building with sanitizers does not always change the install |
| // name, so this is not a foolproof solution. |
| if (!ProvidedBA.InstallName.ends_with("_asan")) { |
| if (!CompareLibraries(ProvidedRPaths, DylibRPaths, |
| diag::warn_rpaths_missing, |
| diag::warn_rpaths_mismatch, |
| /*Fatal=*/false)) |
| return true; |
| } |
| } |
| |
| return true; |
| } |
| |
| std::unique_ptr<SymbolSet> DylibVerifier::takeExports() { |
| for (const auto &[Alias, Base] : Aliases) { |
| TargetList Targets; |
| SymbolFlags Flags = SymbolFlags::None; |
| if (const Symbol *Sym = Exports->findSymbol(Base.second, Base.first)) { |
| Flags = Sym->getFlags(); |
| Targets = {Sym->targets().begin(), Sym->targets().end()}; |
| } |
| |
| Record R(Alias.first, RecordLinkage::Exported, Flags); |
| SymbolContext SymCtx; |
| SymCtx.SymbolName = Alias.first; |
| SymCtx.Kind = Alias.second; |
| addSymbol(&R, SymCtx, std::move(Targets)); |
| } |
| |
| return std::move(Exports); |
| } |
| |
| } // namespace installapi |
| } // namespace clang |