232 lines · cpp
1//===- EntryPointStats.cpp --------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h"10#include "clang/AST/DeclBase.h"11#include "clang/Analysis/AnalysisDeclContext.h"12#include "clang/Index/USRGeneration.h"13#include "llvm/ADT/STLExtras.h"14#include "llvm/ADT/SmallVector.h"15#include "llvm/ADT/StringExtras.h"16#include "llvm/ADT/StringRef.h"17#include "llvm/Support/FileSystem.h"18#include "llvm/Support/ManagedStatic.h"19#include "llvm/Support/raw_ostream.h"20#include <iterator>21 22using namespace clang;23using namespace ento;24 25namespace {26struct Registry {27 std::vector<UnsignedEPStat *> ExplicitlySetStats;28 std::vector<UnsignedMaxEPStat *> MaxStats;29 std::vector<CounterEPStat *> CounterStats;30 31 bool IsLocked = false;32 33 struct Snapshot {34 const Decl *EntryPoint;35 // Explicitly set statistics may not have a value set, so they are separate36 // from other unsigned statistics37 std::vector<std::optional<unsigned>> ExplicitlySetStatValues;38 // These are counting and maximizing statistics that initialize to 0, which39 // is meaningful even if they are never updated, so their value is always40 // present.41 std::vector<unsigned> MaxOrCountStatValues;42 43 void dumpAsCSV(llvm::raw_ostream &OS) const;44 };45 46 std::vector<Snapshot> Snapshots;47 std::string EscapedCPPFileName;48};49} // namespace50 51static llvm::ManagedStatic<Registry> StatsRegistry;52 53namespace {54template <typename Callback> void enumerateStatVectors(const Callback &Fn) {55 // This order is important, it matches the order of the Snapshot fields:56 // - ExplicitlySetStatValues57 Fn(StatsRegistry->ExplicitlySetStats);58 // - MaxOrCountStatValues59 Fn(StatsRegistry->MaxStats);60 Fn(StatsRegistry->CounterStats);61}62 63void clearSnapshots(void *) { StatsRegistry->Snapshots.clear(); }64 65} // namespace66 67static void checkStatName(const EntryPointStat *M) {68#ifdef NDEBUG69 return;70#endif // NDEBUG71 constexpr std::array AllowedSpecialChars = {72 '+', '-', '_', '=', ':', '(', ')', '@', '!', '~',73 '$', '%', '^', '&', '*', '\'', ';', '<', '>', '/'};74 for (unsigned char C : M->name()) {75 if (!std::isalnum(C) && !llvm::is_contained(AllowedSpecialChars, C)) {76 llvm::errs() << "Stat name \"" << M->name() << "\" contains character '"77 << C << "' (" << static_cast<int>(C)78 << ") that is not allowed.";79 assert(false && "The Stat name contains unallowed character");80 }81 }82}83 84void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName,85 ASTContext &Ctx) {86 auto CmpByNames = [](const EntryPointStat *L, const EntryPointStat *R) {87 return L->name() < R->name();88 };89 enumerateStatVectors(90 [CmpByNames](auto &Stats) { llvm::sort(Stats, CmpByNames); });91 enumerateStatVectors(92 [](const auto &Stats) { llvm::for_each(Stats, checkStatName); });93 StatsRegistry->IsLocked = true;94 llvm::raw_string_ostream OS(StatsRegistry->EscapedCPPFileName);95 llvm::printEscapedString(CPPFileName, OS);96 // Make sure snapshots (that reference function Decl's) do not persist after97 // the AST is destroyed. This is especially relevant in the context of unit98 // tests that construct and destruct multiple ASTs in the same process.99 Ctx.AddDeallocation(clearSnapshots, nullptr);100}101 102[[maybe_unused]] static bool isRegistered(llvm::StringLiteral Name) {103 auto ByName = [Name](const EntryPointStat *M) { return M->name() == Name; };104 bool Result = false;105 enumerateStatVectors([ByName, &Result](const auto &Stats) {106 Result = Result || llvm::any_of(Stats, ByName);107 });108 return Result;109}110 111CounterEPStat::CounterEPStat(llvm::StringLiteral Name) : EntryPointStat(Name) {112 assert(!StatsRegistry->IsLocked);113 assert(!isRegistered(Name));114 StatsRegistry->CounterStats.push_back(this);115}116 117UnsignedMaxEPStat::UnsignedMaxEPStat(llvm::StringLiteral Name)118 : EntryPointStat(Name) {119 assert(!StatsRegistry->IsLocked);120 assert(!isRegistered(Name));121 StatsRegistry->MaxStats.push_back(this);122}123 124UnsignedEPStat::UnsignedEPStat(llvm::StringLiteral Name)125 : EntryPointStat(Name) {126 assert(!StatsRegistry->IsLocked);127 assert(!isRegistered(Name));128 StatsRegistry->ExplicitlySetStats.push_back(this);129}130 131static std::vector<std::optional<unsigned>> consumeExplicitlySetStats() {132 std::vector<std::optional<unsigned>> Result;133 Result.reserve(StatsRegistry->ExplicitlySetStats.size());134 for (auto *M : StatsRegistry->ExplicitlySetStats) {135 Result.push_back(M->value());136 M->reset();137 }138 return Result;139}140 141static std::vector<unsigned> consumeMaxAndCounterStats() {142 std::vector<unsigned> Result;143 Result.reserve(StatsRegistry->CounterStats.size() +144 StatsRegistry->MaxStats.size());145 // Order is important, it must match the order in enumerateStatVectors146 for (auto *M : StatsRegistry->MaxStats) {147 Result.push_back(M->value());148 M->reset();149 }150 for (auto *M : StatsRegistry->CounterStats) {151 Result.push_back(M->value());152 M->reset();153 }154 return Result;155}156 157static std::vector<llvm::StringLiteral> getStatNames() {158 std::vector<llvm::StringLiteral> Ret;159 auto GetName = [](const EntryPointStat *M) { return M->name(); };160 enumerateStatVectors([GetName, &Ret](const auto &Stats) {161 transform(Stats, std::back_inserter(Ret), GetName);162 });163 return Ret;164}165 166static std::string getUSR(const Decl *D) {167 llvm::SmallVector<char> Buf;168 if (index::generateUSRForDecl(D, Buf)) {169 assert(false && "This should never fail");170 return AnalysisDeclContext::getFunctionName(D);171 }172 return llvm::toStringRef(Buf).str();173}174 175void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const {176 auto PrintAsUnsignOpt = [&OS](std::optional<unsigned> U) {177 OS << (U.has_value() ? std::to_string(*U) : "");178 };179 auto CommaIfNeeded = [&OS](const auto &Vec1, const auto &Vec2) {180 if (!Vec1.empty() && !Vec2.empty())181 OS << ",";182 };183 auto PrintAsUnsigned = [&OS](unsigned U) { OS << U; };184 185 OS << '"';186 llvm::printEscapedString(getUSR(EntryPoint), OS);187 OS << "\",\"";188 OS << StatsRegistry->EscapedCPPFileName << "\",\"";189 llvm::printEscapedString(190 clang::AnalysisDeclContext::getFunctionName(EntryPoint), OS);191 OS << "\",";192 llvm::interleave(ExplicitlySetStatValues, OS, PrintAsUnsignOpt, ",");193 CommaIfNeeded(ExplicitlySetStatValues, MaxOrCountStatValues);194 llvm::interleave(MaxOrCountStatValues, OS, PrintAsUnsigned, ",");195}196 197void EntryPointStat::takeSnapshot(const Decl *EntryPoint) {198 auto ExplicitlySetValues = consumeExplicitlySetStats();199 auto MaxOrCounterValues = consumeMaxAndCounterStats();200 StatsRegistry->Snapshots.push_back({EntryPoint,201 std::move(ExplicitlySetValues),202 std::move(MaxOrCounterValues)});203}204 205void EntryPointStat::dumpStatsAsCSV(llvm::StringRef FileName) {206 std::error_code EC;207 llvm::raw_fd_ostream File(FileName, EC, llvm::sys::fs::OF_Text);208 if (EC)209 return;210 dumpStatsAsCSV(File);211}212 213void EntryPointStat::dumpStatsAsCSV(llvm::raw_ostream &OS) {214 OS << "USR,File,DebugName,";215 llvm::interleave(getStatNames(), OS, [&OS](const auto &a) { OS << a; }, ",");216 OS << "\n";217 218 std::vector<std::string> Rows;219 Rows.reserve(StatsRegistry->Snapshots.size());220 for (const auto &Snapshot : StatsRegistry->Snapshots) {221 std::string Row;222 llvm::raw_string_ostream RowOs(Row);223 Snapshot.dumpAsCSV(RowOs);224 RowOs << "\n";225 Rows.push_back(RowOs.str());226 }227 llvm::sort(Rows);228 for (const auto &Row : Rows) {229 OS << Row;230 }231}232