Skip to content

Commit 3140333

Browse files
authored
[-Wunsafe-buffer-usage] Support span creation from std::initializer_list and begin/end pairs (#145311)
Support safe construction of `std::span` from `begin` and `end` calls on hardened containers or views or `std::initializer_list`s. For example, the following code is safe: ``` void create(std::initializer_list<int> il) { std::span<int> input{ il.begin(), il.end() }; // no warn } ``` rdar://152637380
1 parent e6d6bb5 commit 3140333

File tree

2 files changed

+95
-5
lines changed

2 files changed

+95
-5
lines changed

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ class MatchResult {
115115
};
116116
} // namespace
117117

118+
#define SIZED_CONTAINER_OR_VIEW_LIST \
119+
"span", "array", "vector", "basic_string_view", "basic_string", \
120+
"initializer_list",
121+
118122
// A `RecursiveASTVisitor` that traverses all descendants of a given node "n"
119123
// except for those belonging to a different callable of "n".
120124
class MatchDescendantVisitor : public DynamicRecursiveASTVisitor {
@@ -463,6 +467,8 @@ static bool areEqualIntegers(const Expr *E1, const Expr *E2, ASTContext &Ctx) {
463467
// `N, M` are parameter indexes to the allocating element number and size.
464468
// Sometimes, there is only one parameter index representing the total
465469
// size.
470+
// 7. `std::span<T>{x.begin(), x.end()}` where `x` is an object in the
471+
// SIZED_CONTAINER_OR_VIEW_LIST.
466472
static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,
467473
ASTContext &Ctx) {
468474
assert(Node.getNumArgs() == 2 &&
@@ -560,6 +566,32 @@ static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,
560566
}
561567
}
562568
}
569+
// Check form 7:
570+
auto IsMethodCallToSizedObject = [](const Stmt *Node, StringRef MethodName) {
571+
if (const auto *MC = dyn_cast<CXXMemberCallExpr>(Node)) {
572+
const auto *MD = MC->getMethodDecl();
573+
const auto *RD = MC->getRecordDecl();
574+
575+
if (RD && MD)
576+
if (auto *II = RD->getDeclName().getAsIdentifierInfo();
577+
II && RD->isInStdNamespace())
578+
return llvm::is_contained({SIZED_CONTAINER_OR_VIEW_LIST},
579+
II->getName()) &&
580+
MD->getName() == MethodName;
581+
}
582+
return false;
583+
};
584+
585+
if (IsMethodCallToSizedObject(Arg0, "begin") &&
586+
IsMethodCallToSizedObject(Arg1, "end"))
587+
return AreSameDRE(
588+
// We know Arg0 and Arg1 are `CXXMemberCallExpr`s:
589+
cast<CXXMemberCallExpr>(Arg0)
590+
->getImplicitObjectArgument()
591+
->IgnoreParenImpCasts(),
592+
cast<CXXMemberCallExpr>(Arg1)
593+
->getImplicitObjectArgument()
594+
->IgnoreParenImpCasts());
563595
return false;
564596
}
565597

@@ -1058,8 +1090,7 @@ static bool hasUnsafeSnprintfBuffer(const CallExpr &Node,
10581090
return false; // not an snprintf call
10591091

10601092
// Pattern 1:
1061-
static StringRef SizedObjs[] = {"span", "array", "vector",
1062-
"basic_string_view", "basic_string"};
1093+
static StringRef SizedObjs[] = {SIZED_CONTAINER_OR_VIEW_LIST};
10631094
Buf = Buf->IgnoreParenImpCasts();
10641095
Size = Size->IgnoreParenImpCasts();
10651096
if (auto *MCEPtr = dyn_cast<CXXMemberCallExpr>(Buf))
@@ -1826,9 +1857,8 @@ class DataInvocationGadget : public WarningGadget {
18261857
auto *method = cast<CXXMethodDecl>(callee);
18271858
if (method->getNameAsString() == "data" &&
18281859
method->getParent()->isInStdNamespace() &&
1829-
(method->getParent()->getName() == "span" ||
1830-
method->getParent()->getName() == "array" ||
1831-
method->getParent()->getName() == "vector"))
1860+
llvm::is_contained({SIZED_CONTAINER_OR_VIEW_LIST},
1861+
method->getParent()->getName()))
18321862
return true;
18331863
return false;
18341864
}

clang/test/SemaCXX/warn-unsafe-buffer-usage-in-container-span-construct.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-container -verify %s
22

3+
typedef unsigned int size_t;
4+
35
namespace std {
46
template <class T> class span {
57
public:
@@ -16,6 +18,9 @@ namespace std {
1618

1719
template<class R>
1820
constexpr span(R && range){};
21+
22+
T* begin() noexcept;
23+
T* end() noexcept;
1924
};
2025

2126

@@ -27,6 +32,37 @@ namespace std {
2732
return &__x;
2833
}
2934

35+
template <typename T, size_t N>
36+
struct array {
37+
T* begin() noexcept;
38+
const T* begin() const noexcept;
39+
T* end() noexcept;
40+
const T* end() const noexcept;
41+
size_t size() const noexcept;
42+
T * data() const noexcept;
43+
T& operator[](size_t n);
44+
};
45+
46+
template<class T>
47+
class initializer_list {
48+
public:
49+
size_t size() const noexcept;
50+
const T* begin() const noexcept;
51+
const T* end() const noexcept;
52+
T * data() const noexcept;
53+
};
54+
55+
template<typename T>
56+
struct basic_string {
57+
T *c_str() const noexcept;
58+
T *data() const noexcept;
59+
unsigned size();
60+
const T* begin() const noexcept;
61+
const T* end() const noexcept;
62+
};
63+
64+
typedef basic_string<char> string;
65+
typedef basic_string<wchar_t> wstring;
3066
}
3167

3268
namespace irrelevant_constructors {
@@ -232,3 +268,27 @@ struct HoldsStdSpanAndNotInitializedInCtor {
232268
: Ptr(P), Size(S)
233269
{}
234270
};
271+
272+
namespace test_begin_end {
273+
struct Object {
274+
int * begin();
275+
int * end();
276+
};
277+
void safe_cases(std::span<int> Sp, std::array<int, 10> Arr, std::string Str, std::initializer_list<Object> Il) {
278+
std::span<int>{Sp.begin(), Sp.end()};
279+
std::span<int>{Arr.begin(), Arr.end()};
280+
std::span<char>{Str.begin(), Str.end()};
281+
std::span<Object>{Il.begin(), Il.end()};
282+
}
283+
284+
void unsafe_cases(std::span<int> Sp, std::array<int, 10> Arr, std::string Str, std::initializer_list<Object> Il,
285+
Object Obj) {
286+
std::span<int>{Obj.begin(), Obj.end()}; // expected-warning {{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
287+
std::span<int>{Sp.end(), Sp.begin()}; // expected-warning {{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
288+
std::span<int>{Sp.begin(), Arr.end()}; // expected-warning {{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
289+
}
290+
291+
void unsupport_cases(std::array<Object, 10> Arr) {
292+
std::span<int>{Arr[0].begin(), Arr[0].end()}; // expected-warning {{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
293+
}
294+
}

0 commit comments

Comments
 (0)