|
| 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-exception |
| 6 | +// |
| 7 | +//===----------------------------------------------------------------------===// |
| 8 | +// |
| 9 | +// This checker demonstrates the use of UnsignedEPStat for per-entry-point |
| 10 | +// 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/ScopeExit.h" |
| 20 | +#include "llvm/ADT/StringMap.h" |
| 21 | +#include "llvm/Support/MemoryBuffer.h" |
| 22 | +#include "gtest/gtest.h" |
| 23 | +#include <optional> |
| 24 | + |
| 25 | +using namespace clang; |
| 26 | +using namespace ento; |
| 27 | + |
| 28 | +static UnsignedEPStat DemoStat("DemoStat"); |
| 29 | + |
| 30 | +namespace { |
| 31 | +class UnsignedStatTesterChecker : public Checker<check::BeginFunction> { |
| 32 | +public: |
| 33 | + void checkBeginFunction(CheckerContext &C) const { |
| 34 | + std::string Name; |
| 35 | + if (const Decl *D = C.getLocationContext()->getDecl()) |
| 36 | + if (const FunctionDecl *F = D->getAsFunction()) |
| 37 | + Name = F->getNameAsString(); |
| 38 | + |
| 39 | + // Conditionally set the statistic based on the function name (leaving it |
| 40 | + // undefined for all other functions) |
| 41 | + if (Name == "func_one") |
| 42 | + DemoStat.set(1); |
| 43 | + else if (Name == "func_two") |
| 44 | + DemoStat.set(2); |
| 45 | + else |
| 46 | + ; // For any other function (e.g., "func_none") don't set the statistic |
| 47 | + } |
| 48 | +}; |
| 49 | + |
| 50 | +void addUnsignedStatTesterChecker(AnalysisASTConsumer &AnalysisConsumer, |
| 51 | + AnalyzerOptions &AnOpts) { |
| 52 | + AnOpts.CheckersAndPackages = {{"test.DemoStatChecker", true}}; |
| 53 | + AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { |
| 54 | + Registry.addChecker<UnsignedStatTesterChecker>( |
| 55 | + "test.DemoStatChecker", "DescriptionOfDemoStatChecker"); |
| 56 | + }); |
| 57 | +} |
| 58 | + |
| 59 | +// Find the index of a column in the CSV header. |
| 60 | +// Returns std::nullopt if the column is not found. |
| 61 | +static std::optional<unsigned> |
| 62 | +findColumnIndex(llvm::ArrayRef<llvm::StringRef> Header, |
| 63 | + llvm::StringRef ColumnName) { |
| 64 | + for (size_t i = 0; i < Header.size(); ++i) { |
| 65 | + llvm::StringRef ColName = Header[i].trim().trim('"'); |
| 66 | + if (ColName == ColumnName) |
| 67 | + return static_cast<unsigned>(i); |
| 68 | + } |
| 69 | + return std::nullopt; |
| 70 | +} |
| 71 | + |
| 72 | +// Extract function name from a DebugName column value. |
| 73 | +// E.g., "func_one()" -> "func_one", "main(int, char **)" -> "main" |
| 74 | +static llvm::StringRef extractFunctionName(llvm::StringRef DebugName) { |
| 75 | + size_t ParenPos = DebugName.find('('); |
| 76 | + if (ParenPos != llvm::StringRef::npos) |
| 77 | + return DebugName.substr(0, ParenPos); |
| 78 | + return DebugName; |
| 79 | +} |
| 80 | + |
| 81 | +// Parse CSV content and extract a mapping from one column to another. |
| 82 | +// KeyColumn is used as the map key (e.g., "DebugName"). |
| 83 | +// ValueColumn is used as the map value (e.g., "DemoStat"). |
| 84 | +// If KeyTransform is true, extracts function name from key (strips parameters). |
| 85 | +// Returns a map from key column values to value column values. |
| 86 | +static llvm::StringMap<std::string> |
| 87 | +parseCSVColumnMapping(llvm::StringRef CSVContent, llvm::StringRef KeyColumn, |
| 88 | + llvm::StringRef ValueColumn, bool KeyTransform = false) { |
| 89 | + llvm::StringMap<std::string> Result; |
| 90 | + |
| 91 | + // Parse CSV: first line is header, subsequent lines are data |
| 92 | + llvm::SmallVector<llvm::StringRef, 8> Lines; |
| 93 | + CSVContent.split(Lines, '\n', -1, false); |
| 94 | + if (Lines.size() < 2) // Need at least header + one data row |
| 95 | + return Result; |
| 96 | + |
| 97 | + // Parse header to find column indices |
| 98 | + llvm::SmallVector<llvm::StringRef, 32> Header; |
| 99 | + Lines[0].split(Header, ','); |
| 100 | + std::optional<unsigned> KeyIdx = findColumnIndex(Header, KeyColumn); |
| 101 | + std::optional<unsigned> ValueIdx = findColumnIndex(Header, ValueColumn); |
| 102 | + |
| 103 | + if (!KeyIdx || !ValueIdx) |
| 104 | + return Result; |
| 105 | + |
| 106 | + // Parse data rows and extract mappings |
| 107 | + for (size_t i = 1; i < Lines.size(); ++i) { |
| 108 | + llvm::SmallVector<llvm::StringRef, 32> Row; |
| 109 | + Lines[i].split(Row, ','); |
| 110 | + if (Row.size() <= std::max(*KeyIdx, *ValueIdx)) |
| 111 | + continue; |
| 112 | + |
| 113 | + llvm::StringRef KeyVal = Row[*KeyIdx].trim().trim('"'); |
| 114 | + llvm::StringRef ValueVal = Row[*ValueIdx].trim().trim('"'); |
| 115 | + |
| 116 | + // Apply transformation to key if requested (e.g., extract function name) |
| 117 | + if (KeyTransform) |
| 118 | + KeyVal = extractFunctionName(KeyVal); |
| 119 | + |
| 120 | + if (!KeyVal.empty()) |
| 121 | + Result[KeyVal] = ValueVal.str(); |
| 122 | + } |
| 123 | + |
| 124 | + return Result; |
| 125 | +} |
| 126 | + |
| 127 | +TEST(UnsignedStat, ExplicitlySetUnsignedStatistic) { |
| 128 | + llvm::SmallString<128> TempMetricsCsvPath; |
| 129 | + std::error_code EC = |
| 130 | + llvm::sys::fs::createTemporaryFile("ep_stats", "csv", TempMetricsCsvPath); |
| 131 | + ASSERT_FALSE(EC); |
| 132 | + std::vector<std::string> Args = { |
| 133 | + "-Xclang", "-analyzer-config", "-Xclang", |
| 134 | + std::string("dump-entry-point-stats-to-csv=") + |
| 135 | + TempMetricsCsvPath.str().str()}; |
| 136 | + // Clean up on exit |
| 137 | + auto Cleanup = llvm::make_scope_exit( |
| 138 | + [&]() { llvm::sys::fs::remove(TempMetricsCsvPath); }); |
| 139 | + EXPECT_TRUE(runCheckerOnCodeWithArgs<addUnsignedStatTesterChecker>( |
| 140 | + R"cpp( |
| 141 | + void func_one() {} |
| 142 | + void func_two() {} |
| 143 | + void func_none() {} |
| 144 | + )cpp", |
| 145 | + Args)); |
| 146 | + |
| 147 | + auto BufferOrError = llvm::MemoryBuffer::getFile(TempMetricsCsvPath); |
| 148 | + ASSERT_TRUE(BufferOrError); |
| 149 | + llvm::StringRef CSVContent = BufferOrError.get()->getBuffer(); |
| 150 | + |
| 151 | + // Parse the CSV and extract function statistics |
| 152 | + llvm::StringMap<std::string> FunctionStats = |
| 153 | + parseCSVColumnMapping(CSVContent, "DebugName", "DemoStat"); |
| 154 | + |
| 155 | + // Verify the expected values |
| 156 | + ASSERT_TRUE(FunctionStats.count("func_one()")); |
| 157 | + EXPECT_EQ(FunctionStats["func_one()"], "1"); |
| 158 | + |
| 159 | + ASSERT_TRUE(FunctionStats.count("func_two()")); |
| 160 | + EXPECT_EQ(FunctionStats["func_two()"], "2"); |
| 161 | + |
| 162 | + ASSERT_TRUE(FunctionStats.count("func_none()")); |
| 163 | + EXPECT_EQ(FunctionStats["func_none()"], ""); // Not set, should be empty |
| 164 | +} |
| 165 | +} // namespace |
0 commit comments