Skip to content

Commit b796401

Browse files
committed
Move UnsignedStatTesterChecker to unit tests
1 parent bfd6037 commit b796401

File tree

6 files changed

+167
-77
lines changed

6 files changed

+167
-77
lines changed

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,10 +1502,6 @@ def TaintTesterChecker : Checker<"TaintTest">,
15021502
HelpText<"Mark tainted symbols as such.">,
15031503
Documentation<NotDocumented>;
15041504

1505-
def UnsignedStatTesterChecker : Checker<"UnsignedStatTester">,
1506-
HelpText<"Test checker for demonstrating UnsignedEPStat usage.">,
1507-
Documentation<NotDocumented>;
1508-
15091505
// This checker *technically* depends on SteamChecker, but we don't allow
15101506
// dependency checkers to emit diagnostics, and a debug checker isn't worth
15111507
// the chore needed to create a modeling portion on its own. Since this checker

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ add_clang_library(clangStaticAnalyzerCheckers
125125
UninitializedObject/UninitializedPointee.cpp
126126
UnixAPIChecker.cpp
127127
UnreachableCodeChecker.cpp
128-
UnsignedStatTesterChecker.cpp
129128
VforkChecker.cpp
130129
VLASizeChecker.cpp
131130
VAListChecker.cpp

clang/lib/StaticAnalyzer/Checkers/UnsignedStatTesterChecker.cpp

Lines changed: 0 additions & 54 deletions
This file was deleted.

clang/test/Analysis/analyzer-stats/entry-point-stats.cpp

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// REQUIRES: asserts
2-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.UnsignedStatTester \
2+
// RUN: %clang_analyze_cc1 -analyzer-checker=core \
33
// RUN: -analyzer-config dump-entry-point-stats-to-csv="%t.csv" \
44
// RUN: -verify %s
55
// RUN: %csv2json "%t.csv" | FileCheck --check-prefix=CHECK %s
@@ -8,7 +8,6 @@
88
// CHECK-NEXT: "c:@F@fib#i#": {
99
// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp",
1010
// CHECK-NEXT: "DebugName": "fib(unsigned int)",
11-
// CHECK-NEXT: "DemoStat": "",
1211
// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}",
1312
// CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}",
1413
// CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}",
@@ -43,22 +42,9 @@
4342
// CHECK-NEXT: "NumZ3QueriesDone": "{{[0-9]+}}",
4443
// CHECK-NEXT: "TimeSpentSolvingZ3Queries": "{{[0-9]+}}"
4544
// CHECK-NEXT: },
46-
// CHECK-NEXT: "c:@F@func_one#": {
47-
// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp",
48-
// CHECK-NEXT: "DebugName": "func_one()",
49-
// CHECK: "DemoStat": "1",
50-
// .... not interesting statistics
51-
// CHECK: },
52-
// CHECK-NEXT: "c:@F@func_two#": {
53-
// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp",
54-
// CHECK-NEXT: "DebugName": "func_two()",
55-
// CHECK: "DemoStat": "2",
56-
// .... not interesting statistics
57-
// CHECK: },
5845
// CHECK-NEXT: "c:@F@main#I#**C#": {
5946
// CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp",
6047
// CHECK-NEXT: "DebugName": "main(int, char **)",
61-
// CHECK-NEXT: "DemoStat": "",
6248
// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}",
6349
// CHECK-NEXT: "MaxBugClassSize": "{{[0-9]+}}",
6450
// CHECK-NEXT: "MaxCFGSize": "{{[0-9]+}}",
@@ -116,6 +102,3 @@ int main(int argc, char **argv) {
116102
int i = non_entry_point(argc);
117103
return i;
118104
}
119-
120-
void func_one() {}
121-
void func_two() {}

clang/unittests/StaticAnalyzer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ add_clang_unittest(StaticAnalysisTests
2020
SValSimplifyerTest.cpp
2121
SValTest.cpp
2222
TestReturnValueUnderConstruction.cpp
23+
UnsignedStatDemo.cpp
2324
Z3CrosscheckOracleTest.cpp
2425
CLANG_LIBS
2526
clangBasic
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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

Comments
 (0)