Skip to content

Commit 8b318da

Browse files
committed
Respect the [[clang::unsafe_buffer_usage]] attribute for constructors
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.
1 parent 317e6ff commit 8b318da

File tree

5 files changed

+176
-57
lines changed

5 files changed

+176
-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: 114 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ class Gadget {
492492
#endif
493493

494494
virtual bool isWarningGadget() const = 0;
495-
virtual const Stmt *getBaseStmt() const = 0;
495+
virtual SourceLocation getSourceLoc() const = 0;
496496

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

514514
static bool classof(const Gadget *G) { return G->isWarningGadget(); }
515515
bool isWarningGadget() const final { return true; }
516+
517+
virtual void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
518+
bool IsRelatedToDecl,
519+
ASTContext &Ctx) const = 0;
516520
};
517521

518522
/// Fixable gadgets correspond to code patterns that aren't always unsafe but
@@ -572,7 +576,12 @@ class IncrementGadget : public WarningGadget {
572576
.bind(OpTag));
573577
}
574578

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

577586
DeclUseList getClaimedVarUseSites() const override {
578587
SmallVector<const DeclRefExpr *, 2> Uses;
@@ -607,7 +616,12 @@ class DecrementGadget : public WarningGadget {
607616
.bind(OpTag));
608617
}
609618

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

612626
DeclUseList getClaimedVarUseSites() const override {
613627
if (const auto *DRE =
@@ -648,7 +662,12 @@ class ArraySubscriptGadget : public WarningGadget {
648662
// clang-format on
649663
}
650664

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

653672
DeclUseList getClaimedVarUseSites() const override {
654673
if (const auto *DRE =
@@ -696,7 +715,12 @@ class PointerArithmeticGadget : public WarningGadget {
696715
.bind(PointerArithmeticTag));
697716
}
698717

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

701725
DeclUseList getClaimedVarUseSites() const override {
702726
if (const auto *DRE = dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) {
@@ -734,7 +758,12 @@ class SpanTwoParamConstructorGadget : public WarningGadget {
734758
.bind(SpanTwoParamConstructorTag));
735759
}
736760

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

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

781810
virtual std::optional<FixItList>
782811
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;
812+
SourceLocation getSourceLoc() const override {
813+
return PtrInitRHS->getBeginLoc();
788814
}
789815

790816
virtual DeclUseList getClaimedVarUseSites() const override {
@@ -833,12 +859,7 @@ class PtrToPtrAssignmentGadget : public FixableGadget {
833859

834860
virtual std::optional<FixItList>
835861
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-
}
862+
SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }
842863

843864
virtual DeclUseList getClaimedVarUseSites() const override {
844865
return DeclUseList{PtrLHS, PtrRHS};
@@ -888,12 +909,7 @@ class CArrayToPtrAssignmentGadget : public FixableGadget {
888909

889910
virtual std::optional<FixItList>
890911
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-
}
912+
SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }
897913

898914
virtual DeclUseList getClaimedVarUseSites() const override {
899915
return DeclUseList{PtrLHS, PtrRHS};
@@ -921,10 +937,55 @@ class UnsafeBufferUsageAttrGadget : public WarningGadget {
921937
}
922938

923939
static Matcher matcher() {
924-
return stmt(callExpr(callee(functionDecl(hasAttr(attr::UnsafeBufferUsage))))
925-
.bind(OpTag));
940+
auto HasUnsafeFnDecl =
941+
callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));
942+
return stmt(callExpr(HasUnsafeFnDecl).bind(OpTag));
943+
}
944+
945+
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
946+
bool IsRelatedToDecl,
947+
ASTContext &Ctx) const override {
948+
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
949+
}
950+
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
951+
952+
DeclUseList getClaimedVarUseSites() const override { return {}; }
953+
};
954+
955+
/// A call of a constructor that performs unchecked buffer operations
956+
/// over one of its pointer parameters, or constructs a class object that will
957+
/// perform buffer operations that depend on the correctness of the parameters.
958+
class UnsafeBufferUsageCtorAttrGadget : public WarningGadget {
959+
constexpr static const char *const OpTag = "cxx_construct_expr";
960+
const CXXConstructExpr *Op;
961+
962+
public:
963+
UnsafeBufferUsageCtorAttrGadget(const MatchFinder::MatchResult &Result)
964+
: WarningGadget(Kind::UnsafeBufferUsageCtorAttr),
965+
Op(Result.Nodes.getNodeAs<CXXConstructExpr>(OpTag)) {}
966+
967+
static bool classof(const Gadget *G) {
968+
return G->getKind() == Kind::UnsafeBufferUsageCtorAttr;
969+
}
970+
971+
static Matcher matcher() {
972+
auto HasUnsafeCtorDecl =
973+
hasDeclaration(cxxConstructorDecl(hasAttr(attr::UnsafeBufferUsage)));
974+
// std::span(ptr, size) ctor is handled by SpanTwoParamConstructorGadget.
975+
auto HasTwoParamSpanCtorDecl = hasDeclaration(
976+
cxxConstructorDecl(hasDeclContext(isInStdNamespace()), hasName("span"),
977+
parameterCountIs(2)));
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);
926987
}
927-
const Stmt *getBaseStmt() const override { return Op; }
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,9 @@ 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+
llvm::errs() << "Warnings not EmitSuggestions\n";
3072+
G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/false,
3073+
D->getASTContext());
30133074
}
30143075

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

32533314
for (const auto &G : UnsafeOps.noVar) {
3254-
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/false,
3255-
D->getASTContext());
3315+
G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/false,
3316+
D->getASTContext());
32563317
}
32573318

32583319
for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {
@@ -3263,8 +3324,8 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
32633324
: FixItList{},
32643325
D, NaiveStrategy);
32653326
for (const auto &G : WarningGadgets) {
3266-
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/true,
3267-
D->getASTContext());
3327+
G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/true,
3328+
D->getASTContext());
32683329
}
32693330
}
32703331
}

0 commit comments

Comments
 (0)