Skip to content

Commit 3b2d778

Browse files
committed
[-Wunsafe-buffer-usage] Add FixableGadget for AddAssign in UnspecifiedUntypedContext (llvm#71862)
(cherry picked from commit e1655a9)
1 parent 06e45e2 commit 3b2d778

File tree

3 files changed

+155
-6
lines changed

3 files changed

+155
-6
lines changed

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 @@ FIXABLE_GADGET(PointerDereference)
3636
FIXABLE_GADGET(UPCAddressofArraySubscript) // '&DRE[any]' in an Unspecified Pointer Context
3737
FIXABLE_GADGET(UPCStandalonePointer)
3838
FIXABLE_GADGET(UPCPreIncrement) // '++Ptr' in an Unspecified Pointer Context
39+
FIXABLE_GADGET(UUCAddAssign) // 'Ptr += n' in an Unspecified Untyped Context
3940
FIXABLE_GADGET(PointerAssignment)
4041
FIXABLE_GADGET(PointerInit)
4142

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,46 @@ class UPCPreIncrementGadget : public FixableGadget {
10211021
}
10221022
};
10231023

1024+
// Representing a pointer type expression of the form `Ptr += n` in an
1025+
// Unspecified Untyped Context (UUC):
1026+
class UUCAddAssignGadget : public FixableGadget {
1027+
private:
1028+
static constexpr const char *const UUCAddAssignTag =
1029+
"PointerAddAssignUnderUUC";
1030+
static constexpr const char *const OffsetTag = "Offset";
1031+
1032+
const BinaryOperator *Node; // the `Ptr += n` node
1033+
const Expr *Offset = nullptr;
1034+
1035+
public:
1036+
UUCAddAssignGadget(const MatchFinder::MatchResult &Result)
1037+
: FixableGadget(Kind::UUCAddAssign),
1038+
Node(Result.Nodes.getNodeAs<BinaryOperator>(UUCAddAssignTag)),
1039+
Offset(Result.Nodes.getNodeAs<Expr>(OffsetTag)) {
1040+
assert(Node != nullptr && "Expecting a non-null matching result");
1041+
}
1042+
1043+
static bool classof(const Gadget *G) {
1044+
return G->getKind() == Kind::UUCAddAssign;
1045+
}
1046+
1047+
static Matcher matcher() {
1048+
return stmt(isInUnspecifiedUntypedContext(expr(ignoringImpCasts(
1049+
binaryOperator(hasOperatorName("+="),
1050+
hasLHS(declRefExpr(toSupportedVariable())),
1051+
hasRHS(expr().bind(OffsetTag)))
1052+
.bind(UUCAddAssignTag)))));
1053+
}
1054+
1055+
virtual std::optional<FixItList> getFixits(const Strategy &S) const override;
1056+
1057+
virtual const Stmt *getBaseStmt() const override { return Node; }
1058+
1059+
virtual DeclUseList getClaimedVarUseSites() const override {
1060+
return {dyn_cast<DeclRefExpr>(Node->getLHS())};
1061+
}
1062+
};
1063+
10241064
// Representing a fixable expression of the form `*(ptr + 123)` or `*(123 +
10251065
// ptr)`:
10261066
class DerefSimplePtrArithFixableGadget : public FixableGadget {
@@ -1304,21 +1344,29 @@ std::optional<FixItList> PointerInitGadget::getFixits(const Strategy &S) const {
13041344
return std::nullopt;
13051345
}
13061346

1347+
static bool isNonNegativeIntegerExpr(const Expr *Expr, const VarDecl *VD,
1348+
const ASTContext &Ctx) {
1349+
if (auto ConstVal = Expr->getIntegerConstantExpr(Ctx)) {
1350+
if (ConstVal->isNegative())
1351+
return false;
1352+
} else if (!Expr->getType()->isUnsignedIntegerType())
1353+
return false;
1354+
return true;
1355+
}
1356+
13071357
std::optional<FixItList>
13081358
ULCArraySubscriptGadget::getFixits(const Strategy &S) const {
13091359
if (const auto *DRE =
13101360
dyn_cast<DeclRefExpr>(Node->getBase()->IgnoreImpCasts()))
13111361
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
13121362
switch (S.lookup(VD)) {
13131363
case Strategy::Kind::Span: {
1364+
13141365
// If the index has a negative constant value, we give up as no valid
13151366
// fix-it can be generated:
13161367
const ASTContext &Ctx = // FIXME: we need ASTContext to be passed in!
13171368
VD->getASTContext();
1318-
if (auto ConstVal = Node->getIdx()->getIntegerConstantExpr(Ctx)) {
1319-
if (ConstVal->isNegative())
1320-
return std::nullopt;
1321-
} else if (!Node->getIdx()->getType()->isUnsignedIntegerType())
1369+
if (!isNonNegativeIntegerExpr(Node->getIdx(), VD, Ctx))
13221370
return std::nullopt;
13231371
// no-op is a good fix-it, otherwise
13241372
return FixItList{};
@@ -1397,10 +1445,8 @@ static std::optional<SourceLocation> getPastLoc(const NodeTy *Node,
13971445
const LangOptions &LangOpts) {
13981446
SourceLocation Loc =
13991447
Lexer::getLocForEndOfToken(Node->getEndLoc(), 0, SM, LangOpts);
1400-
14011448
if (Loc.isValid())
14021449
return Loc;
1403-
14041450
return std::nullopt;
14051451
}
14061452

@@ -1788,6 +1834,49 @@ UPCPreIncrementGadget::getFixits(const Strategy &S) const {
17881834
return std::nullopt; // Not in the cases that we can handle for now, give up.
17891835
}
17901836

1837+
std::optional<FixItList>
1838+
UUCAddAssignGadget::getFixits(const Strategy &S) const {
1839+
DeclUseList DREs = getClaimedVarUseSites();
1840+
1841+
if (DREs.size() != 1)
1842+
return std::nullopt; // In cases of `Ptr += n` where `Ptr` is not a DRE, we
1843+
// give up
1844+
if (const VarDecl *VD = dyn_cast<VarDecl>(DREs.front()->getDecl())) {
1845+
if (S.lookup(VD) == Strategy::Kind::Span) {
1846+
FixItList Fixes;
1847+
1848+
const Stmt *AddAssignNode = getBaseStmt();
1849+
StringRef varName = VD->getName();
1850+
const ASTContext &Ctx = VD->getASTContext();
1851+
1852+
if (!isNonNegativeIntegerExpr(Offset, VD, Ctx))
1853+
return std::nullopt;
1854+
1855+
// To transform UUC(p += n) to UUC(p = p.subspan(..)):
1856+
bool NotParenExpr =
1857+
(Offset->IgnoreParens()->getBeginLoc() == Offset->getBeginLoc());
1858+
std::string SS = varName.str() + " = " + varName.str() + ".subspan";
1859+
if (NotParenExpr)
1860+
SS += "(";
1861+
1862+
std::optional<SourceLocation> AddAssignLocation = getEndCharLoc(
1863+
AddAssignNode, Ctx.getSourceManager(), Ctx.getLangOpts());
1864+
if (!AddAssignLocation)
1865+
return std::nullopt;
1866+
1867+
Fixes.push_back(FixItHint::CreateReplacement(
1868+
SourceRange(AddAssignNode->getBeginLoc(), Node->getOperatorLoc()),
1869+
SS));
1870+
if (NotParenExpr)
1871+
Fixes.push_back(FixItHint::CreateInsertion(
1872+
Offset->getEndLoc().getLocWithOffset(1), ")"));
1873+
return Fixes;
1874+
}
1875+
}
1876+
return std::nullopt; // Not in the cases that we can handle for now, give up.
1877+
}
1878+
1879+
17911880
// For a non-null initializer `Init` of `T *` type, this function returns
17921881
// `FixItHint`s producing a list initializer `{Init, S}` as a part of a fix-it
17931882
// to output stream.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage \
2+
// RUN: -fsafe-buffer-usage-suggestions \
3+
// RUN: -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
4+
void foo(int * , int *);
5+
6+
void add_assign_test(unsigned int n, int *a, int y) {
7+
int *p = new int[10];
8+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> p"
9+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{"
10+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:23-[[@LINE-3]]:23}:", 10}"
11+
p += 2;
12+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:7}:"p = p.subspan("
13+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:9-[[@LINE-2]]:9}:")"
14+
15+
int *r = p;
16+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> r"
17+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{"
18+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:13-[[@LINE-3]]:13}:", <# placeholder #>}"
19+
while (*r != 0) {
20+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:10-[[@LINE-1]]:11}:""
21+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"[0]"
22+
r += 2;
23+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:5-[[@LINE-1]]:9}:"r = r.subspan("
24+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:11-[[@LINE-2]]:11}:")"
25+
}
26+
27+
if (*p == 0) {
28+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:7-[[@LINE-1]]:8}:""
29+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:9-[[@LINE-2]]:9}:"[0]"
30+
p += n;
31+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:5-[[@LINE-1]]:9}:"p = p.subspan("
32+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:11-[[@LINE-2]]:11}:")"
33+
}
34+
35+
if (*p == 1)
36+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:7-[[@LINE-1]]:8}:""
37+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:9-[[@LINE-2]]:9}:"[0]"
38+
p += 3;
39+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:5-[[@LINE-1]]:9}:"p = p.subspan("
40+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:11-[[@LINE-2]]:11}:")"
41+
42+
a += -9;
43+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:5-[[@LINE-1]]:9}:"p = p.subspan("
44+
45+
a += y;
46+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:5-[[@LINE-1]]:9}:"p = p.subspan("
47+
}
48+
49+
int expr_test(unsigned x, int *q, int y) {
50+
char *p = new char[8];
51+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:12}:"std::span<char> p"
52+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:13-[[@LINE-2]]:13}:"{"
53+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:24-[[@LINE-3]]:24}:", 8}"
54+
p += (x + 1);
55+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:7}:"p = p.subspan"
56+
57+
q += (y + 7);
58+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:7}:"q = q.subspan"
59+
}

0 commit comments

Comments
 (0)