Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
927b2f6
Update UnsafeBufferUsage.h
shreya-jain Sep 3, 2025
bae5f2e
Update UnsafeBufferUsageGadgets.def
shreya-jain Sep 3, 2025
284c262
Update DiagnosticGroups.td
shreya-jain Sep 3, 2025
980a75e
Update DiagnosticSemaKinds.td
shreya-jain Sep 3, 2025
11fc596
Update UnsafeBufferUsage.cpp
shreya-jain Sep 3, 2025
800d393
Update AnalysisBasedWarnings.cpp
shreya-jain Sep 3, 2025
4c95972
Update warn-unsafe-buffer-usage-debug-unclaimed.cpp
shreya-jain Sep 3, 2025
38a2d44
fix compilation issues and test
shreya-jain Sep 3, 2025
dd1ab78
Merge branch 'main' into add-uniqueptr-to-unsafe-buffer-usage
shreya-jain Sep 4, 2025
aacc510
Merge branch 'main' into add-uniqueptr-to-unsafe-buffer-usage
shreya-jain Sep 8, 2025
f074fd0
Merge branch 'main' into add-uniqueptr-to-unsafe-buffer-usage
shreya-jain Sep 9, 2025
5bc6580
address review comments
shreya-jain Sep 16, 2025
766be17
Merge branch 'add-uniqueptr-to-unsafe-buffer-usage' of github.com:shr…
shreya-jain Sep 16, 2025
eb9afef
Merge branch 'main' into add-uniqueptr-to-unsafe-buffer-usage
shreya-jain Sep 16, 2025
efb19b6
[-Wunsafe-buffer-usage] Add unique_ptr <T[]> accesses
shreya-jain Sep 18, 2025
7cefe30
[-Wunsafe-buffer-usage] Add unique_ptr <T[]> accesses
shreya-jain Sep 18, 2025
6cf2c9f
[-Wunsafe-buffer-usage] Add unique_ptr <T[]> accesses
shreya-jain Sep 18, 2025
f805faf
[-Wunsafe-buffer-usage] Add unique_ptr <T[]> accesses
shreya-jain Sep 30, 2025
db8a2bc
Merge branch 'main' into add-uniqueptr-to-unsafe-buffer-usage
shreya-jain Oct 1, 2025
9d597ed
Merge branch 'main' into add-uniqueptr-to-unsafe-buffer-usage
shreya-jain Oct 1, 2025
2b823cb
Merge branch 'main' into add-uniqueptr-to-unsafe-buffer-usage
shreya-jain Oct 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_UNSAFEBUFFERUSAGE_H
#define LLVM_CLANG_ANALYSIS_ANALYSES_UNSAFEBUFFERUSAGE_H

#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Stmt.h"
Expand Down Expand Up @@ -139,6 +140,12 @@ class UnsafeBufferUsageHandler {
FixItList &&Fixes, const Decl *D,
const FixitStrategy &VarTargetTypes) = 0;

// Invoked when an array subscript operator[] is used on a
// std::unique_ptr<T[]>.
virtual void handleUnsafeUniquePtrArrayAccess(const DynTypedNode &Node,
bool IsRelatedToDecl,
ASTContext &Ctx) = 0;

#ifndef NDEBUG
public:
bool areDebugNotesRequested() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ WARNING_GADGET(PointerArithmetic)
WARNING_GADGET(UnsafeBufferUsageAttr)
WARNING_GADGET(UnsafeBufferUsageCtorAttr)
WARNING_GADGET(DataInvocation)
WARNING_GADGET(UniquePtrArrayAccess)
WARNING_OPTIONAL_GADGET(UnsafeLibcFunctionCall)
WARNING_OPTIONAL_GADGET(SpanTwoParamConstructor) // Uses of `std::span(arg0, arg1)`
FIXABLE_GADGET(ULCArraySubscript) // `DRE[any]` in an Unspecified Lvalue Context
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -1750,7 +1750,8 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
// Warnings and fixes to support the "safe buffers" programming model.
def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">;
def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">;
def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall]>;
def UnsafeBufferUsageInUniquePtrArrayAccess : DiagGroup<"unsafe-buffer-usage-in-unique-ptr-array-access">;
def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall, UnsafeBufferUsageInUniquePtrArrayAccess]>;

// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -13243,6 +13243,8 @@ def note_safe_buffer_usage_suggestions_disabled : Note<
def warn_unsafe_buffer_usage_in_container : Warning<
"the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information">,
InGroup<UnsafeBufferUsageInContainer>, DefaultIgnore;
def warn_unsafe_buffer_usage_unique_ptr_array_access : Warning<"direct access using operator[] on std::unique_ptr<T[]> is unsafe due to lack of bounds checking">,
InGroup<UnsafeBufferUsageInUniquePtrArrayAccess>, DefaultIgnore;
#ifndef NDEBUG
// Not a user-facing diagnostic. Useful for debugging false negatives in
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).
Expand Down
107 changes: 105 additions & 2 deletions clang/lib/Analysis/UnsafeBufferUsage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/AST/Expr.h"
#include "clang/AST/FormatString.h"
Expand Down Expand Up @@ -1318,6 +1319,105 @@ static bool isSupportedVariable(const DeclRefExpr &Node) {
return D != nullptr && isa<VarDecl>(D);
}

static bool isUniquePtrArray(const CXXRecordDecl *RecordDecl) {
if (!RecordDecl || !RecordDecl->isInStdNamespace() ||
RecordDecl->getNameAsString() != "unique_ptr") {
return false;
}

const ClassTemplateSpecializationDecl *class_template_specialization_decl =
dyn_cast<ClassTemplateSpecializationDecl>(RecordDecl);
if (!class_template_specialization_decl) {
return false;
}

const TemplateArgumentList &template_args =
class_template_specialization_decl->getTemplateArgs();

if (template_args.size() == 0) {
return false;
}

const TemplateArgument &first_arg = template_args[0];

if (first_arg.getKind() != TemplateArgument::Type) {
return false;
}

QualType referred_type = first_arg.getAsType();

if (referred_type->isArrayType()) {
return true;
}

return false;
}

class UniquePtrArrayAccessGadget : public WarningGadget {
static constexpr const char *const AccessorTag = "unique_ptr_array_access";
const CXXOperatorCallExpr *TheAccessorExpr;

public:
UniquePtrArrayAccessGadget(const MatchResult &Result)
: WarningGadget(Kind::UniquePtrArrayAccess),
TheAccessorExpr(Result.getNodeAs<CXXOperatorCallExpr>(AccessorTag)) {
assert(TheAccessorExpr &&
"UniquePtrArrayAccessGadget requires a matched CXXOperatorCallExpr");
}

static bool classof(const Gadget *G) {
return G->getKind() == Kind::UniquePtrArrayAccess;
}

static bool matches(const Stmt *S, const ASTContext &Ctx,
MatchResult &Result) {

const CXXOperatorCallExpr *OpCall = dyn_cast<CXXOperatorCallExpr>(S);
if (!OpCall || OpCall->getOperator() != OO_Subscript) {
return false;
}

const Expr *Callee = OpCall->getCallee()->IgnoreParenImpCasts();
if (!Callee) {
return false;
}

const CXXMethodDecl *Method =
dyn_cast_or_null<CXXMethodDecl>(OpCall->getDirectCallee());
if (!Method) {
return false;
}

if (Method->getNameAsString() != "operator[]") {
return false;
}

const CXXRecordDecl *RecordDecl = Method->getParent();
if (!isUniquePtrArray(RecordDecl)) {
return false;
}

Result.addNode(AccessorTag, DynTypedNode::create(*OpCall));
return true;
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeUniquePtrArrayAccess(
DynTypedNode::create(*TheAccessorExpr), IsRelatedToDecl, Ctx);
}

SourceLocation getSourceLoc() const override {
if (TheAccessorExpr) {
return TheAccessorExpr->getOperatorLoc();
}
return SourceLocation();
}

DeclUseList getClaimedVarUseSites() const override { return {}; }
SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; }
};

using FixableGadgetList = std::vector<std::unique_ptr<FixableGadget>>;
using WarningGadgetList = std::vector<std::unique_ptr<WarningGadget>>;

Expand Down Expand Up @@ -2632,10 +2732,13 @@ std::set<const Expr *> clang::findUnsafePointers(const FunctionDecl *FD) {
const VariableGroupsManager &, FixItList &&,
const Decl *,
const FixitStrategy &) override {}
bool isSafeBufferOptOut(const SourceLocation &) const override {
void handleUnsafeUniquePtrArrayAccess(const DynTypedNode &Node,
bool IsRelatedToDecl,
ASTContext &Ctx) override {}
bool ignoreUnsafeBufferInContainer(const SourceLocation &) const override {
return false;
}
bool ignoreUnsafeBufferInContainer(const SourceLocation &) const override {
bool isSafeBufferOptOut(const SourceLocation &) const override {
return false;
}
bool ignoreUnsafeBufferInLibcCall(const SourceLocation &) const override {
Expand Down
13 changes: 13 additions & 0 deletions clang/lib/Sema/AnalysisBasedWarnings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2606,6 +2606,19 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
#endif
}

void handleUnsafeUniquePtrArrayAccess(const DynTypedNode &Node,
bool IsRelatedToDecl,
ASTContext &Ctx) override {
SourceLocation Loc;
std::string Message;

Loc = Node.get<Stmt>()->getBeginLoc();
Message = "Direct operator[] access on std::unique_ptr<T[]> is unsafe "
"(no bounds check).";
S.Diag(Loc, diag::warn_unsafe_buffer_usage_unique_ptr_array_access)
<< Message << Node.getSourceRange();
}

bool isSafeBufferOptOut(const SourceLocation &Loc) const override {
return S.PP.isSafeBufferOptOut(S.getSourceManager(), Loc);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ void test_unclaimed_use(int *p) { // expected-warning{{'p' is an unsafe pointer
p[5] = 5; // expected-note{{used in buffer access here}}
}

namespace std {
inline namespace __1 {
template <class T> class unique_ptr {
public:
T &operator[](long long i) const;
};
} // namespace __1
} // namespace std

void basic_unique_ptr() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add more test cases - I imagine at a minimum we should have these:

  • index is zero
  • index is a non-zero integer literal
  • index is a variable
  • index is a simple arithmetic expression like i + 5

Copy link
Contributor Author

@shreya-jain shreya-jain Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some more tests.

Added a case where index is 0 fails for when it's not a compile time constant. Not sure if that is the desired behavior

int k = 0; 
buffer[k]; // will flag

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is fine. The warning is not path sensitive.

std::unique_ptr<int[]> p1;
p1[0]; // expected-warning{{direct access using operator[] on std::unique_ptr<T[]> is unsafe due to lack of bounds checking}}
}

// CHECK: Root # 1
// CHECK: |- DeclRefExpr # 4
// CHECK: |-- UnaryOperator(++) # 1
Expand Down