-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[-Wunsafe-buffer-usage] Add fixits for array to pointer assignment #81343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
791130c
1b12f74
c2411fa
5591e8e
3260557
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,11 +7,14 @@ | |
//===----------------------------------------------------------------------===// | ||
|
||
#include "clang/Analysis/Analyses/UnsafeBufferUsage.h" | ||
#include "clang/AST/ASTContext.h" | ||
#include "clang/AST/Decl.h" | ||
#include "clang/AST/Expr.h" | ||
#include "clang/AST/RecursiveASTVisitor.h" | ||
#include "clang/AST/Stmt.h" | ||
#include "clang/AST/StmtVisitor.h" | ||
#include "clang/ASTMatchers/ASTMatchFinder.h" | ||
#include "clang/ASTMatchers/ASTMatchers.h" | ||
#include "clang/Basic/CharInfo.h" | ||
#include "clang/Basic/SourceLocation.h" | ||
#include "clang/Lex/Lexer.h" | ||
|
@@ -799,21 +802,22 @@ class PointerInitGadget : public FixableGadget { | |
/// \code | ||
/// p = q; | ||
/// \endcode | ||
class PointerAssignmentGadget : public FixableGadget { | ||
/// where both `p` and `q` are pointers. | ||
class PtrToPtrAssignmentGadget : public FixableGadget { | ||
private: | ||
static constexpr const char *const PointerAssignLHSTag = "ptrLHS"; | ||
static constexpr const char *const PointerAssignRHSTag = "ptrRHS"; | ||
const DeclRefExpr * PtrLHS; // the LHS pointer expression in `PA` | ||
const DeclRefExpr * PtrRHS; // the RHS pointer expression in `PA` | ||
|
||
public: | ||
PointerAssignmentGadget(const MatchFinder::MatchResult &Result) | ||
: FixableGadget(Kind::PointerAssignment), | ||
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)), | ||
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {} | ||
PtrToPtrAssignmentGadget(const MatchFinder::MatchResult &Result) | ||
: FixableGadget(Kind::PtrToPtrAssignment), | ||
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)), | ||
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {} | ||
|
||
static bool classof(const Gadget *G) { | ||
return G->getKind() == Kind::PointerAssignment; | ||
return G->getKind() == Kind::PtrToPtrAssignment; | ||
} | ||
|
||
static Matcher matcher() { | ||
|
@@ -848,6 +852,60 @@ class PointerAssignmentGadget : public FixableGadget { | |
} | ||
}; | ||
|
||
/// An assignment expression of the form: | ||
/// \code | ||
/// ptr = array; | ||
/// \endcode | ||
/// where `p` is a pointer and `array` is a constant size array. | ||
class CArrayToPtrAssignmentGadget : public FixableGadget { | ||
private: | ||
static constexpr const char *const PointerAssignLHSTag = "ptrLHS"; | ||
static constexpr const char *const PointerAssignRHSTag = "ptrRHS"; | ||
const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA` | ||
const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA` | ||
|
||
public: | ||
CArrayToPtrAssignmentGadget(const MatchFinder::MatchResult &Result) | ||
: FixableGadget(Kind::CArrayToPtrAssignment), | ||
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)), | ||
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {} | ||
|
||
static bool classof(const Gadget *G) { | ||
return G->getKind() == Kind::CArrayToPtrAssignment; | ||
} | ||
|
||
static Matcher matcher() { | ||
auto PtrAssignExpr = binaryOperator( | ||
allOf(hasOperatorName("="), | ||
hasRHS(ignoringParenImpCasts( | ||
declRefExpr(hasType(hasCanonicalType(constantArrayType())), | ||
toSupportedVariable()) | ||
.bind(PointerAssignRHSTag))), | ||
hasLHS(declRefExpr(hasPointerType(), toSupportedVariable()) | ||
.bind(PointerAssignLHSTag)))); | ||
|
||
return stmt(isInUnspecifiedUntypedContext(PtrAssignExpr)); | ||
} | ||
|
||
virtual std::optional<FixItList> | ||
getFixits(const FixitStrategy &S) const override; | ||
|
||
virtual const Stmt *getBaseStmt() const override { | ||
// FIXME: This should be the binary operator, assuming that this method | ||
// makes sense at all on a FixableGadget. | ||
return PtrLHS; | ||
} | ||
|
||
virtual DeclUseList getClaimedVarUseSites() const override { | ||
return DeclUseList{PtrLHS, PtrRHS}; | ||
} | ||
|
||
virtual std::optional<std::pair<const VarDecl *, const VarDecl *>> | ||
getStrategyImplications() const override { | ||
return {}; | ||
} | ||
}; | ||
|
||
/// A call of a function or method that performs unchecked buffer operations | ||
/// over one of its pointer parameters. | ||
class UnsafeBufferUsageAttrGadget : public WarningGadget { | ||
|
@@ -1471,7 +1529,7 @@ bool clang::internal::anyConflict(const SmallVectorImpl<FixItHint> &FixIts, | |
} | ||
|
||
std::optional<FixItList> | ||
PointerAssignmentGadget::getFixits(const FixitStrategy &S) const { | ||
PtrToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const { | ||
const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl()); | ||
const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl()); | ||
switch (S.lookup(LeftVD)) { | ||
|
@@ -1490,6 +1548,42 @@ PointerAssignmentGadget::getFixits(const FixitStrategy &S) const { | |
return std::nullopt; | ||
} | ||
|
||
/// \returns fixit that adds .data() call after \DRE. | ||
static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx, | ||
const DeclRefExpr *DRE); | ||
|
||
std::optional<FixItList> | ||
CArrayToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there should be a comment explaining why the "both sides are fixed" case is tricky. Otherwise somebody may accidentally implement it 😅 |
||
const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl()); | ||
const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl()); | ||
// TLDR: Implementing fixits for non-Wontfix strategy on both LHS and RHS is | ||
// non-trivial. | ||
// | ||
// CArrayToPtrAssignmentGadget doesn't have strategy implications because | ||
// constant size array propagates its bounds. Because of that LHS and RHS are | ||
// addressed by two different fixits. | ||
// | ||
// At the same time FixitStrategy S doesn't reflect what group a fixit belongs | ||
// to and can't be generally relied on in multi-variable Fixables! | ||
// | ||
// E. g. If an instance of this gadget is fixing variable on LHS then the | ||
// variable on RHS is fixed by a different fixit and its strategy for LHS | ||
// fixit is as if Wontfix. | ||
// | ||
// The only exception is Wontfix strategy for a given variable as that is | ||
// valid for any fixit produced for the given input source code. | ||
if (S.lookup(LeftVD) == FixitStrategy::Kind::Span) { | ||
if (S.lookup(RightVD) == FixitStrategy::Kind::Wontfix) { | ||
return FixItList{}; | ||
} | ||
} else if (S.lookup(LeftVD) == FixitStrategy::Kind::Wontfix) { | ||
if (S.lookup(RightVD) == FixitStrategy::Kind::Array) { | ||
return createDataFixit(RightVD->getASTContext(), PtrRHS); | ||
} | ||
} | ||
return std::nullopt; | ||
} | ||
|
||
std::optional<FixItList> | ||
PointerInitGadget::getFixits(const FixitStrategy &S) const { | ||
const auto *LeftVD = PtrInitLHS; | ||
|
@@ -1907,6 +2001,19 @@ PointerDereferenceGadget::getFixits(const FixitStrategy &S) const { | |
return std::nullopt; | ||
} | ||
|
||
static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx, | ||
const DeclRefExpr *DRE) { | ||
const SourceManager &SM = Ctx.getSourceManager(); | ||
// Inserts the .data() after the DRE | ||
std::optional<SourceLocation> EndOfOperand = | ||
getPastLoc(DRE, SM, Ctx.getLangOpts()); | ||
|
||
if (EndOfOperand) | ||
return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}}; | ||
|
||
return std::nullopt; | ||
} | ||
|
||
// Generates fix-its replacing an expression of the form UPC(DRE) with | ||
// `DRE.data()` | ||
std::optional<FixItList> | ||
|
@@ -1915,14 +2022,7 @@ UPCStandalonePointerGadget::getFixits(const FixitStrategy &S) const { | |
switch (S.lookup(VD)) { | ||
case FixitStrategy::Kind::Array: | ||
case FixitStrategy::Kind::Span: { | ||
ASTContext &Ctx = VD->getASTContext(); | ||
SourceManager &SM = Ctx.getSourceManager(); | ||
// Inserts the .data() after the DRE | ||
std::optional<SourceLocation> EndOfOperand = | ||
getPastLoc(Node, SM, Ctx.getLangOpts()); | ||
|
||
if (EndOfOperand) | ||
return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}}; | ||
return createDataFixit(VD->getASTContext(), Node); | ||
// FIXME: Points inside a macro expansion. | ||
break; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage \ | ||
// RUN: -fsafe-buffer-usage-suggestions \ | ||
// RUN: -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s | ||
|
||
void safe_array_assigned_to_safe_ptr(unsigned idx) { | ||
int buffer[10]; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]: | ||
int* ptr; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]: | ||
ptr = buffer; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]: | ||
} | ||
|
||
void safe_array_assigned_to_unsafe_ptr(unsigned idx) { | ||
int buffer[10]; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]: | ||
int* ptr; | ||
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> ptr" | ||
ptr = buffer; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]: | ||
ptr[idx] = 0; | ||
} | ||
|
||
void unsafe_array_assigned_to_safe_ptr(unsigned idx) { | ||
int buffer[10]; | ||
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:17}:"std::array<int, 10> buffer" | ||
int* ptr; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]: | ||
ptr = buffer; | ||
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:15-[[@LINE-1]]:15}:".data()" | ||
buffer[idx] = 0; | ||
} | ||
|
||
// FIXME: Implement fixit/s for this case. | ||
// See comment in CArrayToPtrAssignmentGadget::getFixits to learn why this hasn't been implemented. | ||
void unsafe_array_assigned_to_unsafe_ptr(unsigned idx) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly, let's mark this test as a FIXME test, and add a comment explaining why this is hard and the naive fix would be incorrect. |
||
int buffer[10]; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}} | ||
int* ptr; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}} | ||
ptr = buffer; | ||
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:{{.*}} | ||
buffer[idx] = 0; | ||
ptr[idx] = 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably the default behavior 🤔