Skip to content

Commit c2411fa

Browse files
committed
[-Wunsafe-buffer-usage] Implement fixits for const size array assigned to pointer
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 1b12f74 commit c2411fa

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ FIXABLE_GADGET(UPCStandalonePointer)
4646
FIXABLE_GADGET(UPCPreIncrement) // '++Ptr' in an Unspecified Pointer Context
4747
FIXABLE_GADGET(UUCAddAssign) // 'Ptr += n' in an Unspecified Untyped Context
4848
FIXABLE_GADGET(PtrToPtrAssignment)
49+
FIXABLE_GADGET(CArrayToPtrAssignment)
4950
FIXABLE_GADGET(PointerInit)
5051

5152
#undef FIXABLE_GADGET

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 72 additions & 0 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"
@@ -849,6 +852,59 @@ class PtrToPtrAssignmentGadget : public FixableGadget {
849852
}
850853
};
851854

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(allOf(hasOperatorName("="),
879+
hasRHS(ignoringParenImpCasts(declRefExpr(hasType(hasCanonicalType(constantArrayType())),
880+
toSupportedVariable()).
881+
bind(PointerAssignRHSTag))),
882+
hasLHS(declRefExpr(hasPointerType(),
883+
toSupportedVariable()).
884+
bind(PointerAssignLHSTag))));
885+
886+
return stmt(isInUnspecifiedUntypedContext(PtrAssignExpr));
887+
}
888+
889+
virtual std::optional<FixItList>
890+
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+
}
897+
898+
virtual DeclUseList getClaimedVarUseSites() const override {
899+
return DeclUseList{PtrLHS, PtrRHS};
900+
}
901+
902+
virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
903+
getStrategyImplications() const override {
904+
return {};
905+
}
906+
};
907+
852908
/// A call of a function or method that performs unchecked buffer operations
853909
/// over one of its pointer parameters.
854910
class UnsafeBufferUsageAttrGadget : public WarningGadget {
@@ -1494,6 +1550,22 @@ PtrToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
14941550
/// \returns fixit that adds .data() call after \DRE.
14951551
static inline std::optional<FixItList> createDataFixit(const ASTContext& Ctx, const DeclRefExpr * DRE);
14961552

1553+
std::optional<FixItList>
1554+
CArrayToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
1555+
const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());
1556+
const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());
1557+
if (S.lookup(LeftVD) == FixitStrategy::Kind::Span) {
1558+
if (S.lookup(RightVD) == FixitStrategy::Kind::Wontfix) {
1559+
return FixItList{};
1560+
}
1561+
} else if (S.lookup(LeftVD) == FixitStrategy::Kind::Wontfix) {
1562+
if (S.lookup(RightVD) == FixitStrategy::Kind::Array) {
1563+
return createDataFixit(RightVD->getASTContext(), PtrRHS);
1564+
}
1565+
}
1566+
return std::nullopt;
1567+
}
1568+
14971569
std::optional<FixItList>
14981570
PointerInitGadget::getFixits(const FixitStrategy &S) const {
14991571
const auto *LeftVD = PtrInitLHS;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
void unsafe_array_assigned_to_unsafe_ptr(unsigned idx) {
35+
int buffer[10];
36+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}}
37+
int* ptr;
38+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}}
39+
ptr = buffer;
40+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}}
41+
buffer[idx] = 0;
42+
ptr[idx] = 0;
43+
}

0 commit comments

Comments
 (0)