Skip to content

Commit 867f82a

Browse files
malavikasamakMalavikaSamak
andcommitted
[-Wunsafe-buffer-usage] Warning for unsafe invocation of span::data (llvm#75650)
…-Wunsafe-buffer-usage, there maybe accidental re-introduction of new OutOfBound accesses into the code bases. One such case is invoking span::data() method on a span variable to retrieve a pointer, which is then cast to a larger type and dereferenced. Such dereferences can introduce OutOfBound accesses. To address this, a new WarningGadget is being introduced to warn against such invocations. --------- Co-authored-by: MalavikaSamak <[email protected]> (cherry picked from commit 7122f55)
1 parent 688d0dd commit 867f82a

File tree

6 files changed

+182
-8
lines changed

6 files changed

+182
-8
lines changed

clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class UnsafeBufferUsageHandler {
6666

6767
/// Invoked when an unsafe operation over raw pointers is found.
6868
virtual void handleUnsafeOperation(const Stmt *Operation,
69-
bool IsRelatedToDecl) = 0;
69+
bool IsRelatedToDecl, ASTContext &Ctx) = 0;
7070

7171
/// Invoked when a fix is suggested against a variable. This function groups
7272
/// all variables that must be fixed together (i.e their types must be changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ WARNING_GADGET(Decrement)
3030
WARNING_GADGET(ArraySubscript)
3131
WARNING_GADGET(PointerArithmetic)
3232
WARNING_GADGET(UnsafeBufferUsageAttr)
33+
WARNING_GADGET(DataInvocation)
3334
FIXABLE_GADGET(ULCArraySubscript) // `DRE[any]` in an Unspecified Lvalue Context
3435
FIXABLE_GADGET(DerefSimplePtrArithFixable)
3536
FIXABLE_GADGET(PointerDereference)

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11959,7 +11959,7 @@ def warn_unsafe_buffer_variable : Warning<
1195911959
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1196011960
def warn_unsafe_buffer_operation : Warning<
1196111961
"%select{unsafe pointer operation|unsafe pointer arithmetic|"
11962-
"unsafe buffer access|function introduces unsafe buffer manipulation}0">,
11962+
"unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data}0">,
1196311963
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1196411964
def note_unsafe_buffer_operation : Note<
1196511965
"used%select{| in pointer arithmetic| in buffer access}0 here">;

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,34 @@ class UnsafeBufferUsageAttrGadget : public WarningGadget {
719719
DeclUseList getClaimedVarUseSites() const override { return {}; }
720720
};
721721

722+
// Warning gadget for unsafe invocation of span::data method.
723+
// Triggers when the pointer returned by the invocation is immediately
724+
// cast to a larger type.
725+
726+
class DataInvocationGadget : public WarningGadget {
727+
constexpr static const char *const OpTag = "data_invocation_expr";
728+
const ExplicitCastExpr *Op;
729+
730+
public:
731+
DataInvocationGadget(const MatchFinder::MatchResult &Result)
732+
: WarningGadget(Kind::DataInvocation),
733+
Op(Result.Nodes.getNodeAs<ExplicitCastExpr>(OpTag)) {}
734+
735+
static bool classof(const Gadget *G) {
736+
return G->getKind() == Kind::DataInvocation;
737+
}
738+
739+
static Matcher matcher() {
740+
return stmt(
741+
explicitCastExpr(has(cxxMemberCallExpr(callee(cxxMethodDecl(
742+
hasName("data"), ofClass(hasName("std::span")))))))
743+
.bind(OpTag));
744+
}
745+
const Stmt *getBaseStmt() const override { return Op; }
746+
747+
DeclUseList getClaimedVarUseSites() const override { return {}; }
748+
};
749+
722750
// Represents expressions of the form `DRE[*]` in the Unspecified Lvalue
723751
// Context (see `isInUnspecifiedLvalueContext`).
724752
// Note here `[]` is the built-in subscript operator.
@@ -2653,8 +2681,8 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
26532681
// every problematic operation and consider it done. No need to deal
26542682
// with fixable gadgets, no need to group operations by variable.
26552683
for (const auto &G : WarningGadgets) {
2656-
Handler.handleUnsafeOperation(G->getBaseStmt(),
2657-
/*IsRelatedToDecl=*/false);
2684+
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/false,
2685+
D->getASTContext());
26582686
}
26592687

26602688
// This return guarantees that most of the machine doesn't run when
@@ -2889,7 +2917,8 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
28892917
Tracker, Handler, VarGrpMgr);
28902918

28912919
for (const auto &G : UnsafeOps.noVar) {
2892-
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/false);
2920+
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/false,
2921+
D->getASTContext());
28932922
}
28942923

28952924
for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {
@@ -2900,7 +2929,8 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
29002929
: FixItList{},
29012930
D);
29022931
for (const auto &G : WarningGadgets) {
2903-
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/true);
2932+
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/true,
2933+
D->getASTContext());
29042934
}
29052935
}
29062936
}

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,8 +2202,8 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
22022202
UnsafeBufferUsageReporter(Sema &S, bool SuggestSuggestions)
22032203
: S(S), SuggestSuggestions(SuggestSuggestions) {}
22042204

2205-
void handleUnsafeOperation(const Stmt *Operation,
2206-
bool IsRelatedToDecl) override {
2205+
void handleUnsafeOperation(const Stmt *Operation, bool IsRelatedToDecl,
2206+
ASTContext &Ctx) override {
22072207
SourceLocation Loc;
22082208
SourceRange Range;
22092209
unsigned MsgParam = 0;
@@ -2237,6 +2237,18 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
22372237
// note_unsafe_buffer_operation doesn't have this mode yet.
22382238
assert(!IsRelatedToDecl && "Not implemented yet!");
22392239
MsgParam = 3;
2240+
} else if (const auto *ECE = dyn_cast<ExplicitCastExpr>(Operation)) {
2241+
QualType destType = ECE->getType();
2242+
const uint64_t dSize =
2243+
Ctx.getTypeSize(destType.getTypePtr()->getPointeeType());
2244+
if (const auto *CE = dyn_cast<CXXMemberCallExpr>(ECE->getSubExpr())) {
2245+
QualType srcType = CE->getType();
2246+
const uint64_t sSize =
2247+
Ctx.getTypeSize(srcType.getTypePtr()->getPointeeType());
2248+
if (sSize >= dSize)
2249+
return;
2250+
}
2251+
MsgParam = 4;
22402252
}
22412253
Loc = Operation->getBeginLoc();
22422254
Range = Operation->getSourceRange();
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \
2+
// RUN: -fsafe-buffer-usage-suggestions \
3+
// RUN: -fblocks -include %s -verify %s
4+
5+
// RUN: %clang -x c++ -frtti -fsyntax-only -fblocks -include %s %s 2>&1 | FileCheck --allow-empty %s
6+
// RUN: %clang_cc1 -std=c++11 -fblocks -include %s %s 2>&1 | FileCheck --allow-empty %s
7+
// RUN: %clang_cc1 -std=c++20 -fblocks -include %s %s 2>&1 | FileCheck --allow-empty %s
8+
// CHECK-NOT: [-Wunsafe-buffer-usage]
9+
10+
#ifndef INCLUDED
11+
#define INCLUDED
12+
#pragma clang system_header
13+
14+
// no spanification warnings for system headers
15+
#else
16+
17+
namespace std {
18+
class type_info;
19+
class bad_cast;
20+
class bad_typeid;
21+
}
22+
using size_t = __typeof(sizeof(int));
23+
void *malloc(size_t);
24+
25+
void foo(int v) {
26+
}
27+
28+
void foo(int *p){}
29+
30+
namespace std{
31+
template <typename T> class span {
32+
33+
T *elements;
34+
35+
span(T *, unsigned){}
36+
37+
public:
38+
39+
constexpr span<T> subspan(size_t offset, size_t count) const {
40+
return span<T> (elements+offset, count); // expected-warning{{unsafe pointer arithmetic}}
41+
}
42+
43+
constexpr T* data() const noexcept {
44+
return elements;
45+
}
46+
47+
48+
constexpr T* hello() const noexcept {
49+
return elements;
50+
}
51+
};
52+
53+
template <typename T> class span_duplicate {
54+
span_duplicate(T *, unsigned){}
55+
56+
T array[10];
57+
58+
public:
59+
60+
T* data() {
61+
return array;
62+
}
63+
64+
};
65+
}
66+
67+
using namespace std;
68+
69+
class A {
70+
int a, b, c;
71+
};
72+
73+
class B {
74+
int a, b, c;
75+
};
76+
77+
struct Base {
78+
virtual ~Base() = default;
79+
};
80+
81+
struct Derived: Base {
82+
int d;
83+
};
84+
85+
void cast_without_data(int *ptr) {
86+
A *a = (A*) ptr;
87+
float *p = (float*) ptr;
88+
}
89+
90+
void warned_patterns(std::span<int> span_ptr, std::span<Base> base_span, span<int> span_without_qual) {
91+
A *a1 = (A*)span_ptr.data(); // expected-warning{{unsafe invocation of span::data}}
92+
a1 = (A*)span_ptr.data(); // expected-warning{{unsafe invocation of span::data}}
93+
94+
A *a2 = (A*) span_without_qual.data(); // expected-warning{{unsafe invocation of span::data}}
95+
96+
// TODO:: Should we warn when we cast from base to derived type?
97+
Derived *b = dynamic_cast<Derived*> (base_span.data());// expected-warning{{unsafe invocation of span::data}}
98+
99+
// TODO:: This pattern is safe. We can add special handling for it, if we decide this
100+
// is the recommended fixit for the unsafe invocations.
101+
A *a3 = (A*)span_ptr.subspan(0, sizeof(A)).data(); // expected-warning{{unsafe invocation of span::data}}
102+
}
103+
104+
void not_warned_patterns(std::span<A> span_ptr, std::span<Base> base_span) {
105+
int *p = (int*) span_ptr.data(); // Cast to a smaller type
106+
107+
B *b = (B*) span_ptr.data(); // Cast to a type of same size.
108+
109+
p = (int*) span_ptr.data();
110+
A *a = (A*) span_ptr.hello(); // Invoking other methods.
111+
}
112+
113+
// We do not want to warn about other types
114+
void other_classes(std::span_duplicate<int> span_ptr) {
115+
int *p;
116+
A *a = (A*)span_ptr.data();
117+
a = (A*)span_ptr.data();
118+
}
119+
120+
// Potential source for false negatives
121+
122+
A false_negatives(std::span<int> span_pt, span<A> span_A) {
123+
int *ptr = span_pt.data();
124+
125+
A *a1 = (A*)ptr; //TODO: We want to warn here eventually.
126+
127+
A *a2= span_A.data();
128+
return *a2; // TODO: Can cause OOB if span_pt is empty
129+
130+
}
131+
#endif

0 commit comments

Comments
 (0)