Skip to content

Commit a6ae740

Browse files
committed
[-Wunsafe-buffer-usage] Add a facility for debugging low fixit coverage
Differential Revision: https://reviews.llvm.org/D154880
1 parent 9a67c6b commit a6ae740

File tree

5 files changed

+220
-16
lines changed

5 files changed

+220
-16
lines changed

clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "clang/AST/Decl.h"
1818
#include "clang/AST/Stmt.h"
19+
#include "llvm/Support/Debug.h"
1920

2021
namespace clang {
2122

@@ -24,6 +25,18 @@ using DefMapTy = llvm::DenseMap<const VarDecl *, std::vector<const VarDecl *>>;
2425
/// The interface that lets the caller handle unsafe buffer usage analysis
2526
/// results by overriding this class's handle... methods.
2627
class UnsafeBufferUsageHandler {
28+
#ifndef NDEBUG
29+
public:
30+
// A self-debugging facility that you can use to notify the user when
31+
// suggestions or fixits are incomplete.
32+
// Uses std::function to avoid computing the message when it won't
33+
// actually be displayed.
34+
using DebugNote = std::pair<SourceLocation, std::string>;
35+
using DebugNoteList = std::vector<DebugNote>;
36+
using DebugNoteByVar = std::map<const VarDecl *, DebugNoteList>;
37+
DebugNoteByVar DebugNotesByVar;
38+
#endif
39+
2740
public:
2841
UnsafeBufferUsageHandler() = default;
2942
virtual ~UnsafeBufferUsageHandler() = default;
@@ -43,6 +56,26 @@ class UnsafeBufferUsageHandler {
4356
const DefMapTy &VarGrpMap,
4457
FixItList &&Fixes) = 0;
4558

59+
#ifndef NDEBUG
60+
public:
61+
bool areDebugNotesRequested() {
62+
DEBUG_WITH_TYPE("SafeBuffers", return true);
63+
return false;
64+
}
65+
66+
void addDebugNoteForVar(const VarDecl *VD, SourceLocation Loc,
67+
std::string Text) {
68+
if (areDebugNotesRequested())
69+
DebugNotesByVar[VD].push_back(std::make_pair(Loc, Text));
70+
}
71+
72+
void clearDebugNotes() {
73+
if (areDebugNotesRequested())
74+
DebugNotesByVar.clear();
75+
}
76+
#endif
77+
78+
public:
4679
/// Returns a reference to the `Preprocessor`:
4780
virtual bool isSafeBufferOptOut(const SourceLocation &Loc) const = 0;
4881

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11872,6 +11872,12 @@ def note_unsafe_buffer_variable_fixit_group : Note<
1187211872
"change type of %0 to '%select{std::span|std::array|std::span::iterator}1' to preserve bounds information%select{|, and change %2 to '%select{std::span|std::array|std::span::iterator}1' to propagate bounds information between them}3">;
1187311873
def note_safe_buffer_usage_suggestions_disabled : Note<
1187411874
"pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions">;
11875+
#ifndef NDEBUG
11876+
// Not a user-facing diagnostic. Useful for debugging false negatives in
11877+
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).
11878+
def note_safe_buffer_debug_mode : Note<"safe buffers debug: %0">;
11879+
#endif
11880+
1187511881
def err_loongarch_builtin_requires_la32 : Error<
1187611882
"this builtin requires target: loongarch32">;
1187711883

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 107 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,15 @@ class Gadget {
316316

317317
Kind getKind() const { return K; }
318318

319+
#ifndef NDEBUG
320+
StringRef getDebugName() const {
321+
switch (K) {
322+
#define GADGET(x) case Kind::x: return #x;
323+
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
324+
}
325+
}
326+
#endif
327+
319328
virtual bool isWarningGadget() const = 0;
320329
virtual const Stmt *getBaseStmt() const = 0;
321330

@@ -565,7 +574,11 @@ class PointerInitGadget : public FixableGadget {
565574

566575
virtual std::optional<FixItList> getFixits(const Strategy &S) const override;
567576

568-
virtual const Stmt *getBaseStmt() const override { return nullptr; }
577+
virtual const Stmt *getBaseStmt() const override {
578+
// FIXME: This needs to be the entire DeclStmt, assuming that this method
579+
// makes sense at all on a FixableGadget.
580+
return PtrInitRHS;
581+
}
569582

570583
virtual DeclUseList getClaimedVarUseSites() const override {
571584
return DeclUseList{PtrInitRHS};
@@ -613,7 +626,11 @@ class PointerAssignmentGadget : public FixableGadget {
613626

614627
virtual std::optional<FixItList> getFixits(const Strategy &S) const override;
615628

616-
virtual const Stmt *getBaseStmt() const override { return nullptr; }
629+
virtual const Stmt *getBaseStmt() const override {
630+
// FIXME: This should be the binary operator, assuming that this method
631+
// makes sense at all on a FixableGadget.
632+
return PtrLHS;
633+
}
617634

618635
virtual DeclUseList getClaimedVarUseSites() const override {
619636
return DeclUseList{PtrLHS, PtrRHS};
@@ -845,6 +862,16 @@ class DeclUseTracker {
845862
});
846863
}
847864

865+
UseSetTy getUnclaimedUses(const VarDecl *VD) const {
866+
UseSetTy ReturnSet;
867+
for (auto use : *Uses) {
868+
if (use->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl()) {
869+
ReturnSet.insert(use);
870+
}
871+
}
872+
return ReturnSet;
873+
}
874+
848875
void discoverDecl(const DeclStmt *DS) {
849876
for (const Decl *D : DS->decls()) {
850877
if (const auto *VD = dyn_cast<VarDecl>(D)) {
@@ -1703,6 +1730,13 @@ populateInitializerFixItWithSpan(const Expr *Init, ASTContext &Ctx,
17031730
return FixIts;
17041731
}
17051732

1733+
#ifndef NDEBUG
1734+
#define DEBUG_NOTE_DECL_FAIL(D, Msg) \
1735+
Handler.addDebugNoteForVar((D), (D)->getBeginLoc(), "failed to produce fixit for declaration '" + (D)->getNameAsString() + "'" + (Msg))
1736+
#else
1737+
#define DEBUG_NOTE_DECL_FAIL(D, Msg)
1738+
#endif
1739+
17061740
// For a `VarDecl` of the form `T * var (= Init)?`, this
17071741
// function generates a fix-it for the declaration, which re-declares `var` to
17081742
// be of `span<T>` type and transforms the initializer, if present, to a span
@@ -1717,7 +1751,9 @@ populateInitializerFixItWithSpan(const Expr *Init, ASTContext &Ctx,
17171751
// Returns:
17181752
// the generated fix-it
17191753
static FixItList fixVarDeclWithSpan(const VarDecl *D, ASTContext &Ctx,
1720-
const StringRef UserFillPlaceHolder) {
1754+
const StringRef UserFillPlaceHolder,
1755+
UnsafeBufferUsageHandler &Handler) {
1756+
(void)Handler; // Suppress unused variable warning in release builds.
17211757
const QualType &SpanEltT = D->getType()->getPointeeType();
17221758
assert(!SpanEltT.isNull() && "Trying to fix a non-pointer type variable!");
17231759

@@ -1730,8 +1766,10 @@ static FixItList fixVarDeclWithSpan(const VarDecl *D, ASTContext &Ctx,
17301766
FixItList InitFixIts =
17311767
populateInitializerFixItWithSpan(Init, Ctx, UserFillPlaceHolder);
17321768

1733-
if (InitFixIts.empty())
1769+
if (InitFixIts.empty()) {
1770+
DEBUG_NOTE_DECL_FAIL(D, " : empty initializer");
17341771
return {};
1772+
}
17351773

17361774
// The loc right before the initializer:
17371775
ReplacementLastLoc = Init->getBeginLoc().getLocWithOffset(-1);
@@ -1746,8 +1784,10 @@ static FixItList fixVarDeclWithSpan(const VarDecl *D, ASTContext &Ctx,
17461784

17471785
OS << "std::span<" << SpanEltT.getAsString() << "> " << D->getName();
17481786

1749-
if (!ReplacementLastLoc)
1787+
if (!ReplacementLastLoc) {
1788+
DEBUG_NOTE_DECL_FAIL(D, " : failed to get end char loc (macro)");
17501789
return {};
1790+
}
17511791

17521792
FixIts.push_back(FixItHint::CreateReplacement(
17531793
SourceRange{D->getBeginLoc(), *ReplacementLastLoc}, OS.str()));
@@ -1939,26 +1979,35 @@ createOverloadsForFixedParams(unsigned ParmIdx, StringRef NewTyText,
19391979
// `createOverloadsForFixedParams`).
19401980
static FixItList fixParamWithSpan(const ParmVarDecl *PVD, const ASTContext &Ctx,
19411981
UnsafeBufferUsageHandler &Handler) {
1942-
if (PVD->hasDefaultArg())
1982+
if (PVD->hasDefaultArg()) {
19431983
// FIXME: generate fix-its for default values:
1984+
DEBUG_NOTE_DECL_FAIL(PVD, " : has default arg");
19441985
return {};
1986+
}
1987+
19451988
assert(PVD->getType()->isPointerType());
19461989
auto *FD = dyn_cast<FunctionDecl>(PVD->getDeclContext());
19471990

1948-
if (!FD)
1991+
if (!FD) {
1992+
DEBUG_NOTE_DECL_FAIL(PVD, " : invalid func decl");
19491993
return {};
1994+
}
19501995

19511996
std::optional<Qualifiers> PteTyQualifiers = std::nullopt;
19521997
std::optional<std::string> PteTyText = getPointeeTypeText(
19531998
PVD, Ctx.getSourceManager(), Ctx.getLangOpts(), &PteTyQualifiers);
19541999

1955-
if (!PteTyText)
2000+
if (!PteTyText) {
2001+
DEBUG_NOTE_DECL_FAIL(PVD, " : invalid pointee type");
19562002
return {};
2003+
}
19572004

19582005
std::optional<StringRef> PVDNameText = PVD->getIdentifier()->getName();
19592006

1960-
if (!PVDNameText)
2007+
if (!PVDNameText) {
2008+
DEBUG_NOTE_DECL_FAIL(PVD, " : invalid identifier name");
19612009
return {};
2010+
}
19622011

19632012
std::string SpanOpen = "std::span<";
19642013
std::string SpanClose = ">";
@@ -1994,6 +2043,7 @@ static FixItList fixParamWithSpan(const ParmVarDecl *PVD, const ASTContext &Ctx,
19942043
Fixes.append(*OverloadFix);
19952044
return Fixes;
19962045
}
2046+
DEBUG_NOTE_DECL_FAIL(PVD, " : invalid number of parameters");
19972047
return {};
19982048
}
19992049

@@ -2005,6 +2055,7 @@ static FixItList fixVariableWithSpan(const VarDecl *VD,
20052055
assert(DS && "Fixing non-local variables not implemented yet!");
20062056
if (!DS->isSingleDecl()) {
20072057
// FIXME: to support handling multiple `VarDecl`s in a single `DeclStmt`
2058+
DEBUG_NOTE_DECL_FAIL(VD, " : multiple VarDecls");
20082059
return {};
20092060
}
20102061
// Currently DS is an unused variable but we'll need it when
@@ -2013,7 +2064,8 @@ static FixItList fixVariableWithSpan(const VarDecl *VD,
20132064
(void)DS;
20142065

20152066
// FIXME: handle cases where DS has multiple declarations
2016-
return fixVarDeclWithSpan(VD, Ctx, getUserFillPlaceHolder());
2067+
return fixVarDeclWithSpan(VD, Ctx, getUserFillPlaceHolder(),
2068+
Handler);
20172069
}
20182070

20192071
// TODO: we should be consistent to use `std::nullopt` to represent no-fix due
@@ -2025,10 +2077,12 @@ fixVariable(const VarDecl *VD, Strategy::Kind K,
20252077
UnsafeBufferUsageHandler &Handler) {
20262078
if (const auto *PVD = dyn_cast<ParmVarDecl>(VD)) {
20272079
auto *FD = dyn_cast<clang::FunctionDecl>(PVD->getDeclContext());
2028-
if (!FD || FD != D)
2080+
if (!FD || FD != D) {
20292081
// `FD != D` means that `PVD` belongs to a function that is not being
20302082
// analyzed currently. Thus `FD` may not be complete.
2083+
DEBUG_NOTE_DECL_FAIL(VD, " : function not currently analyzed");
20312084
return {};
2085+
}
20322086

20332087
// TODO If function has a try block we can't change params unless we check
20342088
// also its catch block for their use.
@@ -2041,8 +2095,10 @@ fixVariable(const VarDecl *VD, Strategy::Kind K,
20412095
isa<CXXMethodDecl>(FD) ||
20422096
// skip when the function body is a try-block
20432097
(FD->hasBody() && isa<CXXTryStmt>(FD->getBody())) ||
2044-
FD->isOverloadedOperator())
2098+
FD->isOverloadedOperator()) {
2099+
DEBUG_NOTE_DECL_FAIL(VD, " : unsupported function decl");
20452100
return {}; // TODO test all these cases
2101+
}
20462102
}
20472103

20482104
switch (K) {
@@ -2054,6 +2110,7 @@ fixVariable(const VarDecl *VD, Strategy::Kind K,
20542110
if (VD->isLocalVarDecl())
20552111
return fixVariableWithSpan(VD, Tracker, Ctx, Handler);
20562112
}
2113+
DEBUG_NOTE_DECL_FAIL(VD, " : not a pointer");
20572114
return {};
20582115
}
20592116
case Strategy::Kind::Iterator:
@@ -2113,6 +2170,12 @@ getFixIts(FixableGadgetSets &FixablesForAllVars, const Strategy &S,
21132170
for (const auto &F : Fixables) {
21142171
std::optional<FixItList> Fixits = F->getFixits(S);
21152172
if (!Fixits) {
2173+
#ifndef NDEBUG
2174+
Handler.addDebugNoteForVar(
2175+
VD, F->getBaseStmt()->getBeginLoc(),
2176+
("gadget '" + F->getDebugName() + "' refused to produce a fix")
2177+
.str());
2178+
#endif
21162179
ImpossibleToFix = true;
21172180
break;
21182181
} else {
@@ -2198,6 +2261,10 @@ getNaiveStrategy(const llvm::SmallVectorImpl<const VarDecl *> &UnsafeVars) {
21982261
void clang::checkUnsafeBufferUsage(const Decl *D,
21992262
UnsafeBufferUsageHandler &Handler,
22002263
bool EmitSuggestions) {
2264+
#ifndef NDEBUG
2265+
Handler.clearDebugNotes();
2266+
#endif
2267+
22012268
assert(D && D->getBody());
22022269

22032270
// We do not want to visit a Lambda expression defined inside a method independently.
@@ -2277,10 +2344,34 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
22772344
for (auto it = FixablesForAllVars.byVar.cbegin();
22782345
it != FixablesForAllVars.byVar.cend();) {
22792346
// FIXME: need to deal with global variables later
2280-
if ((!it->first->isLocalVarDecl() && !isa<ParmVarDecl>(it->first)) ||
2281-
Tracker.hasUnclaimedUses(it->first) || it->first->isInitCapture()) {
2282-
it = FixablesForAllVars.byVar.erase(it);
2283-
} else {
2347+
if ((!it->first->isLocalVarDecl() && !isa<ParmVarDecl>(it->first))) {
2348+
#ifndef NDEBUG
2349+
Handler.addDebugNoteForVar(
2350+
it->first, it->first->getBeginLoc(),
2351+
("failed to produce fixit for '" + it->first->getNameAsString() +
2352+
"' : neither local nor a parameter"));
2353+
#endif
2354+
it = FixablesForAllVars.byVar.erase(it);
2355+
} else if (Tracker.hasUnclaimedUses(it->first)) {
2356+
#ifndef NDEBUG
2357+
auto AllUnclaimed = Tracker.getUnclaimedUses(it->first);
2358+
for (auto UnclaimedDRE : AllUnclaimed) {
2359+
Handler.addDebugNoteForVar(
2360+
it->first, UnclaimedDRE->getBeginLoc(),
2361+
("failed to produce fixit for '" + it->first->getNameAsString() +
2362+
"' : has an unclaimed use"));
2363+
}
2364+
#endif
2365+
it = FixablesForAllVars.byVar.erase(it);
2366+
} else if (it->first->isInitCapture()) {
2367+
#ifndef NDEBUG
2368+
Handler.addDebugNoteForVar(
2369+
it->first, it->first->getBeginLoc(),
2370+
("failed to produce fixit for '" + it->first->getNameAsString() +
2371+
"' : init capture"));
2372+
#endif
2373+
it = FixablesForAllVars.byVar.erase(it);
2374+
}else {
22842375
++it;
22852376
}
22862377
}

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2276,6 +2276,12 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
22762276
for (const auto &F : Fixes)
22772277
FD << F;
22782278
}
2279+
2280+
#ifndef NDEBUG
2281+
if (areDebugNotesRequested())
2282+
for (const DebugNote &Note: DebugNotesByVar[Variable])
2283+
S.Diag(Note.first, diag::note_safe_buffer_debug_mode) << Note.second;
2284+
#endif
22792285
}
22802286

22812287
bool isSafeBufferOptOut(const SourceLocation &Loc) const override {

0 commit comments

Comments
 (0)