Skip to content

Commit 6fce42f

Browse files
[-Wunsafe-buffer-usage] Add fixits for array to pointer assignment (llvm#81343)
Introducing CArrayToPtrAssignment gadget and implementing fixits for some cases of array being assigned to pointer. Key observations: - const size array can be assigned to std::span and bounds are propagated - const size array can't be on LHS of assignment This means array to pointer assignment has no strategy implications. Fixits are implemented for cases where one of the variables in the assignment is safe. For assignment of a safe array to unsafe pointer we know that the RHS will never be transformed since it's safe and can immediately emit the optimal fixit. Similarly for assignment of unsafe array to safe pointer. (Obviously this is not and can't be future-proof in regards to what variables we consider unsafe and that is fine.) Fixits for assignment from unsafe array to unsafe pointer (from Array to Span strategy) are not implemented in this patch as that needs to be properly designed first - we might possibly implement optimal fixits for partially transformed cases, put both variables in a single fixit group or do something else.
1 parent a7982d5 commit 6fce42f

File tree

4 files changed

+163
-17
lines changed

4 files changed

+163
-17
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ FIXABLE_GADGET(UPCAddressofArraySubscript) // '&DRE[any]' in an Unspecified Poin
4545
FIXABLE_GADGET(UPCStandalonePointer)
4646
FIXABLE_GADGET(UPCPreIncrement) // '++Ptr' in an Unspecified Pointer Context
4747
FIXABLE_GADGET(UUCAddAssign) // 'Ptr += n' in an Unspecified Untyped Context
48-
FIXABLE_GADGET(PointerAssignment)
48+
FIXABLE_GADGET(PtrToPtrAssignment)
49+
FIXABLE_GADGET(CArrayToPtrAssignment)
4950
FIXABLE_GADGET(PointerInit)
5051

5152
#undef FIXABLE_GADGET

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 115 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "clang/Analysis/Analyses/UnsafeBufferUsage.h"
10+
#include "clang/AST/ASTContext.h"
1011
#include "clang/AST/Decl.h"
1112
#include "clang/AST/Expr.h"
1213
#include "clang/AST/RecursiveASTVisitor.h"
14+
#include "clang/AST/Stmt.h"
1315
#include "clang/AST/StmtVisitor.h"
1416
#include "clang/ASTMatchers/ASTMatchFinder.h"
17+
#include "clang/ASTMatchers/ASTMatchers.h"
1518
#include "clang/Basic/CharInfo.h"
1619
#include "clang/Basic/SourceLocation.h"
1720
#include "clang/Lex/Lexer.h"
@@ -799,21 +802,22 @@ class PointerInitGadget : public FixableGadget {
799802
/// \code
800803
/// p = q;
801804
/// \endcode
802-
class PointerAssignmentGadget : public FixableGadget {
805+
/// where both `p` and `q` are pointers.
806+
class PtrToPtrAssignmentGadget : public FixableGadget {
803807
private:
804808
static constexpr const char *const PointerAssignLHSTag = "ptrLHS";
805809
static constexpr const char *const PointerAssignRHSTag = "ptrRHS";
806810
const DeclRefExpr * PtrLHS; // the LHS pointer expression in `PA`
807811
const DeclRefExpr * PtrRHS; // the RHS pointer expression in `PA`
808812

809813
public:
810-
PointerAssignmentGadget(const MatchFinder::MatchResult &Result)
811-
: FixableGadget(Kind::PointerAssignment),
812-
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),
813-
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}
814+
PtrToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)
815+
: FixableGadget(Kind::PtrToPtrAssignment),
816+
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),
817+
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}
814818

815819
static bool classof(const Gadget *G) {
816-
return G->getKind() == Kind::PointerAssignment;
820+
return G->getKind() == Kind::PtrToPtrAssignment;
817821
}
818822

819823
static Matcher matcher() {
@@ -848,6 +852,60 @@ class PointerAssignmentGadget : public FixableGadget {
848852
}
849853
};
850854

855+
/// An assignment expression of the form:
856+
/// \code
857+
/// ptr = array;
858+
/// \endcode
859+
/// where `p` is a pointer and `array` is a constant size array.
860+
class CArrayToPtrAssignmentGadget : public FixableGadget {
861+
private:
862+
static constexpr const char *const PointerAssignLHSTag = "ptrLHS";
863+
static constexpr const char *const PointerAssignRHSTag = "ptrRHS";
864+
const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`
865+
const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`
866+
867+
public:
868+
CArrayToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)
869+
: FixableGadget(Kind::CArrayToPtrAssignment),
870+
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),
871+
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}
872+
873+
static bool classof(const Gadget *G) {
874+
return G->getKind() == Kind::CArrayToPtrAssignment;
875+
}
876+
877+
static Matcher matcher() {
878+
auto PtrAssignExpr = binaryOperator(
879+
allOf(hasOperatorName("="),
880+
hasRHS(ignoringParenImpCasts(
881+
declRefExpr(hasType(hasCanonicalType(constantArrayType())),
882+
toSupportedVariable())
883+
.bind(PointerAssignRHSTag))),
884+
hasLHS(declRefExpr(hasPointerType(), toSupportedVariable())
885+
.bind(PointerAssignLHSTag))));
886+
887+
return stmt(isInUnspecifiedUntypedContext(PtrAssignExpr));
888+
}
889+
890+
virtual std::optional<FixItList>
891+
getFixits(const FixitStrategy &S) const override;
892+
893+
virtual const Stmt *getBaseStmt() const override {
894+
// FIXME: This should be the binary operator, assuming that this method
895+
// makes sense at all on a FixableGadget.
896+
return PtrLHS;
897+
}
898+
899+
virtual DeclUseList getClaimedVarUseSites() const override {
900+
return DeclUseList{PtrLHS, PtrRHS};
901+
}
902+
903+
virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
904+
getStrategyImplications() const override {
905+
return {};
906+
}
907+
};
908+
851909
/// A call of a function or method that performs unchecked buffer operations
852910
/// over one of its pointer parameters.
853911
class UnsafeBufferUsageAttrGadget : public WarningGadget {
@@ -1471,7 +1529,7 @@ bool clang::internal::anyConflict(const SmallVectorImpl<FixItHint> &FixIts,
14711529
}
14721530

14731531
std::optional<FixItList>
1474-
PointerAssignmentGadget::getFixits(const FixitStrategy &S) const {
1532+
PtrToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
14751533
const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());
14761534
const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());
14771535
switch (S.lookup(LeftVD)) {
@@ -1490,6 +1548,42 @@ PointerAssignmentGadget::getFixits(const FixitStrategy &S) const {
14901548
return std::nullopt;
14911549
}
14921550

1551+
/// \returns fixit that adds .data() call after \DRE.
1552+
static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,
1553+
const DeclRefExpr *DRE);
1554+
1555+
std::optional<FixItList>
1556+
CArrayToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
1557+
const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());
1558+
const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());
1559+
// TLDR: Implementing fixits for non-Wontfix strategy on both LHS and RHS is
1560+
// non-trivial.
1561+
//
1562+
// CArrayToPtrAssignmentGadget doesn't have strategy implications because
1563+
// constant size array propagates its bounds. Because of that LHS and RHS are
1564+
// addressed by two different fixits.
1565+
//
1566+
// At the same time FixitStrategy S doesn't reflect what group a fixit belongs
1567+
// to and can't be generally relied on in multi-variable Fixables!
1568+
//
1569+
// E. g. If an instance of this gadget is fixing variable on LHS then the
1570+
// variable on RHS is fixed by a different fixit and its strategy for LHS
1571+
// fixit is as if Wontfix.
1572+
//
1573+
// The only exception is Wontfix strategy for a given variable as that is
1574+
// valid for any fixit produced for the given input source code.
1575+
if (S.lookup(LeftVD) == FixitStrategy::Kind::Span) {
1576+
if (S.lookup(RightVD) == FixitStrategy::Kind::Wontfix) {
1577+
return FixItList{};
1578+
}
1579+
} else if (S.lookup(LeftVD) == FixitStrategy::Kind::Wontfix) {
1580+
if (S.lookup(RightVD) == FixitStrategy::Kind::Array) {
1581+
return createDataFixit(RightVD->getASTContext(), PtrRHS);
1582+
}
1583+
}
1584+
return std::nullopt;
1585+
}
1586+
14931587
std::optional<FixItList>
14941588
PointerInitGadget::getFixits(const FixitStrategy &S) const {
14951589
const auto *LeftVD = PtrInitLHS;
@@ -1907,6 +2001,19 @@ PointerDereferenceGadget::getFixits(const FixitStrategy &S) const {
19072001
return std::nullopt;
19082002
}
19092003

2004+
static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,
2005+
const DeclRefExpr *DRE) {
2006+
const SourceManager &SM = Ctx.getSourceManager();
2007+
// Inserts the .data() after the DRE
2008+
std::optional<SourceLocation> EndOfOperand =
2009+
getPastLoc(DRE, SM, Ctx.getLangOpts());
2010+
2011+
if (EndOfOperand)
2012+
return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}};
2013+
2014+
return std::nullopt;
2015+
}
2016+
19102017
// Generates fix-its replacing an expression of the form UPC(DRE) with
19112018
// `DRE.data()`
19122019
std::optional<FixItList>
@@ -1915,14 +2022,7 @@ UPCStandalonePointerGadget::getFixits(const FixitStrategy &S) const {
19152022
switch (S.lookup(VD)) {
19162023
case FixitStrategy::Kind::Array:
19172024
case FixitStrategy::Kind::Span: {
1918-
ASTContext &Ctx = VD->getASTContext();
1919-
SourceManager &SM = Ctx.getSourceManager();
1920-
// Inserts the .data() after the DRE
1921-
std::optional<SourceLocation> EndOfOperand =
1922-
getPastLoc(Node, SM, Ctx.getLangOpts());
1923-
1924-
if (EndOfOperand)
1925-
return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}};
2025+
return createDataFixit(VD->getASTContext(), Node);
19262026
// FIXME: Points inside a macro expansion.
19272027
break;
19282028
}

clang/test/SemaCXX/warn-unsafe-buffer-usage-debug.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ void unclaimed_use() {
5353
void implied_unclaimed_var(int *b) { // expected-warning{{'b' is an unsafe pointer used for buffer access}}
5454
int *a = new int[3]; // expected-warning{{'a' is an unsafe pointer used for buffer access}}
5555
a[4] = 7; // expected-note{{used in buffer access here}}
56-
a = b; // debug-note{{safe buffers debug: gadget 'PointerAssignment' refused to produce a fix}}
56+
a = b; // debug-note{{safe buffers debug: gadget 'PtrToPtrAssignment' refused to produce a fix}}
5757
b++; // expected-note{{used in pointer arithmetic here}} \
5858
// debug-note{{safe buffers debug: failed to produce fixit for 'b' : has an unclaimed use}}
5959
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
5+
void safe_array_assigned_to_safe_ptr(unsigned idx) {
6+
int buffer[10];
7+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
8+
int* ptr;
9+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
10+
ptr = buffer;
11+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
12+
}
13+
14+
void safe_array_assigned_to_unsafe_ptr(unsigned idx) {
15+
int buffer[10];
16+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
17+
int* ptr;
18+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> ptr"
19+
ptr = buffer;
20+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
21+
ptr[idx] = 0;
22+
}
23+
24+
void unsafe_array_assigned_to_safe_ptr(unsigned idx) {
25+
int buffer[10];
26+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:17}:"std::array<int, 10> buffer"
27+
int* ptr;
28+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
29+
ptr = buffer;
30+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:15-[[@LINE-1]]:15}:".data()"
31+
buffer[idx] = 0;
32+
}
33+
34+
// FIXME: Implement fixit/s for this case.
35+
// See comment in CArrayToPtrAssignmentGadget::getFixits to learn why this hasn't been implemented.
36+
void unsafe_array_assigned_to_unsafe_ptr(unsigned idx) {
37+
int buffer[10];
38+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}}
39+
int* ptr;
40+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}}
41+
ptr = buffer;
42+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}}
43+
buffer[idx] = 0;
44+
ptr[idx] = 0;
45+
}

0 commit comments

Comments
 (0)