//===-- DiffEngine.cpp - Structural file comparison -----------------------===// // // 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 implementation of the llvm-tapi difference // engine, which structurally compares two tbd files. // //===----------------------------------------------------------------------===/ #include "DiffEngine.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TextAPI/InterfaceFile.h" #include "llvm/TextAPI/Symbol.h" #include "llvm/TextAPI/Target.h" using namespace llvm; using namespace MachO; using namespace object; StringRef setOrderIndicator(InterfaceInputOrder Order) { return ((Order == lhs) ? "< " : "> "); } // The following template specialization implementations // need to be explicitly placed into the llvm namespace // to work around a GCC 4.8 bug. namespace llvm { template inline void DiffScalarVal::print(raw_ostream &OS, std::string Indent) { OS << Indent << "\t" << setOrderIndicator(Order) << Val << "\n"; } template <> inline void DiffScalarVal::print(raw_ostream &OS, std::string Indent) { OS << Indent << "\t\t" << setOrderIndicator(Order) << Val << "\n"; } template <> inline void DiffScalarVal::print(raw_ostream &OS, std::string Indent) { OS << Indent << "\t" << setOrderIndicator(Order) << std::to_string(Val) << "\n"; } template <> inline void DiffScalarVal::print(raw_ostream &OS, std::string Indent) { OS << Indent << "\t" << setOrderIndicator(Order) << ((Val == true) ? "true" : "false") << "\n"; } } // end namespace llvm StringLiteral SymScalar::getSymbolNamePrefix(MachO::SymbolKind Kind) { switch (Kind) { case MachO::SymbolKind::GlobalSymbol: return StringLiteral(""); case MachO::SymbolKind::ObjectiveCClass: return ObjC2MetaClassNamePrefix; case MachO::SymbolKind ::ObjectiveCClassEHType: return ObjC2EHTypePrefix; case MachO::SymbolKind ::ObjectiveCInstanceVariable: return ObjC2IVarPrefix; } llvm_unreachable("Unknown llvm::MachO::SymbolKind enum"); } std::string SymScalar::stringifySymbolFlag(MachO::SymbolFlags Flag) { switch (Flag) { case MachO::SymbolFlags::None: return ""; case MachO::SymbolFlags::ThreadLocalValue: return "Thread-Local"; case MachO::SymbolFlags::WeakDefined: return "Weak-Defined"; case MachO::SymbolFlags::WeakReferenced: return "Weak-Referenced"; case MachO::SymbolFlags::Undefined: return "Undefined"; case MachO::SymbolFlags::Rexported: return "Reexported"; } llvm_unreachable("Unknown llvm::MachO::SymbolFlags enum"); } void SymScalar::print(raw_ostream &OS, std::string Indent, MachO::Target Targ) { if (Val->getKind() == MachO::SymbolKind::ObjectiveCClass) { if (Targ.Arch == MachO::AK_i386 && Targ.Platform == MachO::PlatformKind::macOS) { OS << Indent << "\t\t" << ((Order == lhs) ? "< " : "> ") << ObjC1ClassNamePrefix << Val->getName() << getFlagString(Val->getFlags()) << "\n"; return; } OS << Indent << "\t\t" << ((Order == lhs) ? "< " : "> ") << ObjC2ClassNamePrefix << Val->getName() << getFlagString(Val->getFlags()) << "\n"; } OS << Indent << "\t\t" << ((Order == lhs) ? "< " : "> ") << getSymbolNamePrefix(Val->getKind()) << Val->getName() << getFlagString(Val->getFlags()) << "\n"; } bool checkSymbolEquality(llvm::MachO::InterfaceFile::const_symbol_range LHS, llvm::MachO::InterfaceFile::const_symbol_range RHS) { return std::equal(LHS.begin(), LHS.end(), RHS.begin(), [&](auto LHS, auto RHS) { return *LHS == *RHS; }); } template void addDiffForTargSlice(V Val, Target Targ, DiffOutput &Diff, InterfaceInputOrder Order) { auto TargetVector = llvm::find_if( Diff.Values, [&](const std::unique_ptr &RawTVec) { if (TargetVecT *TVec = dyn_cast(RawTVec.get())) return TVec->Targ == Targ; return false; }); if (TargetVector != Diff.Values.end()) { ValTypeT NewVal(Order, Val); cast(TargetVector->get())->TargValues.push_back(NewVal); } else { auto NewTargetVec = std::make_unique(Targ); ValTypeT NewVal(Order, Val); NewTargetVec->TargValues.push_back(NewVal); Diff.Values.push_back(std::move(NewTargetVec)); } } DiffOutput getSingleAttrDiff(const std::vector &IRefVec, std::string Name, InterfaceInputOrder Order) { DiffOutput Diff(Name); Diff.Kind = AD_Str_Vec; for (const auto &IRef : IRefVec) for (auto Targ : IRef.targets()) addDiffForTargSlice>( IRef.getInstallName(), Targ, Diff, Order); return Diff; } DiffOutput getSingleAttrDiff(const std::vector> &PairVec, std::string Name, InterfaceInputOrder Order) { DiffOutput Diff(Name); Diff.Kind = AD_Str_Vec; for (const auto &Pair : PairVec) addDiffForTargSlice>( StringRef(Pair.second), Pair.first, Diff, Order); return Diff; } DiffOutput getSingleAttrDiff(InterfaceFile::const_symbol_range SymRange, std::string Name, InterfaceInputOrder Order) { DiffOutput Diff(Name); Diff.Kind = AD_Sym_Vec; for (const auto *Sym : SymRange) for (auto Targ : Sym->targets()) addDiffForTargSlice(Sym, Targ, Diff, Order); return Diff; } template DiffOutput getSingleAttrDiff(T SingleAttr, std::string Attribute) { DiffOutput Diff(Attribute); Diff.Kind = SingleAttr.getKind(); Diff.Values.push_back(std::make_unique(SingleAttr)); return Diff; } template void diffAttribute(std::string Name, std::vector &Output, DiffScalarVal Attr) { Output.push_back(getSingleAttrDiff(Attr, Name)); } template void diffAttribute(std::string Name, std::vector &Output, const T &Val, InterfaceInputOrder Order) { Output.push_back(getSingleAttrDiff(Val, Name, Order)); } std::vector getSingleIF(InterfaceFile *Interface, InterfaceInputOrder Order) { std::vector Output; diffAttribute("Install Name", Output, DiffScalarVal( Order, Interface->getInstallName())); diffAttribute("Current Version", Output, DiffScalarVal( Order, Interface->getCurrentVersion())); diffAttribute("Compatibility Version", Output, DiffScalarVal( Order, Interface->getCompatibilityVersion())); diffAttribute("Swift ABI Version", Output, DiffScalarVal( Order, Interface->getSwiftABIVersion())); diffAttribute("InstallAPI", Output, DiffScalarVal( Order, Interface->isInstallAPI())); diffAttribute("Two Level Namespace", Output, DiffScalarVal( Order, Interface->isTwoLevelNamespace())); diffAttribute("Application Extension Safe", Output, DiffScalarVal( Order, Interface->isApplicationExtensionSafe())); diffAttribute("Reexported Libraries", Output, Interface->reexportedLibraries(), Order); diffAttribute("Allowable Clients", Output, Interface->allowableClients(), Order); diffAttribute("Parent Umbrellas", Output, Interface->umbrellas(), Order); diffAttribute("Symbols", Output, Interface->symbols(), Order); for (auto Doc : Interface->documents()) { DiffOutput Documents("Inlined Reexported Frameworks/Libraries"); Documents.Kind = AD_Inline_Doc; Documents.Values.push_back(std::make_unique( InlineDoc(Doc->getInstallName(), getSingleIF(Doc.get(), Order)))); Output.push_back(std::move(Documents)); } return Output; } void findAndAddDiff(const std::vector &CollectedIRefVec, const std::vector &LookupIRefVec, DiffOutput &Result, InterfaceInputOrder Order) { Result.Kind = AD_Str_Vec; for (const auto &IRef : CollectedIRefVec) for (auto Targ : IRef.targets()) { auto FoundIRef = llvm::find_if(LookupIRefVec, [&](const auto LIRef) { auto FoundTarg = llvm::find(LIRef.targets(), Targ); return (FoundTarg != LIRef.targets().end() && IRef.getInstallName() == LIRef.getInstallName()); }); if (FoundIRef == LookupIRefVec.end()) addDiffForTargSlice>( IRef.getInstallName(), Targ, Result, Order); } } void findAndAddDiff( const std::vector> &CollectedPairs, const std::vector> &LookupPairs, DiffOutput &Result, InterfaceInputOrder Order) { Result.Kind = AD_Str_Vec; for (const auto &Pair : CollectedPairs) { auto FoundPair = llvm::find(LookupPairs, Pair); if (FoundPair == LookupPairs.end()) addDiffForTargSlice>( StringRef(Pair.second), Pair.first, Result, Order); } } void findAndAddDiff(InterfaceFile::const_symbol_range CollectedSyms, InterfaceFile::const_symbol_range LookupSyms, DiffOutput &Result, InterfaceInputOrder Order) { Result.Kind = AD_Sym_Vec; for (const auto *Sym : CollectedSyms) for (const auto Targ : Sym->targets()) { auto FoundSym = llvm::find_if(LookupSyms, [&](const auto LSym) { auto FoundTarg = llvm::find(LSym->targets(), Targ); return (Sym->getName() == LSym->getName() && Sym->getKind() == LSym->getKind() && Sym->getFlags() == LSym->getFlags() && FoundTarg != LSym->targets().end()); }); if (FoundSym == LookupSyms.end()) addDiffForTargSlice(Sym, Targ, Result, Order); } } template DiffOutput recordDifferences(T LHS, T RHS, std::string Attr) { DiffOutput Diff(Attr); if (LHS.getKind() == RHS.getKind()) { Diff.Kind = LHS.getKind(); Diff.Values.push_back(std::make_unique(LHS)); Diff.Values.push_back(std::make_unique(RHS)); } return Diff; } template DiffOutput recordDifferences(const std::vector &LHS, const std::vector &RHS, std::string Attr) { DiffOutput Diff(Attr); Diff.Kind = AD_Str_Vec; findAndAddDiff(LHS, RHS, Diff, lhs); findAndAddDiff(RHS, LHS, Diff, rhs); return Diff; } DiffOutput recordDifferences(llvm::MachO::InterfaceFile::const_symbol_range LHS, llvm::MachO::InterfaceFile::const_symbol_range RHS, std::string Attr) { DiffOutput Diff(Attr); Diff.Kind = AD_Sym_Vec; findAndAddDiff(LHS, RHS, Diff, lhs); findAndAddDiff(RHS, LHS, Diff, rhs); return Diff; } std::vector DiffEngine::findDifferences(const InterfaceFile *IFLHS, const InterfaceFile *IFRHS) { std::vector Output; if (IFLHS->getInstallName() != IFRHS->getInstallName()) Output.push_back(recordDifferences( DiffScalarVal(lhs, IFLHS->getInstallName()), DiffScalarVal(rhs, IFRHS->getInstallName()), "Install Name")); if (IFLHS->getCurrentVersion() != IFRHS->getCurrentVersion()) Output.push_back(recordDifferences( DiffScalarVal( lhs, IFLHS->getCurrentVersion()), DiffScalarVal( rhs, IFRHS->getCurrentVersion()), "Current Version")); if (IFLHS->getCompatibilityVersion() != IFRHS->getCompatibilityVersion()) Output.push_back(recordDifferences( DiffScalarVal( lhs, IFLHS->getCompatibilityVersion()), DiffScalarVal( rhs, IFRHS->getCompatibilityVersion()), "Compatibility Version")); if (IFLHS->getSwiftABIVersion() != IFRHS->getSwiftABIVersion()) Output.push_back( recordDifferences(DiffScalarVal( lhs, IFLHS->getSwiftABIVersion()), DiffScalarVal( rhs, IFRHS->getSwiftABIVersion()), "Swift ABI Version")); if (IFLHS->isInstallAPI() != IFRHS->isInstallAPI()) Output.push_back(recordDifferences( DiffScalarVal(lhs, IFLHS->isInstallAPI()), DiffScalarVal(rhs, IFRHS->isInstallAPI()), "InstallAPI")); if (IFLHS->isTwoLevelNamespace() != IFRHS->isTwoLevelNamespace()) Output.push_back(recordDifferences(DiffScalarVal( lhs, IFLHS->isTwoLevelNamespace()), DiffScalarVal( rhs, IFRHS->isTwoLevelNamespace()), "Two Level Namespace")); if (IFLHS->isApplicationExtensionSafe() != IFRHS->isApplicationExtensionSafe()) Output.push_back( recordDifferences(DiffScalarVal( lhs, IFLHS->isApplicationExtensionSafe()), DiffScalarVal( rhs, IFRHS->isApplicationExtensionSafe()), "Application Extension Safe")); if (IFLHS->reexportedLibraries() != IFRHS->reexportedLibraries()) Output.push_back(recordDifferences(IFLHS->reexportedLibraries(), IFRHS->reexportedLibraries(), "Reexported Libraries")); if (IFLHS->allowableClients() != IFRHS->allowableClients()) Output.push_back(recordDifferences(IFLHS->allowableClients(), IFRHS->allowableClients(), "Allowable Clients")); if (IFLHS->umbrellas() != IFRHS->umbrellas()) Output.push_back(recordDifferences(IFLHS->umbrellas(), IFRHS->umbrellas(), "Parent Umbrellas")); if (!checkSymbolEquality(IFLHS->symbols(), IFRHS->symbols())) Output.push_back( recordDifferences(IFLHS->symbols(), IFRHS->symbols(), "Symbols")); if (IFLHS->documents() != IFRHS->documents()) { DiffOutput Docs("Inlined Reexported Frameworks/Libraries"); Docs.Kind = AD_Inline_Doc; std::vector DocsInserted; // Iterate through inline frameworks/libraries from interface file and find // match based on install name. for (auto DocLHS : IFLHS->documents()) { auto Pair = llvm::find_if(IFRHS->documents(), [&](const auto &DocRHS) { return (DocLHS->getInstallName() == DocRHS->getInstallName()); }); // If a match found, recursively get differences between the pair. if (Pair != IFRHS->documents().end()) { InlineDoc PairDiff = InlineDoc(DocLHS->getInstallName(), findDifferences(DocLHS.get(), Pair->get())); if (!PairDiff.DocValues.empty()) Docs.Values.push_back( std::make_unique(std::move(PairDiff))); } // If a match is not found, get attributes from single item. else Docs.Values.push_back(std::make_unique(InlineDoc( DocLHS->getInstallName(), getSingleIF(DocLHS.get(), lhs)))); DocsInserted.push_back(DocLHS->getInstallName()); } for (auto DocRHS : IFRHS->documents()) { auto WasGathered = llvm::find_if(DocsInserted, [&](const auto &GatheredDoc) { return (GatheredDoc == DocRHS->getInstallName()); }); if (WasGathered == DocsInserted.end()) Docs.Values.push_back(std::make_unique(InlineDoc( DocRHS->getInstallName(), getSingleIF(DocRHS.get(), rhs)))); } if (!Docs.Values.empty()) Output.push_back(std::move(Docs)); } return Output; } template void printSingleVal(std::string Indent, const DiffOutput &Attr, raw_ostream &OS) { if (Attr.Values.empty()) return; OS << Indent << Attr.Name << "\n"; for (auto &RawItem : Attr.Values) if (T *Item = dyn_cast(RawItem.get())) Item->print(OS, Indent); } template T *castValues(const std::unique_ptr &RawAttr) { T *CastAttr = cast(RawAttr.get()); return CastAttr; } template void sortTargetValues(std::vector &TargValues) { llvm::stable_sort(TargValues, [](const auto &ValA, const auto &ValB) { return ValA.getOrder() < ValB.getOrder(); }); llvm::stable_sort(TargValues, [](const auto &ValA, const auto &ValB) { return ValA.getOrder() == ValB.getOrder() && ValA.getVal() < ValB.getVal(); }); } template void printVecVal(std::string Indent, const DiffOutput &Attr, raw_ostream &OS) { if (Attr.Values.empty()) return; OS << Indent << Attr.Name << "\n"; std::vector SortedAttrs; llvm::transform(Attr.Values, std::back_inserter(SortedAttrs), castValues); llvm::sort(SortedAttrs, [&](const auto &ValA, const auto &ValB) { return ValA->Targ < ValB->Targ; }); for (auto *Vec : SortedAttrs) { sortTargetValues>( Vec->TargValues); OS << Indent << "\t" << getTargetTripleName(Vec->Targ) << "\n"; for (auto &Item : Vec->TargValues) Item.print(OS, Indent); } } template <> void printVecVal(std::string Indent, const DiffOutput &Attr, raw_ostream &OS) { if (Attr.Values.empty()) return; OS << Indent << Attr.Name << "\n"; std::vector SortedAttrs; llvm::transform(Attr.Values, std::back_inserter(SortedAttrs), castValues); llvm::sort(SortedAttrs, [&](const auto &ValA, const auto &ValB) { return ValA->Targ < ValB->Targ; }); for (auto *SymVec : SortedAttrs) { sortTargetValues(SymVec->TargValues); OS << Indent << "\t" << getTargetTripleName(SymVec->Targ) << "\n"; for (auto &Item : SymVec->TargValues) Item.print(OS, Indent, SymVec->Targ); } } void DiffEngine::printDifferences(raw_ostream &OS, const std::vector &Diffs, int IndentCounter) { std::string Indent = std::string(IndentCounter, '\t'); for (auto &Attr : Diffs) { switch (Attr.Kind) { case AD_Diff_Scalar_Str: if (IndentCounter == 0) printSingleVal>(Indent, Attr, OS); break; case AD_Diff_Scalar_PackedVersion: printSingleVal< DiffScalarVal>(Indent, Attr, OS); break; case AD_Diff_Scalar_Unsigned: printSingleVal>(Indent, Attr, OS); break; case AD_Diff_Scalar_Bool: printSingleVal>(Indent, Attr, OS); break; case AD_Str_Vec: printVecVal(Indent, Attr, OS); break; case AD_Sym_Vec: printVecVal(Indent, Attr, OS); break; case AD_Inline_Doc: if (!Attr.Values.empty()) { OS << Indent << Attr.Name << "\n"; for (auto &Item : Attr.Values) if (InlineDoc *Doc = dyn_cast(Item.get())) if (!Doc->DocValues.empty()) { OS << Indent << "\t" << Doc->InstallName << "\n"; printDifferences(OS, std::move(Doc->DocValues), 2); } } break; } } } bool DiffEngine::compareFiles(raw_ostream &OS) { const auto *IFLHS = &(FileLHS->getInterfaceFile()); const auto *IFRHS = &(FileRHS->getInterfaceFile()); if (*IFLHS == *IFRHS) return false; OS << "< " << std::string(IFLHS->getPath().data()) << "\n> " << std::string(IFRHS->getPath().data()) << "\n\n"; std::vector Diffs = findDifferences(IFLHS, IFRHS); printDifferences(OS, Diffs, 0); return true; }