Skip to content

Commit 5ac3435

Browse files
authored
Respect the [[clang::unsafe_buffer_usage]] attribute for constructors (#91777)
The -Wunsafe-buffer-usage warning should fire on any call to a function annotated with [[clang::unsafe_buffer_usage]], however it omitted calls to constructors, since the expression is a CXXConstructExpr which does not subclass CallExpr. Thus the matcher on callExpr() does not find these expressions. Add a new WarningGadget that matches cxxConstructExpr that are calling a CXXConstructDecl annotated by [[clang::unsafe_buffer_usage]] and fires the warning. The new UnsafeBufferUsageCtorAttrGadget gadget explicitly avoids matching against the std::span(ptr, size) constructor because that is handled by SpanTwoParamConstructorGadget and we never want two gadgets to match the same thing (and this is guarded by asserts). The gadgets themselves do not report the warnings, instead each gadget's Stmt is passed to the UnsafeBufferUsageHandler (implemented by UnsafeBufferUsageReporter). The Reporter is previously hardcoded that a CXXConstructExpr statement must be a match for std::span(ptr, size), but that is no longer the case. We want the Reporter to generate different warnings (in the -Wunsafe-buffer-usage-in-container subgroup) for the span contructor. And we will want it to report more warnings for other std-container-specific gadgets in the future. To handle this we allow the gadget to control if the warning is general (it calls handleUnsafeBufferUsage()) or is a std-container-specific warning (it calls handleUnsafeOperationInContainer()). Then the WarningGadget grows a virtual method to dispatch to the appropriate path in the UnsafeBufferUsageHandler. By doing so, we no longer need getBaseStmt in the Gadget interface. The only use of it for FixableGadgets was to get the SourceLocation, so we make an explicit virtual method for that on Gadget. Then the handleUnsafeOperation() dispatcher can be a virtual method that is only in WarningGadget. The SpanTwoParamConstructorGadget gadget dispatches to handleUnsafeOperationInContainer() while the other WarningGadgets all dispatch to the original handleUnsafeBufferUsage(). Tests are added for annotated constructors, conversion operattors, call operators, fold expressions, and regular methods. Issue #80482
1 parent d1f96d4 commit 5ac3435

File tree

5 files changed

+178
-57
lines changed

5 files changed

+178
-57
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ class UnsafeBufferUsageHandler {
106106
virtual void handleUnsafeOperation(const Stmt *Operation,
107107
bool IsRelatedToDecl, ASTContext &Ctx) = 0;
108108

109+
/// Invoked when an unsafe operation with a std container is found.
110+
virtual void handleUnsafeOperationInContainer(const Stmt *Operation,
111+
bool IsRelatedToDecl,
112+
ASTContext &Ctx) = 0;
113+
109114
/// Invoked when a fix is suggested against a variable. This function groups
110115
/// all variables that must be fixed together (i.e their types must be changed
111116
/// to the same target type to prevent type mismatches) into a single fixit.

clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ WARNING_GADGET(Decrement)
3636
WARNING_GADGET(ArraySubscript)
3737
WARNING_GADGET(PointerArithmetic)
3838
WARNING_GADGET(UnsafeBufferUsageAttr)
39+
WARNING_GADGET(UnsafeBufferUsageCtorAttr)
3940
WARNING_GADGET(DataInvocation)
4041
WARNING_CONTAINER_GADGET(SpanTwoParamConstructor) // Uses of `std::span(arg0, arg1)`
4142
FIXABLE_GADGET(ULCArraySubscript) // `DRE[any]` in an Unspecified Lvalue Context

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 113 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,9 @@ class Gadget {
492492
#endif
493493

494494
virtual bool isWarningGadget() const = 0;
495-
virtual const Stmt *getBaseStmt() const = 0;
495+
// TODO remove this method from WarningGadget interface. It's only used for
496+
// debug prints in FixableGadget.
497+
virtual SourceLocation getSourceLoc() const = 0;
496498

497499
/// Returns the list of pointer-type variables on which this gadget performs
498500
/// its operation. Typically, there's only one variable. This isn't a list
@@ -513,6 +515,10 @@ class WarningGadget : public Gadget {
513515

514516
static bool classof(const Gadget *G) { return G->isWarningGadget(); }
515517
bool isWarningGadget() const final { return true; }
518+
519+
virtual void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
520+
bool IsRelatedToDecl,
521+
ASTContext &Ctx) const = 0;
516522
};
517523

518524
/// Fixable gadgets correspond to code patterns that aren't always unsafe but
@@ -572,7 +578,12 @@ class IncrementGadget : public WarningGadget {
572578
.bind(OpTag));
573579
}
574580

575-
const UnaryOperator *getBaseStmt() const override { return Op; }
581+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
582+
bool IsRelatedToDecl,
583+
ASTContext &Ctx) const override {
584+
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
585+
}
586+
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
576587

577588
DeclUseList getClaimedVarUseSites() const override {
578589
SmallVector<const DeclRefExpr *, 2> Uses;
@@ -607,7 +618,12 @@ class DecrementGadget : public WarningGadget {
607618
.bind(OpTag));
608619
}
609620

610-
const UnaryOperator *getBaseStmt() const override { return Op; }
621+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
622+
bool IsRelatedToDecl,
623+
ASTContext &Ctx) const override {
624+
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
625+
}
626+
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
611627

612628
DeclUseList getClaimedVarUseSites() const override {
613629
if (const auto *DRE =
@@ -648,7 +664,12 @@ class ArraySubscriptGadget : public WarningGadget {
648664
// clang-format on
649665
}
650666

651-
const ArraySubscriptExpr *getBaseStmt() const override { return ASE; }
667+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
668+
bool IsRelatedToDecl,
669+
ASTContext &Ctx) const override {
670+
Handler.handleUnsafeOperation(ASE, IsRelatedToDecl, Ctx);
671+
}
672+
SourceLocation getSourceLoc() const override { return ASE->getBeginLoc(); }
652673

653674
DeclUseList getClaimedVarUseSites() const override {
654675
if (const auto *DRE =
@@ -696,7 +717,12 @@ class PointerArithmeticGadget : public WarningGadget {
696717
.bind(PointerArithmeticTag));
697718
}
698719

699-
const Stmt *getBaseStmt() const override { return PA; }
720+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
721+
bool IsRelatedToDecl,
722+
ASTContext &Ctx) const override {
723+
Handler.handleUnsafeOperation(PA, IsRelatedToDecl, Ctx);
724+
}
725+
SourceLocation getSourceLoc() const override { return PA->getBeginLoc(); }
700726

701727
DeclUseList getClaimedVarUseSites() const override {
702728
if (const auto *DRE = dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) {
@@ -734,7 +760,12 @@ class SpanTwoParamConstructorGadget : public WarningGadget {
734760
.bind(SpanTwoParamConstructorTag));
735761
}
736762

737-
const Stmt *getBaseStmt() const override { return Ctor; }
763+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
764+
bool IsRelatedToDecl,
765+
ASTContext &Ctx) const override {
766+
Handler.handleUnsafeOperationInContainer(Ctor, IsRelatedToDecl, Ctx);
767+
}
768+
SourceLocation getSourceLoc() const override { return Ctor->getBeginLoc(); }
738769

739770
DeclUseList getClaimedVarUseSites() const override {
740771
// If the constructor call is of the form `std::span{var, n}`, `var` is
@@ -780,11 +811,8 @@ class PointerInitGadget : public FixableGadget {
780811

781812
virtual std::optional<FixItList>
782813
getFixits(const FixitStrategy &S) const override;
783-
784-
virtual const Stmt *getBaseStmt() const override {
785-
// FIXME: This needs to be the entire DeclStmt, assuming that this method
786-
// makes sense at all on a FixableGadget.
787-
return PtrInitRHS;
814+
SourceLocation getSourceLoc() const override {
815+
return PtrInitRHS->getBeginLoc();
788816
}
789817

790818
virtual DeclUseList getClaimedVarUseSites() const override {
@@ -833,12 +861,7 @@ class PtrToPtrAssignmentGadget : public FixableGadget {
833861

834862
virtual std::optional<FixItList>
835863
getFixits(const FixitStrategy &S) const override;
836-
837-
virtual const Stmt *getBaseStmt() const override {
838-
// FIXME: This should be the binary operator, assuming that this method
839-
// makes sense at all on a FixableGadget.
840-
return PtrLHS;
841-
}
864+
SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }
842865

843866
virtual DeclUseList getClaimedVarUseSites() const override {
844867
return DeclUseList{PtrLHS, PtrRHS};
@@ -888,12 +911,7 @@ class CArrayToPtrAssignmentGadget : public FixableGadget {
888911

889912
virtual std::optional<FixItList>
890913
getFixits(const FixitStrategy &S) const override;
891-
892-
virtual const Stmt *getBaseStmt() const override {
893-
// FIXME: This should be the binary operator, assuming that this method
894-
// makes sense at all on a FixableGadget.
895-
return PtrLHS;
896-
}
914+
SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }
897915

898916
virtual DeclUseList getClaimedVarUseSites() const override {
899917
return DeclUseList{PtrLHS, PtrRHS};
@@ -921,10 +939,53 @@ class UnsafeBufferUsageAttrGadget : public WarningGadget {
921939
}
922940

923941
static Matcher matcher() {
924-
return stmt(callExpr(callee(functionDecl(hasAttr(attr::UnsafeBufferUsage))))
925-
.bind(OpTag));
942+
auto HasUnsafeFnDecl =
943+
callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));
944+
return stmt(callExpr(HasUnsafeFnDecl).bind(OpTag));
945+
}
946+
947+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
948+
bool IsRelatedToDecl,
949+
ASTContext &Ctx) const override {
950+
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
926951
}
927-
const Stmt *getBaseStmt() const override { return Op; }
952+
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
953+
954+
DeclUseList getClaimedVarUseSites() const override { return {}; }
955+
};
956+
957+
/// A call of a constructor that performs unchecked buffer operations
958+
/// over one of its pointer parameters, or constructs a class object that will
959+
/// perform buffer operations that depend on the correctness of the parameters.
960+
class UnsafeBufferUsageCtorAttrGadget : public WarningGadget {
961+
constexpr static const char *const OpTag = "cxx_construct_expr";
962+
const CXXConstructExpr *Op;
963+
964+
public:
965+
UnsafeBufferUsageCtorAttrGadget(const MatchFinder::MatchResult &Result)
966+
: WarningGadget(Kind::UnsafeBufferUsageCtorAttr),
967+
Op(Result.Nodes.getNodeAs<CXXConstructExpr>(OpTag)) {}
968+
969+
static bool classof(const Gadget *G) {
970+
return G->getKind() == Kind::UnsafeBufferUsageCtorAttr;
971+
}
972+
973+
static Matcher matcher() {
974+
auto HasUnsafeCtorDecl =
975+
hasDeclaration(cxxConstructorDecl(hasAttr(attr::UnsafeBufferUsage)));
976+
// std::span(ptr, size) ctor is handled by SpanTwoParamConstructorGadget.
977+
auto HasTwoParamSpanCtorDecl = SpanTwoParamConstructorGadget::matcher();
978+
return stmt(
979+
cxxConstructExpr(HasUnsafeCtorDecl, unless(HasTwoParamSpanCtorDecl))
980+
.bind(OpTag));
981+
}
982+
983+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
984+
bool IsRelatedToDecl,
985+
ASTContext &Ctx) const override {
986+
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
987+
}
988+
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
928989

929990
DeclUseList getClaimedVarUseSites() const override { return {}; }
930991
};
@@ -953,7 +1014,13 @@ class DataInvocationGadget : public WarningGadget {
9531014
explicitCastExpr(anyOf(has(callExpr), has(parenExpr(has(callExpr)))))
9541015
.bind(OpTag));
9551016
}
956-
const Stmt *getBaseStmt() const override { return Op; }
1017+
1018+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
1019+
bool IsRelatedToDecl,
1020+
ASTContext &Ctx) const override {
1021+
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
1022+
}
1023+
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
9571024

9581025
DeclUseList getClaimedVarUseSites() const override { return {}; }
9591026
};
@@ -990,8 +1057,7 @@ class ULCArraySubscriptGadget : public FixableGadget {
9901057

9911058
virtual std::optional<FixItList>
9921059
getFixits(const FixitStrategy &S) const override;
993-
994-
virtual const Stmt *getBaseStmt() const override { return Node; }
1060+
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
9951061

9961062
virtual DeclUseList getClaimedVarUseSites() const override {
9971063
if (const auto *DRE =
@@ -1031,8 +1097,7 @@ class UPCStandalonePointerGadget : public FixableGadget {
10311097

10321098
virtual std::optional<FixItList>
10331099
getFixits(const FixitStrategy &S) const override;
1034-
1035-
virtual const Stmt *getBaseStmt() const override { return Node; }
1100+
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
10361101

10371102
virtual DeclUseList getClaimedVarUseSites() const override { return {Node}; }
10381103
};
@@ -1070,10 +1135,9 @@ class PointerDereferenceGadget : public FixableGadget {
10701135
return {BaseDeclRefExpr};
10711136
}
10721137

1073-
virtual const Stmt *getBaseStmt() const final { return Op; }
1074-
10751138
virtual std::optional<FixItList>
10761139
getFixits(const FixitStrategy &S) const override;
1140+
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
10771141
};
10781142

10791143
// Represents expressions of the form `&DRE[any]` in the Unspecified Pointer
@@ -1108,8 +1172,7 @@ class UPCAddressofArraySubscriptGadget : public FixableGadget {
11081172

11091173
virtual std::optional<FixItList>
11101174
getFixits(const FixitStrategy &) const override;
1111-
1112-
virtual const Stmt *getBaseStmt() const override { return Node; }
1175+
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
11131176

11141177
virtual DeclUseList getClaimedVarUseSites() const override {
11151178
const auto *ArraySubst = cast<ArraySubscriptExpr>(Node->getSubExpr());
@@ -1218,8 +1281,7 @@ class UPCPreIncrementGadget : public FixableGadget {
12181281

12191282
virtual std::optional<FixItList>
12201283
getFixits(const FixitStrategy &S) const override;
1221-
1222-
virtual const Stmt *getBaseStmt() const override { return Node; }
1284+
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
12231285

12241286
virtual DeclUseList getClaimedVarUseSites() const override {
12251287
return {dyn_cast<DeclRefExpr>(Node->getSubExpr())};
@@ -1264,8 +1326,7 @@ class UUCAddAssignGadget : public FixableGadget {
12641326

12651327
virtual std::optional<FixItList>
12661328
getFixits(const FixitStrategy &S) const override;
1267-
1268-
virtual const Stmt *getBaseStmt() const override { return Node; }
1329+
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
12691330

12701331
virtual DeclUseList getClaimedVarUseSites() const override {
12711332
return {dyn_cast<DeclRefExpr>(Node->getLHS())};
@@ -1315,9 +1376,9 @@ class DerefSimplePtrArithFixableGadget : public FixableGadget {
13151376

13161377
virtual std::optional<FixItList>
13171378
getFixits(const FixitStrategy &s) const final;
1318-
1319-
// TODO remove this method from FixableGadget interface
1320-
virtual const Stmt *getBaseStmt() const final { return nullptr; }
1379+
SourceLocation getSourceLoc() const override {
1380+
return DerefOp->getBeginLoc();
1381+
}
13211382

13221383
virtual DeclUseList getClaimedVarUseSites() const final {
13231384
return {BaseDeclRefExpr};
@@ -2070,7 +2131,7 @@ UUCAddAssignGadget::getFixits(const FixitStrategy &S) const {
20702131
if (S.lookup(VD) == FixitStrategy::Kind::Span) {
20712132
FixItList Fixes;
20722133

2073-
const Stmt *AddAssignNode = getBaseStmt();
2134+
const Stmt *AddAssignNode = Node;
20742135
StringRef varName = VD->getName();
20752136
const ASTContext &Ctx = VD->getASTContext();
20762137

@@ -2112,20 +2173,19 @@ UPCPreIncrementGadget::getFixits(const FixitStrategy &S) const {
21122173
if (S.lookup(VD) == FixitStrategy::Kind::Span) {
21132174
FixItList Fixes;
21142175
std::stringstream SS;
2115-
const Stmt *PreIncNode = getBaseStmt();
21162176
StringRef varName = VD->getName();
21172177
const ASTContext &Ctx = VD->getASTContext();
21182178

21192179
// To transform UPC(++p) to UPC((p = p.subspan(1)).data()):
21202180
SS << "(" << varName.data() << " = " << varName.data()
21212181
<< ".subspan(1)).data()";
21222182
std::optional<SourceLocation> PreIncLocation =
2123-
getEndCharLoc(PreIncNode, Ctx.getSourceManager(), Ctx.getLangOpts());
2183+
getEndCharLoc(Node, Ctx.getSourceManager(), Ctx.getLangOpts());
21242184
if (!PreIncLocation)
21252185
return std::nullopt;
21262186

21272187
Fixes.push_back(FixItHint::CreateReplacement(
2128-
SourceRange(PreIncNode->getBeginLoc(), *PreIncLocation), SS.str()));
2188+
SourceRange(Node->getBeginLoc(), *PreIncLocation), SS.str()));
21292189
return Fixes;
21302190
}
21312191
}
@@ -2856,7 +2916,7 @@ getFixIts(FixableGadgetSets &FixablesForAllVars, const FixitStrategy &S,
28562916
}
28572917
#ifndef NDEBUG
28582918
Handler.addDebugNoteForVar(
2859-
VD, F->getBaseStmt()->getBeginLoc(),
2919+
VD, F->getSourceLoc(),
28602920
("gadget '" + F->getDebugName() + "' refused to produce a fix")
28612921
.str());
28622922
#endif
@@ -3008,8 +3068,8 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
30083068
// every problematic operation and consider it done. No need to deal
30093069
// with fixable gadgets, no need to group operations by variable.
30103070
for (const auto &G : WarningGadgets) {
3011-
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/false,
3012-
D->getASTContext());
3071+
G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/false,
3072+
D->getASTContext());
30133073
}
30143074

30153075
// This return guarantees that most of the machine doesn't run when
@@ -3251,8 +3311,8 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
32513311
Tracker, Handler, VarGrpMgr);
32523312

32533313
for (const auto &G : UnsafeOps.noVar) {
3254-
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/false,
3255-
D->getASTContext());
3314+
G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/false,
3315+
D->getASTContext());
32563316
}
32573317

32583318
for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {
@@ -3263,8 +3323,8 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
32633323
: FixItList{},
32643324
D, NaiveStrategy);
32653325
for (const auto &G : WarningGadgets) {
3266-
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/true,
3267-
D->getASTContext());
3326+
G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/true,
3327+
D->getASTContext());
32683328
}
32693329
}
32703330
}

0 commit comments

Comments
 (0)