Skip to content

Commit 24bd3dd

Browse files
authored
Merge pull request swiftlang#8197 from jkorous-apple/cxx-safe-buffers/integrate/cxx-safe-buffers/array-assigned-to-ptr
[-Wunsafe-buffer-usage] Add fixits for array to pointer assignment (#…
2 parents 5fe208d + 4dbaf18 commit 24bd3dd

File tree

4 files changed

+161
-15
lines changed

4 files changed

+161
-15
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: 113 additions & 13 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"
@@ -798,21 +801,22 @@ class PointerInitGadget : public FixableGadget {
798801
/// \code
799802
/// p = q;
800803
/// \endcode
801-
class PointerAssignmentGadget : public FixableGadget {
804+
/// where both `p` and `q` are pointers.
805+
class PtrToPtrAssignmentGadget : public FixableGadget {
802806
private:
803807
static constexpr const char *const PointerAssignLHSTag = "ptrLHS";
804808
static constexpr const char *const PointerAssignRHSTag = "ptrRHS";
805809
const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`
806810
const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`
807811

808812
public:
809-
PointerAssignmentGadget(const MatchFinder::MatchResult &Result)
810-
: FixableGadget(Kind::PointerAssignment),
813+
PtrToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)
814+
: FixableGadget(Kind::PtrToPtrAssignment),
811815
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),
812816
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}
813817

814818
static bool classof(const Gadget *G) {
815-
return G->getKind() == Kind::PointerAssignment;
819+
return G->getKind() == Kind::PtrToPtrAssignment;
816820
}
817821

818822
static Matcher matcher() {
@@ -847,6 +851,60 @@ class PointerAssignmentGadget : public FixableGadget {
847851
}
848852
};
849853

854+
/// An assignment expression of the form:
855+
/// \code
856+
/// ptr = array;
857+
/// \endcode
858+
/// where `p` is a pointer and `array` is a constant size array.
859+
class CArrayToPtrAssignmentGadget : public FixableGadget {
860+
private:
861+
static constexpr const char *const PointerAssignLHSTag = "ptrLHS";
862+
static constexpr const char *const PointerAssignRHSTag = "ptrRHS";
863+
const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`
864+
const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`
865+
866+
public:
867+
CArrayToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)
868+
: FixableGadget(Kind::CArrayToPtrAssignment),
869+
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),
870+
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}
871+
872+
static bool classof(const Gadget *G) {
873+
return G->getKind() == Kind::CArrayToPtrAssignment;
874+
}
875+
876+
static Matcher matcher() {
877+
auto PtrAssignExpr = binaryOperator(
878+
allOf(hasOperatorName("="),
879+
hasRHS(ignoringParenImpCasts(
880+
declRefExpr(hasType(hasCanonicalType(constantArrayType())),
881+
toSupportedVariable())
882+
.bind(PointerAssignRHSTag))),
883+
hasLHS(declRefExpr(hasPointerType(), 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+
850908
/// A call of a function or method that performs unchecked buffer operations
851909
/// over one of its pointer parameters.
852910
class UnsafeBufferUsageAttrGadget : public WarningGadget {
@@ -1466,7 +1524,7 @@ bool clang::internal::anyConflict(const SmallVectorImpl<FixItHint> &FixIts,
14661524
}
14671525

14681526
std::optional<FixItList>
1469-
PointerAssignmentGadget::getFixits(const FixitStrategy &S) const {
1527+
PtrToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
14701528
const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());
14711529
const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());
14721530
switch (S.lookup(LeftVD)) {
@@ -1485,6 +1543,42 @@ PointerAssignmentGadget::getFixits(const FixitStrategy &S) const {
14851543
return std::nullopt;
14861544
}
14871545

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

1999+
static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,
2000+
const DeclRefExpr *DRE) {
2001+
const SourceManager &SM = Ctx.getSourceManager();
2002+
// Inserts the .data() after the DRE
2003+
std::optional<SourceLocation> EndOfOperand =
2004+
getPastLoc(DRE, SM, Ctx.getLangOpts());
2005+
2006+
if (EndOfOperand)
2007+
return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}};
2008+
2009+
return std::nullopt;
2010+
}
2011+
19052012
// Generates fix-its replacing an expression of the form UPC(DRE) with
19062013
// `DRE.data()`
19072014
std::optional<FixItList>
@@ -1910,14 +2017,7 @@ UPCStandalonePointerGadget::getFixits(const FixitStrategy &S) const {
19102017
switch (S.lookup(VD)) {
19112018
case FixitStrategy::Kind::Array:
19122019
case FixitStrategy::Kind::Span: {
1913-
ASTContext &Ctx = VD->getASTContext();
1914-
SourceManager &SM = Ctx.getSourceManager();
1915-
// Inserts the .data() after the DRE
1916-
std::optional<SourceLocation> EndOfOperand =
1917-
getPastLoc(Node, SM, Ctx.getLangOpts());
1918-
1919-
if (EndOfOperand)
1920-
return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}};
2020+
return createDataFixit(VD->getASTContext(), Node);
19212021
// FIXME: Points inside a macro expansion.
19222022
break;
19232023
}

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)