151 lines · cpp
1//=== UnsignedStatDemo.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// This checker demonstrates the use of UnsignedEPStat for per-entry-point10// statistics. It conditionally sets a statistic based on the entry point name.11//12//===----------------------------------------------------------------------===//13 14#include "CheckerRegistration.h"15#include "clang/StaticAnalyzer/Core/Checker.h"16#include "clang/StaticAnalyzer/Core/CheckerManager.h"17#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"18#include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h"19#include "llvm/ADT/STLExtras.h"20#include "llvm/ADT/ScopeExit.h"21#include "llvm/ADT/StringMap.h"22#include "llvm/Support/MemoryBuffer.h"23#include "gtest/gtest.h"24#include <optional>25 26using namespace clang;27using namespace ento;28 29static UnsignedEPStat DemoStat("DemoStat");30 31namespace {32class UnsignedStatTesterChecker : public Checker<check::BeginFunction> {33public:34 void checkBeginFunction(CheckerContext &C) const {35 StringRef Name;36 if (const Decl *D = C.getLocationContext()->getDecl())37 if (const FunctionDecl *F = D->getAsFunction())38 Name = F->getName();39 40 // Conditionally set the statistic based on the function name (leaving it41 // undefined for all other functions)42 if (Name == "func_one")43 DemoStat.set(1);44 else if (Name == "func_two")45 DemoStat.set(2);46 else47 ; // For any other function (e.g., "func_none") don't set the statistic48 }49};50 51void addUnsignedStatTesterChecker(AnalysisASTConsumer &AnalysisConsumer,52 AnalyzerOptions &AnOpts) {53 AnOpts.CheckersAndPackages = {{"test.DemoStatChecker", true}};54 AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {55 Registry.addChecker<UnsignedStatTesterChecker>(56 "test.DemoStatChecker", "DescriptionOfDemoStatChecker");57 });58}59 60// Find the index of a column in the CSV header.61// Returns std::nullopt if the column is not found.62static std::optional<unsigned>63findColumnIndex(llvm::ArrayRef<llvm::StringRef> Header,64 llvm::StringRef ColumnName) {65 auto Iter = llvm::find(Header, ColumnName);66 if (Iter != Header.end())67 return std::distance(Header.begin(), Iter);68 return std::nullopt;69}70 71// Parse CSV content and extract a mapping from one column to another.72// KeyColumn is used as the map key (e.g., "DebugName").73// ValueColumn is used as the map value (e.g., "DemoStat").74// Returns a map from key column values to value column values.75static llvm::StringMap<std::string>76parseCSVColumnMapping(llvm::StringRef CSVContent, llvm::StringRef KeyColumn,77 llvm::StringRef ValueColumn) {78 llvm::StringMap<std::string> Result;79 80 // Parse CSV: first line is header, subsequent lines are data81 llvm::SmallVector<llvm::StringRef, 8> Lines;82 CSVContent.split(Lines, '\n', -1, false);83 if (Lines.size() < 2) // Need at least header + one data row84 return Result;85 86 // Parse header to find column indices87 llvm::SmallVector<llvm::StringRef, 32> Header;88 Lines[0].split(Header, ',');89 std::optional<unsigned> KeyIdx = findColumnIndex(Header, KeyColumn);90 std::optional<unsigned> ValueIdx = findColumnIndex(Header, ValueColumn);91 92 if (!KeyIdx || !ValueIdx)93 return Result;94 95 // Parse data rows and extract mappings96 for (auto Line : llvm::drop_begin(Lines)) {97 llvm::SmallVector<llvm::StringRef, 32> Row;98 Line.split(Row, ',');99 if (Row.size() <= std::max(*KeyIdx, *ValueIdx))100 continue;101 102 llvm::StringRef KeyVal = Row[*KeyIdx].trim().trim('"');103 llvm::StringRef ValueVal = Row[*ValueIdx].trim().trim('"');104 105 if (!KeyVal.empty())106 Result[KeyVal] = ValueVal.str();107 }108 109 return Result;110}111 112TEST(UnsignedStat, ExplicitlySetUnsignedStatistic) {113 llvm::SmallString<128> TempMetricsCsvPath;114 std::error_code EC =115 llvm::sys::fs::createTemporaryFile("ep_stats", "csv", TempMetricsCsvPath);116 ASSERT_FALSE(EC);117 std::vector<std::string> Args = {118 "-Xclang", "-analyzer-config", "-Xclang",119 std::string("dump-entry-point-stats-to-csv=") +120 TempMetricsCsvPath.str().str()};121 // Clean up on exit122 auto Cleanup = llvm::make_scope_exit(123 [&]() { llvm::sys::fs::remove(TempMetricsCsvPath); });124 EXPECT_TRUE(runCheckerOnCodeWithArgs<addUnsignedStatTesterChecker>(125 R"cpp(126 void func_one() {}127 void func_two() {}128 void func_none() {}129 )cpp",130 Args));131 132 auto BufferOrError = llvm::MemoryBuffer::getFile(TempMetricsCsvPath);133 ASSERT_TRUE(BufferOrError);134 llvm::StringRef CSVContent = BufferOrError.get()->getBuffer();135 136 // Parse the CSV and extract function statistics137 llvm::StringMap<std::string> FunctionStats =138 parseCSVColumnMapping(CSVContent, "DebugName", "DemoStat");139 140 // Verify the expected values141 ASSERT_TRUE(FunctionStats.count("func_one()"));142 EXPECT_EQ(FunctionStats["func_one()"], "1");143 144 ASSERT_TRUE(FunctionStats.count("func_two()"));145 EXPECT_EQ(FunctionStats["func_two()"], "2");146 147 ASSERT_TRUE(FunctionStats.count("func_none()"));148 EXPECT_EQ(FunctionStats["func_none()"], ""); // Not set, should be empty149}150} // namespace151