From 22c52d8f8ed3240005bcffb1ff30298f0805a470 Mon Sep 17 00:00:00 2001 From: Patryk Stefanski Date: Mon, 10 Nov 2025 14:50:10 -0800 Subject: [PATCH] [-Wunsafe-buffer-usage] Check assignments to __single pointer This introduces a new warning gadget that matches assignments to __single pointers. To verify whether the assignment is safe or not, we use the same logic as for __single pointer arguments. rdar://128158123 (cherry picked from commit 46ed78991dd2ce91b27d1743bd4915124c410432) Conflicts: clang/include/clang/Basic/DiagnosticSemaKinds.td --- .../Analysis/Analyses/UnsafeBufferUsage.h | 7 + .../Analyses/UnsafeBufferUsageGadgets.def | 1 + .../clang/Basic/DiagnosticSemaKinds.td | 3 + clang/lib/Analysis/UnsafeBufferUsage.cpp | 59 +++++++ clang/lib/Sema/AnalysisBasedWarnings.cpp | 7 + ...buffer-usage-single-pointer-assignment.cpp | 157 ++++++++++++++++++ 6 files changed, 234 insertions(+) create mode 100644 clang/test/SemaCXX/warn-unsafe-buffer-usage-single-pointer-assignment.cpp diff --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h index 2692cd440f0e5..7cddb4caea6fd 100644 --- a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h +++ b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h @@ -143,6 +143,13 @@ class UnsafeBufferUsageHandler { handleUnsafeOperation(Arg, IsRelatedToDecl, Ctx); } + /// Invoked when an unsafe assignment to __single pointer is found. + virtual void handleUnsafeSinglePointerAssignment(const BinaryOperator *Assign, + bool IsRelatedToDecl, + ASTContext &Ctx) { + handleUnsafeOperation(Assign, IsRelatedToDecl, Ctx); + } + virtual void handleTooComplexCountAttributedAssign(const Expr *E, const ValueDecl *VD, bool IsRelatedToDecl, diff --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def index 6af6ea2c2c338..c3f9cc7a009f8 100644 --- a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def +++ b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def @@ -47,6 +47,7 @@ WARNING_GADGET(DataInvocation) // TO_UPSTREAM(BoundsSafety) ON WARNING_BOUNDS_SAFETY_GADGET(CountAttributedPointerArgument) WARNING_BOUNDS_SAFETY_GADGET(SinglePointerArgument) +WARNING_BOUNDS_SAFETY_GADGET(SinglePointerAssignment) // TO_UPSTREAM(BoundsSafety) OFF WARNING_OPTIONAL_GADGET(UnsafeLibcFunctionCall) WARNING_OPTIONAL_GADGET(SpanTwoParamConstructor) // Uses of `std::span(arg0, arg1)` diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 702c4f1218344..6749e2fd3502f 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -14114,6 +14114,9 @@ def note_unsafe_count_attributed_pointer_argument_null_to_nonnull : Note< def warn_unsafe_single_pointer_argument : Warning< "unsafe assignment to function parameter of __single pointer type">, InGroup, DefaultIgnore; +def warn_unsafe_single_pointer_assignment : Warning< + "unsafe assignment to __single pointer type">, + InGroup, DefaultIgnore; def warn_assign_to_count_attributed_must_be_simple_stmt : Warning< "assignment to %select{count-attributed pointer|dependent count}0 '%1' " "must be a simple statement '%1 = ...'">, diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp index 7e7e89878b657..e91d262e906f8 100644 --- a/clang/lib/Analysis/UnsafeBufferUsage.cpp +++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -3533,6 +3533,65 @@ class SinglePointerArgumentGadget : public WarningGadget { } }; +// Represents an assignment to a __single pointer. +class SinglePointerAssignmentGadget : public WarningGadget { +private: + static constexpr const char *const AssignTag = "SinglePointerAssignment_Assign"; + const BinaryOperator *Assign; + +public: + explicit SinglePointerAssignmentGadget(const MatchResult &Result) + : WarningGadget(Kind::SinglePointerAssignment), + Assign(Result.getNodeAs(AssignTag)) { + assert(Assign != nullptr && "Expecting a non-null matching result"); + } + + static bool classof(const Gadget *G) { + return G->getKind() == Kind::SinglePointerAssignment; + } + + static bool matches(const Stmt *S, ASTContext &Ctx, + llvm::SmallVectorImpl &Results) { + bool Found = false; + findStmtsInUnspecifiedUntypedContext( + S, [&Results, &Ctx, &Found](const Stmt *S) { + const auto *E = dyn_cast(S); + if (!E) + return; + const auto *BO = dyn_cast(E->IgnoreImpCasts()); + if (!BO || BO->getOpcode() != BO_Assign) + return; + QualType LHSTy = BO->getLHS()->getType(); + if (!LHSTy->isSinglePointerType()) + return; + if (isSinglePointerArgumentSafe(Ctx, LHSTy, BO->getRHS())) + return; + Results.emplace_back(AssignTag, DynTypedNode::create(*BO)); + Found = true; + }); + return Found; + } + + void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler, + bool IsRelatedToDecl, + ASTContext &Ctx) const override { + Handler.handleUnsafeSinglePointerAssignment(Assign, IsRelatedToDecl, Ctx); + } + + SourceLocation getSourceLoc() const override { + return Assign->getOperatorLoc(); + } + + virtual DeclUseList getClaimedVarUseSites() const override { + if (const auto *DRE = dyn_cast(Assign->getLHS())) { + return {DRE}; + } + return {}; + } + + SmallVector getUnsafePtrs() const override { return {}; } +}; + /// Scan the function and return a list of gadgets found with provided kits. class WarningGadgetMatcher : public FastMatcher { diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index a65fc5ca382f2..942b5b1e08929 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2601,6 +2601,13 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { S.Diag(Arg->getBeginLoc(), diag::warn_unsafe_single_pointer_argument); } + void handleUnsafeSinglePointerAssignment( + const BinaryOperator *Assign, [[maybe_unused]] bool IsRelatedToDecl, + [[maybe_unused]] ASTContext &Ctx) override { + S.Diag(Assign->getOperatorLoc(), + diag::warn_unsafe_single_pointer_assignment); + } + void handleTooComplexCountAttributedAssign( const Expr *E, const ValueDecl *VD, [[maybe_unused]] bool IsRelatedToDecl, [[maybe_unused]] ASTContext &Ctx) override { diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-single-pointer-assignment.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-single-pointer-assignment.cpp new file mode 100644 index 0000000000000..d7daad2fbcf3f --- /dev/null +++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-single-pointer-assignment.cpp @@ -0,0 +1,157 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++20 -Wno-all -Wunsafe-buffer-usage -fexperimental-bounds-safety-attributes -verify %s + +#include +#include + +namespace std { + +template +struct array { + T &operator[](size_t n) noexcept; +}; + +template +struct basic_string { + CharT &operator[](size_t n) noexcept; +}; + +typedef basic_string string; + +template +struct basic_string_view { + const CharT &operator[](size_t n) const noexcept; +}; + +typedef basic_string_view string_view; + +template +struct span { + T *data() const noexcept; + span first(size_t count) const noexcept; + span last(size_t count) const noexcept; + span subspan(size_t offset, size_t count) const noexcept; + T &operator[](size_t n) noexcept; +}; + +template +struct vector { + T &operator[](size_t n) noexcept; +}; + +} // namespace std + +template +struct my_vec { + T &operator[](size_t n) noexcept; +}; + +// Check assignment to `void *__single`. + +void single_void(void *__single p_void, void *pv, std::span sp, my_vec &mv) { + char array[42] = {}; + + p_void = pv; + + p_void = sp.data(); + p_void = sp.first(1).data(); + p_void = sp.first(42).data(); + p_void = &sp[42]; + + p_void = &mv[0]; + + p_void = array; +} + +// Check `nullptr`. + +void null(char *__single p_char, + const char *__single p_cchar, + int *__single p_int, + void *__single p_void) { + p_char = nullptr; + p_cchar = nullptr; + p_int = nullptr; + p_void = nullptr; +} + +// Check `&var` pattern. + +void addr_of_var(char *__single p_char, + const char *__single p_cchar, + int *__single p_int, + void *__single p_void) { + char c = 0; + p_char = &c; + p_cchar = &c; + p_void = &c; + + int i = 0; + p_int = &i; + p_void = &i; +} + +// Check allowed classes in `&C[index]` pattern. + +void allowed_class(char *__single p_char, + const char *__single p_cchar, + int *__single p_int, + void *__single p_void, + std::array &a, + std::string &s, + std::string_view sv, + std::span sp, + std::vector &v) { + p_int = &a[0]; + p_void = &a[0]; + + p_char = &s[0]; + p_cchar = &s[0]; + p_void = &s[0]; + + p_cchar = &sv[0]; + + p_int = &sp[0]; + p_void = &sp[0]; + + p_int = &v[0]; + p_void = &v[0]; +} + +void not_allowed_class(int *__single p_int, my_vec &mv) { + p_int = &mv[0]; // expected-warning{{unsafe assignment to __single pointer type}} +} + +// Check if index doesn't matter in `&C[index]` pattern. + +void index_does_not_matter(int *__single p_int, std::span sp, size_t index) { + p_int = &sp[0]; + p_int = &sp[1]; + p_int = &sp[index]; + p_int = &sp[42 - index]; +} + +// Check span's subview pattern. + +void span_subview(int *__single p_int, std::span sp, int n) { + p_int = sp.first(1).data(); + p_int = sp.first(0).data(); // expected-warning{{unsafe assignment to __single pointer type}} + p_int = sp.first(n).data(); // expected-warning{{unsafe assignment to __single pointer type}} + + p_int = sp.last(1).data(); + p_int = sp.last(0).data(); // expected-warning{{unsafe assignment to __single pointer type}} + p_int = sp.last(n).data(); // expected-warning{{unsafe assignment to __single pointer type}} + + p_int = sp.subspan(0, 1).data(); + p_int = sp.subspan(42, 1).data(); + p_int = sp.subspan(n, 1).data(); + p_int = sp.subspan(0, 0).data(); // expected-warning{{unsafe assignment to __single pointer type}} + p_int = sp.subspan(0, n).data(); // expected-warning{{unsafe assignment to __single pointer type}} +} + +// Check common unsafe patterns. + +void unsafe(int *__single p_int, std::span sp, int *p) { + p_int = sp.data(); // expected-warning{{unsafe assignment to __single pointer type}} + + p_int = p; // expected-warning{{unsafe assignment to __single pointer type}} +}