[clang] [analyzer][NFC] Take BugReport descriptions as Twine instead of StringRef (PR #205527)
Balázs Benics via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 24 03:58:04 PDT 2026
https://github.com/steakhal created https://github.com/llvm/llvm-project/pull/205527
The constructors of `BugReport`, `BasicBugReport`, and
`PathSensitiveBugReport` previously took the description (and short
description) as `StringRef`. The base class always copies into a
`std::string` member regardless, so taking `const llvm::Twine &` is
strictly more flexible at no storage cost: callers can keep passing
string literals, `StringRef`, `std::string`, `SmallString::str()`, or
`formatv(...).str()` exactly as before, and now they can also pass a
`Twine` concatenation directly without first materializing a temporary
through `SmallString` + `raw_svector_ostream` or `+`/`formatv`.
Assisted-By: claude
>From dfbb924d2f83b5773c7753f07c043add75754540 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Wed, 24 Jun 2026 11:14:08 +0100
Subject: [PATCH 1/2] [analyzer][NFC] Take BugReport descriptions as Twine
instead of StringRef
The constructors of `BugReport`, `BasicBugReport`, and
`PathSensitiveBugReport` previously took the description (and short
description) as `StringRef`. The base class always copies into a
`std::string` member regardless, so taking `const llvm::Twine &` is
strictly more flexible at no storage cost: callers can keep passing
string literals, `StringRef`, `std::string`, `SmallString::str()`, or
`formatv(...).str()` exactly as before, and now they can also pass a
`Twine` concatenation directly without first materializing a temporary
through `SmallString` + `raw_svector_ostream` or `+`/`formatv`.
This commit only changes the parameter types; no callsites are touched.
A follow-up will simplify the checker callsites that build the
description into a local buffer purely to feed the constructor.
Assisted-By: claude
---
.../Core/BugReporter/BugReporter.h | 30 ++++++++++---------
clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 2 +-
2 files changed, 17 insertions(+), 15 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h
index 6d2de7a27608c..51c54151ac07b 100644
--- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h
+++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h
@@ -33,6 +33,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
#include "llvm/ADT/ilist.h"
#include "llvm/ADT/ilist_node.h"
#include "llvm/ADT/iterator_range.h"
@@ -132,13 +133,13 @@ class BugReport {
SmallVector<std::shared_ptr<PathDiagnosticNotePiece>, 4> Notes;
SmallVector<FixItHint, 4> Fixits;
- BugReport(Kind kind, const BugType &bt, StringRef desc)
+ BugReport(Kind kind, const BugType &bt, const llvm::Twine &desc)
: BugReport(kind, bt, "", desc) {}
- BugReport(Kind K, const BugType &BT, StringRef ShortDescription,
- StringRef Description)
- : K(K), BT(BT), ShortDescription(ShortDescription),
- Description(Description) {}
+ BugReport(Kind K, const BugType &BT, const llvm::Twine &ShortDescription,
+ const llvm::Twine &Description)
+ : K(K), BT(BT), ShortDescription(ShortDescription.str()),
+ Description(Description.str()) {}
public:
virtual ~BugReport() = default;
@@ -252,11 +253,12 @@ class BasicBugReport : public BugReport {
const Decl *DeclWithIssue = nullptr;
public:
- BasicBugReport(const BugType &bt, StringRef desc, PathDiagnosticLocation l)
+ BasicBugReport(const BugType &bt, const llvm::Twine &desc,
+ PathDiagnosticLocation l)
: BugReport(Kind::Basic, bt, desc), Location(l) {}
- BasicBugReport(const BugType &BT, StringRef ShortDesc, StringRef Desc,
- PathDiagnosticLocation L)
+ BasicBugReport(const BugType &BT, const llvm::Twine &ShortDesc,
+ const llvm::Twine &Desc, PathDiagnosticLocation L)
: BugReport(Kind::Basic, BT, ShortDesc, Desc), Location(L) {}
static bool classof(const BugReport *R) {
@@ -369,12 +371,12 @@ class PathSensitiveBugReport : public BugReport {
StackHints;
public:
- PathSensitiveBugReport(const BugType &bt, StringRef desc,
+ PathSensitiveBugReport(const BugType &bt, const llvm::Twine &desc,
const ExplodedNode *errorNode)
: PathSensitiveBugReport(bt, desc, desc, errorNode) {}
- PathSensitiveBugReport(const BugType &bt, StringRef shortDesc, StringRef desc,
- const ExplodedNode *errorNode)
+ PathSensitiveBugReport(const BugType &bt, const llvm::Twine &shortDesc,
+ const llvm::Twine &desc, const ExplodedNode *errorNode)
: PathSensitiveBugReport(bt, shortDesc, desc, errorNode,
/*LocationToUnique*/ {},
/*DeclToUnique*/ nullptr) {}
@@ -386,15 +388,15 @@ class PathSensitiveBugReport : public BugReport {
/// to the user. This method allows to rest the location which should be used
/// for uniquing reports. For example, memory leaks checker, could set this to
/// the allocation site, rather then the location where the bug is reported.
- PathSensitiveBugReport(const BugType &bt, StringRef desc,
+ PathSensitiveBugReport(const BugType &bt, const llvm::Twine &desc,
const ExplodedNode *errorNode,
PathDiagnosticLocation LocationToUnique,
const Decl *DeclToUnique)
: PathSensitiveBugReport(bt, desc, desc, errorNode, LocationToUnique,
DeclToUnique) {}
- PathSensitiveBugReport(const BugType &bt, StringRef shortDesc, StringRef desc,
- const ExplodedNode *errorNode,
+ PathSensitiveBugReport(const BugType &bt, const llvm::Twine &shortDesc,
+ const llvm::Twine &desc, const ExplodedNode *errorNode,
PathDiagnosticLocation LocationToUnique,
const Decl *DeclToUnique);
diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
index b6c709963501f..b609a98b0aed2 100644
--- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
@@ -2154,7 +2154,7 @@ LLVM_ATTRIBUTE_USED static bool isHidden(const CheckerRegistryData &Registry,
}
PathSensitiveBugReport::PathSensitiveBugReport(
- const BugType &bt, StringRef shortDesc, StringRef desc,
+ const BugType &bt, const llvm::Twine &shortDesc, const llvm::Twine &desc,
const ExplodedNode *errorNode, PathDiagnosticLocation LocationToUnique,
const Decl *DeclToUnique)
: BugReport(Kind::PathSensitive, bt, shortDesc, desc), ErrorNode(errorNode),
>From 48a9265da49ebe96d1d74cf2688e792b42f7d4c6 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Wed, 24 Jun 2026 11:36:03 +0100
Subject: [PATCH 2/2] [analyzer][NFC] Simplify bug-report sites to use Twine
concatenation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Now that the bug-report constructors take their description as
`const llvm::Twine &`, several call sites that used a local
`SmallString` + `raw_svector_ostream` purely to feed the constructor
can drop the buffer and pass an inline `Twine` concatenation (or a
plain string literal where there was no real concatenation).
Touched checkers:
* CXXDeleteChecker
* MacOSKeychainAPIChecker (3 sites)
* NonNullParamChecker
* UndefCapturedBlockVarChecker
* UndefinedNewArraySizeChecker
* UnixAPIChecker
* VLASizeChecker
Other call sites that build the description across control flow,
through a helper that takes `raw_ostream &`, or via `printQuotedName`
/ `QualType::print` / similar, are intentionally left alone — they
are not single-expression Twine candidates.
Diagnostic text is unchanged; no test expectations needed updating.
Assisted-By: claude
---
.../Checkers/CXXDeleteChecker.cpp | 16 ++++-----
.../Checkers/MacOSKeychainAPIChecker.cpp | 35 +++++++++----------
.../Checkers/NonNullParamChecker.cpp | 13 +++----
.../Checkers/UndefCapturedBlockVarChecker.cpp | 13 +++----
.../Checkers/UndefinedNewArraySizeChecker.cpp | 9 ++---
.../Checkers/UnixAPIChecker.cpp | 8 ++---
.../Checkers/VLASizeChecker.cpp | 11 +++---
7 files changed, 43 insertions(+), 62 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp
index bfab91dd67919..e674b254bd15a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp
@@ -156,19 +156,17 @@ void CXXArrayDeleteChecker::checkTypedDeleteExpr(
if (!N)
return;
- SmallString<256> Buf;
- llvm::raw_svector_ostream OS(Buf);
-
QualType SourceType = BaseClassRegion->getValueType();
QualType TargetType =
DerivedClassRegion->getSymbol()->getType()->getPointeeType();
- OS << "Deleting an array of '" << TargetType.getAsString()
- << "' objects as their base class '"
- << SourceType.getAsString(C.getASTContext().getPrintingPolicy())
- << "' is undefined";
-
- auto R = std::make_unique<PathSensitiveBugReport>(BT, OS.str(), N);
+ auto R = std::make_unique<PathSensitiveBugReport>(
+ BT,
+ "Deleting an array of '" + TargetType.getAsString() +
+ "' objects as their base class '" +
+ SourceType.getAsString(C.getASTContext().getPrintingPolicy()) +
+ "' is undefined",
+ N);
// Mark region of problematic base class for later use in the BugVisitor.
R->markInteresting(BaseClassRegion);
diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
index 5d4a8b6b24766..3c6b707373841 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
@@ -226,14 +226,14 @@ void MacOSKeychainAPIChecker::
if (!N)
return;
- SmallString<80> sbuf;
- llvm::raw_svector_ostream os(sbuf);
unsigned int PDeallocIdx =
- FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx;
+ FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx;
- os << "Deallocator doesn't match the allocator: '"
- << FunctionsToTrack[PDeallocIdx].Name << "' should be used.";
- auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
+ auto Report = std::make_unique<PathSensitiveBugReport>(
+ BT,
+ "Deallocator doesn't match the allocator: '" +
+ Twine(FunctionsToTrack[PDeallocIdx].Name) + "' should be used.",
+ N);
Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first));
Report->addRange(ArgExpr->getSourceRange());
markInteresting(Report.get(), AP);
@@ -269,14 +269,13 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
ExplodedNode *N = C.generateNonFatalErrorNode(State);
if (!N)
return;
- SmallString<128> sbuf;
- llvm::raw_svector_ostream os(sbuf);
unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
- os << "Allocated data should be released before another call to "
- << "the allocator: missing a call to '"
- << FunctionsToTrack[DIdx].Name
- << "'.";
- auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
+ auto Report = std::make_unique<PathSensitiveBugReport>(
+ BT,
+ "Allocated data should be released before another call to "
+ "the allocator: missing a call to '" +
+ Twine(FunctionsToTrack[DIdx].Name) + "'.",
+ N);
Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(V));
Report->addRange(ArgExpr->getSourceRange());
Report->markInteresting(AS->Region);
@@ -463,10 +462,6 @@ std::unique_ptr<PathSensitiveBugReport>
MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport(
const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const {
const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx];
- SmallString<70> sbuf;
- llvm::raw_svector_ostream os(sbuf);
- os << "Allocated data is not released: missing a call to '"
- << FunctionsToTrack[FI.DeallocatorIdx].Name << "'.";
// Most bug reports are cached at the location where they occurred.
// With leaks, we want to unique them by the location where they were
@@ -480,8 +475,10 @@ MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport(
AllocStmt, C.getSourceManager(), AllocNode->getStackFrame());
auto Report = std::make_unique<PathSensitiveBugReport>(
- BT, os.str(), N, LocUsedForUniqueing,
- AllocNode->getStackFrame()->getDecl());
+ BT,
+ "Allocated data is not released: missing a call to '" +
+ Twine(FunctionsToTrack[FI.DeallocatorIdx].Name) + "'.",
+ N, LocUsedForUniqueing, AllocNode->getStackFrame()->getDecl());
Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first));
markInteresting(Report.get(), AP);
diff --git a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
index 4b55c7c49caa8..2978fa7005871 100644
--- a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
@@ -280,14 +280,11 @@ std::unique_ptr<PathSensitiveBugReport>
NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode,
const Expr *ArgE,
unsigned IdxOfArg) const {
- llvm::SmallString<256> SBuf;
- llvm::raw_svector_ostream OS(SBuf);
- OS << "Null pointer passed to "
- << IdxOfArg << llvm::getOrdinalSuffix(IdxOfArg)
- << " parameter expecting 'nonnull'";
-
- auto R =
- std::make_unique<PathSensitiveBugReport>(BTAttrNonNull, SBuf, ErrorNode);
+ auto R = std::make_unique<PathSensitiveBugReport>(
+ BTAttrNonNull,
+ "Null pointer passed to " + Twine(IdxOfArg) +
+ llvm::getOrdinalSuffix(IdxOfArg) + " parameter expecting 'nonnull'",
+ ErrorNode);
if (ArgE)
bugreporter::trackExpressionValue(ErrorNode, ArgE, *R);
diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
index 2839ef0b6d2e6..f75c94edbd87c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
@@ -70,14 +70,11 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE,
if (std::optional<UndefinedVal> V =
state->getSVal(Var.getOriginalRegion()).getAs<UndefinedVal>()) {
if (ExplodedNode *N = C.generateErrorNode()) {
- // Generate a bug report.
- SmallString<128> buf;
- llvm::raw_svector_ostream os(buf);
-
- os << "Variable '" << VD->getName()
- << "' is uninitialized when captured by block";
-
- auto R = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
+ auto R = std::make_unique<PathSensitiveBugReport>(
+ BT,
+ "Variable '" + VD->getName() +
+ "' is uninitialized when captured by block",
+ N);
if (const Expr *Ex = FindBlockDeclRefExpr(BE->getBody(), VD))
R->addRange(Ex->getSourceRange());
bugreporter::trackStoredValue(*V, VR, *R,
diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedNewArraySizeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedNewArraySizeChecker.cpp
index f053ee887a1aa..dcab55a7e370d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UndefinedNewArraySizeChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedNewArraySizeChecker.cpp
@@ -55,13 +55,8 @@ void UndefinedNewArraySizeChecker::HandleUndefinedArrayElementCount(
CheckerContext &C, SVal ArgVal, const Expr *Init, SourceRange Range) const {
if (ExplodedNode *N = C.generateErrorNode()) {
-
- SmallString<100> buf;
- llvm::raw_svector_ostream os(buf);
-
- os << "Element count in new[] is a garbage value";
-
- auto R = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
+ auto R = std::make_unique<PathSensitiveBugReport>(
+ BT, "Element count in new[] is a garbage value", N);
R->markInteresting(ArgVal);
R->addRange(Range);
bugreporter::trackExpressionValue(N, Init, *R);
diff --git a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
index 4df751d203973..2c10b6858a916 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
@@ -480,11 +480,9 @@ bool UnixAPIPortabilityChecker::ReportZeroByteAllocation(
if (!N)
return false;
- SmallString<256> S;
- llvm::raw_svector_ostream os(S);
- os << "Call to '" << fn_name << "' has an allocation size of 0 bytes";
- auto report =
- std::make_unique<PathSensitiveBugReport>(BT_mallocZero, os.str(), N);
+ auto report = std::make_unique<PathSensitiveBugReport>(
+ BT_mallocZero,
+ "Call to '" + Twine(fn_name) + "' has an allocation size of 0 bytes", N);
report->addRange(arg->getSourceRange());
bugreporter::trackExpressionValue(N, arg, *report);
diff --git a/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
index d5c91a20d60b2..54d8faa571ecc 100644
--- a/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
@@ -213,12 +213,11 @@ void VLASizeChecker::reportTaintBug(const Expr *SizeE, ProgramStateRef State,
if (!N)
return;
- SmallString<256> buf;
- llvm::raw_svector_ostream os(buf);
- os << "Declared variable-length array (VLA) ";
- os << "has tainted (attacker controlled) size that can be 0 or negative";
-
- auto report = std::make_unique<PathSensitiveBugReport>(TaintBT, os.str(), N);
+ auto report = std::make_unique<PathSensitiveBugReport>(
+ TaintBT,
+ "Declared variable-length array (VLA) has tainted (attacker controlled) "
+ "size that can be 0 or negative",
+ N);
report->addRange(SizeE->getSourceRange());
bugreporter::trackExpressionValue(N, SizeE, *report);
// The vla size may be a complex expression where multiple memory locations
More information about the cfe-commits
mailing list