//===- EntryPointStats.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/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h" #include "clang/AST/DeclBase.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Index/USRGeneration.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/raw_ostream.h" #include using namespace clang; using namespace ento; namespace { struct Registry { std::vector ExplicitlySetStats; std::vector MaxStats; std::vector CounterStats; bool IsLocked = false; struct Snapshot { const Decl *EntryPoint; // Explicitly set statistics may not have a value set, so they are separate // from other unsigned statistics std::vector> ExplicitlySetStatValues; // These are counting and maximizing statistics that initialize to 0, which // is meaningful even if they are never updated, so their value is always // present. std::vector MaxOrCountStatValues; void dumpAsCSV(llvm::raw_ostream &OS) const; }; std::vector Snapshots; std::string EscapedCPPFileName; }; } // namespace static llvm::ManagedStatic StatsRegistry; namespace { template void enumerateStatVectors(const Callback &Fn) { // This order is important, it matches the order of the Snapshot fields: // - ExplicitlySetStatValues Fn(StatsRegistry->ExplicitlySetStats); // - MaxOrCountStatValues Fn(StatsRegistry->MaxStats); Fn(StatsRegistry->CounterStats); } void clearSnapshots(void *) { StatsRegistry->Snapshots.clear(); } } // namespace static void checkStatName(const EntryPointStat *M) { #ifdef NDEBUG return; #endif // NDEBUG constexpr std::array AllowedSpecialChars = { '+', '-', '_', '=', ':', '(', ')', '@', '!', '~', '$', '%', '^', '&', '*', '\'', ';', '<', '>', '/'}; for (unsigned char C : M->name()) { if (!std::isalnum(C) && !llvm::is_contained(AllowedSpecialChars, C)) { llvm::errs() << "Stat name \"" << M->name() << "\" contains character '" << C << "' (" << static_cast(C) << ") that is not allowed."; assert(false && "The Stat name contains unallowed character"); } } } void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName, ASTContext &Ctx) { auto CmpByNames = [](const EntryPointStat *L, const EntryPointStat *R) { return L->name() < R->name(); }; enumerateStatVectors( [CmpByNames](auto &Stats) { llvm::sort(Stats, CmpByNames); }); enumerateStatVectors( [](const auto &Stats) { llvm::for_each(Stats, checkStatName); }); StatsRegistry->IsLocked = true; llvm::raw_string_ostream OS(StatsRegistry->EscapedCPPFileName); llvm::printEscapedString(CPPFileName, OS); // Make sure snapshots (that reference function Decl's) do not persist after // the AST is destroyed. This is especially relevant in the context of unit // tests that construct and destruct multiple ASTs in the same process. Ctx.AddDeallocation(clearSnapshots, nullptr); } [[maybe_unused]] static bool isRegistered(llvm::StringLiteral Name) { auto ByName = [Name](const EntryPointStat *M) { return M->name() == Name; }; bool Result = false; enumerateStatVectors([ByName, &Result](const auto &Stats) { Result = Result || llvm::any_of(Stats, ByName); }); return Result; } CounterEPStat::CounterEPStat(llvm::StringLiteral Name) : EntryPointStat(Name) { assert(!StatsRegistry->IsLocked); assert(!isRegistered(Name)); StatsRegistry->CounterStats.push_back(this); } UnsignedMaxEPStat::UnsignedMaxEPStat(llvm::StringLiteral Name) : EntryPointStat(Name) { assert(!StatsRegistry->IsLocked); assert(!isRegistered(Name)); StatsRegistry->MaxStats.push_back(this); } UnsignedEPStat::UnsignedEPStat(llvm::StringLiteral Name) : EntryPointStat(Name) { assert(!StatsRegistry->IsLocked); assert(!isRegistered(Name)); StatsRegistry->ExplicitlySetStats.push_back(this); } static std::vector> consumeExplicitlySetStats() { std::vector> Result; Result.reserve(StatsRegistry->ExplicitlySetStats.size()); for (auto *M : StatsRegistry->ExplicitlySetStats) { Result.push_back(M->value()); M->reset(); } return Result; } static std::vector consumeMaxAndCounterStats() { std::vector Result; Result.reserve(StatsRegistry->CounterStats.size() + StatsRegistry->MaxStats.size()); // Order is important, it must match the order in enumerateStatVectors for (auto *M : StatsRegistry->MaxStats) { Result.push_back(M->value()); M->reset(); } for (auto *M : StatsRegistry->CounterStats) { Result.push_back(M->value()); M->reset(); } return Result; } static std::vector getStatNames() { std::vector Ret; auto GetName = [](const EntryPointStat *M) { return M->name(); }; enumerateStatVectors([GetName, &Ret](const auto &Stats) { transform(Stats, std::back_inserter(Ret), GetName); }); return Ret; } static std::string getUSR(const Decl *D) { llvm::SmallVector Buf; if (index::generateUSRForDecl(D, Buf)) { assert(false && "This should never fail"); return AnalysisDeclContext::getFunctionName(D); } return llvm::toStringRef(Buf).str(); } void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { auto PrintAsUnsignOpt = [&OS](std::optional U) { OS << (U.has_value() ? std::to_string(*U) : ""); }; auto CommaIfNeeded = [&OS](const auto &Vec1, const auto &Vec2) { if (!Vec1.empty() && !Vec2.empty()) OS << ","; }; auto PrintAsUnsigned = [&OS](unsigned U) { OS << U; }; OS << '"'; llvm::printEscapedString(getUSR(EntryPoint), OS); OS << "\",\""; OS << StatsRegistry->EscapedCPPFileName << "\",\""; llvm::printEscapedString( clang::AnalysisDeclContext::getFunctionName(EntryPoint), OS); OS << "\","; llvm::interleave(ExplicitlySetStatValues, OS, PrintAsUnsignOpt, ","); CommaIfNeeded(ExplicitlySetStatValues, MaxOrCountStatValues); llvm::interleave(MaxOrCountStatValues, OS, PrintAsUnsigned, ","); } void EntryPointStat::takeSnapshot(const Decl *EntryPoint) { auto ExplicitlySetValues = consumeExplicitlySetStats(); auto MaxOrCounterValues = consumeMaxAndCounterStats(); StatsRegistry->Snapshots.push_back({EntryPoint, std::move(ExplicitlySetValues), std::move(MaxOrCounterValues)}); } void EntryPointStat::dumpStatsAsCSV(llvm::StringRef FileName) { std::error_code EC; llvm::raw_fd_ostream File(FileName, EC, llvm::sys::fs::OF_Text); if (EC) return; dumpStatsAsCSV(File); } void EntryPointStat::dumpStatsAsCSV(llvm::raw_ostream &OS) { OS << "USR,File,DebugName,"; llvm::interleave(getStatNames(), OS, [&OS](const auto &a) { OS << a; }, ","); OS << "\n"; std::vector Rows; Rows.reserve(StatsRegistry->Snapshots.size()); for (const auto &Snapshot : StatsRegistry->Snapshots) { std::string Row; llvm::raw_string_ostream RowOs(Row); Snapshot.dumpAsCSV(RowOs); RowOs << "\n"; Rows.push_back(RowOs.str()); } llvm::sort(Rows); for (const auto &Row : Rows) { OS << Row; } }