Skip to content

Commit f896bd3

Browse files
authored
[libc++] Diagnose when nullptrs are passed to string APIs (#122790)
This allows catching misuses of APIs that take a pointer to a null-terminated string.
1 parent 28851ed commit f896bd3

File tree

5 files changed

+72
-17
lines changed

5 files changed

+72
-17
lines changed

libcxx/include/__config

+6
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,12 @@ typedef __char32_t char32_t;
12011201
# define _LIBCPP_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK
12021202
# endif
12031203

1204+
# if __has_feature(nullability)
1205+
# define _LIBCPP_DIAGNOSE_NULLPTR _Nonnull
1206+
# else
1207+
# define _LIBCPP_DIAGNOSE_NULLPTR
1208+
# endif
1209+
12041210
// TODO(LLVM 22): Remove this macro once LLVM19 support ends. __cpp_explicit_this_parameter has been set in LLVM20.
12051211
// Clang-18 has support for deducing this, but it does not set the FTM.
12061212
# if defined(__cpp_explicit_this_parameter) || (defined(_LIBCPP_CLANG_VER) && _LIBCPP_CLANG_VER >= 1800)

libcxx/include/string

+21-17
Original file line numberDiff line numberDiff line change
@@ -1037,13 +1037,14 @@ public:
10371037
# endif // _LIBCPP_CXX03_LANG
10381038

10391039
template <__enable_if_t<__is_allocator<_Allocator>::value, int> = 0>
1040-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(const _CharT* __s) {
1040+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(const _CharT* _LIBCPP_DIAGNOSE_NULLPTR __s) {
10411041
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "basic_string(const char*) detected nullptr");
10421042
__init(__s, traits_type::length(__s));
10431043
}
10441044

10451045
template <__enable_if_t<__is_allocator<_Allocator>::value, int> = 0>
1046-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(const _CharT* __s, const _Allocator& __a)
1046+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
1047+
basic_string(const _CharT* _LIBCPP_DIAGNOSE_NULLPTR __s, const _Allocator& __a)
10471048
: __alloc_(__a) {
10481049
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "basic_string(const char*, allocator) detected nullptr");
10491050
__init(__s, traits_type::length(__s));
@@ -1214,7 +1215,8 @@ public:
12141215
return assign(__il.begin(), __il.size());
12151216
}
12161217
# endif
1217-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(const value_type* __s) {
1218+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
1219+
operator=(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) {
12181220
return assign(__s);
12191221
}
12201222
# if _LIBCPP_STD_VER >= 23
@@ -1340,7 +1342,8 @@ public:
13401342
return append(__sv);
13411343
}
13421344

1343-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator+=(const value_type* __s) {
1345+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
1346+
operator+=(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) {
13441347
return append(__s);
13451348
}
13461349

@@ -1381,7 +1384,7 @@ public:
13811384
append(const _Tp& __t, size_type __pos, size_type __n = npos);
13821385

13831386
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& append(const value_type* __s, size_type __n);
1384-
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& append(const value_type* __s);
1387+
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& append(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s);
13851388
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& append(size_type __n, value_type __c);
13861389

13871390
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __append_default_init(size_type __n);
@@ -1539,7 +1542,7 @@ public:
15391542
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
15401543
insert(size_type __pos1, const basic_string& __str, size_type __pos2, size_type __n = npos);
15411544
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, const value_type* __s, size_type __n);
1542-
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, const value_type* __s);
1545+
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s);
15431546
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, size_type __n, value_type __c);
15441547
_LIBCPP_CONSTEXPR_SINCE_CXX20 iterator insert(const_iterator __pos, value_type __c);
15451548

@@ -1719,7 +1722,7 @@ public:
17191722
}
17201723

17211724
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
1722-
find(const value_type* __s, size_type __pos = 0) const _NOEXCEPT {
1725+
find(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = 0) const _NOEXCEPT {
17231726
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find(): received nullptr");
17241727
return std::__str_find<value_type, size_type, traits_type, npos>(
17251728
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1750,7 +1753,7 @@ public:
17501753
}
17511754

17521755
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
1753-
rfind(const value_type* __s, size_type __pos = npos) const _NOEXCEPT {
1756+
rfind(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = npos) const _NOEXCEPT {
17541757
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::rfind(): received nullptr");
17551758
return std::__str_rfind<value_type, size_type, traits_type, npos>(
17561759
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1783,7 +1786,7 @@ public:
17831786
}
17841787

17851788
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
1786-
find_first_of(const value_type* __s, size_type __pos = 0) const _NOEXCEPT {
1789+
find_first_of(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = 0) const _NOEXCEPT {
17871790
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find_first_of(): received nullptr");
17881791
return std::__str_find_first_of<value_type, size_type, traits_type, npos>(
17891792
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1817,7 +1820,7 @@ public:
18171820
}
18181821

18191822
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
1820-
find_last_of(const value_type* __s, size_type __pos = npos) const _NOEXCEPT {
1823+
find_last_of(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = npos) const _NOEXCEPT {
18211824
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find_last_of(): received nullptr");
18221825
return std::__str_find_last_of<value_type, size_type, traits_type, npos>(
18231826
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1851,7 +1854,7 @@ public:
18511854
}
18521855

18531856
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
1854-
find_first_not_of(const value_type* __s, size_type __pos = 0) const _NOEXCEPT {
1857+
find_first_not_of(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = 0) const _NOEXCEPT {
18551858
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find_first_not_of(): received nullptr");
18561859
return std::__str_find_first_not_of<value_type, size_type, traits_type, npos>(
18571860
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1885,7 +1888,7 @@ public:
18851888
}
18861889

18871890
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
1888-
find_last_not_of(const value_type* __s, size_type __pos = npos) const _NOEXCEPT {
1891+
find_last_not_of(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = npos) const _NOEXCEPT {
18891892
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find_last_not_of(): received nullptr");
18901893
return std::__str_find_last_not_of<value_type, size_type, traits_type, npos>(
18911894
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1933,12 +1936,13 @@ public:
19331936
return __self_view(*this).substr(__pos1, __n1).compare(__sv.substr(__pos2, __n2));
19341937
}
19351938

1936-
_LIBCPP_CONSTEXPR_SINCE_CXX20 int compare(const value_type* __s) const _NOEXCEPT {
1939+
_LIBCPP_CONSTEXPR_SINCE_CXX20 int compare(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const _NOEXCEPT {
19371940
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::compare(): received nullptr");
19381941
return compare(0, npos, __s, traits_type::length(__s));
19391942
}
19401943

1941-
_LIBCPP_CONSTEXPR_SINCE_CXX20 int compare(size_type __pos1, size_type __n1, const value_type* __s) const {
1944+
_LIBCPP_CONSTEXPR_SINCE_CXX20 int
1945+
compare(size_type __pos1, size_type __n1, const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const {
19421946
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::compare(): received nullptr");
19431947
return compare(__pos1, __n1, __s, traits_type::length(__s));
19441948
}
@@ -1957,7 +1961,7 @@ public:
19571961
return !empty() && _Traits::eq(front(), __c);
19581962
}
19591963

1960-
constexpr _LIBCPP_HIDE_FROM_ABI bool starts_with(const value_type* __s) const noexcept {
1964+
constexpr _LIBCPP_HIDE_FROM_ABI bool starts_with(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const noexcept {
19611965
return starts_with(__self_view(__s));
19621966
}
19631967

@@ -1971,7 +1975,7 @@ public:
19711975
return !empty() && _Traits::eq(back(), __c);
19721976
}
19731977

1974-
constexpr _LIBCPP_HIDE_FROM_ABI bool ends_with(const value_type* __s) const noexcept {
1978+
constexpr _LIBCPP_HIDE_FROM_ABI bool ends_with(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const noexcept {
19751979
return ends_with(__self_view(__s));
19761980
}
19771981
# endif
@@ -1987,7 +1991,7 @@ public:
19871991
return __self_view(typename __self_view::__assume_valid(), data(), size()).contains(__c);
19881992
}
19891993

1990-
constexpr _LIBCPP_HIDE_FROM_ABI bool contains(const value_type* __s) const {
1994+
constexpr _LIBCPP_HIDE_FROM_ABI bool contains(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const {
19911995
return __self_view(typename __self_view::__assume_valid(), data(), size()).contains(__s);
19921996
}
19931997
# endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03
10+
11+
// Ensure that APIs which take a CharT* (and no size for it) are diagnosing passing a nullptr to them
12+
13+
#include <string>
14+
15+
#include "test_macros.h"
16+
17+
void func() {
18+
const char* const np = nullptr;
19+
std::string str1(np); // expected-warning {{null passed}}
20+
std::string str2(np, std::allocator<char>{}); // expected-warning {{null passed}}
21+
str2 = np; // expected-warning {{null passed}}
22+
str2 += np; // expected-warning {{null passed}}
23+
str2.append(np); // expected-warning {{null passed}}
24+
str2.insert(0, np); // expected-warning {{null passed}}
25+
str2.find(np); // expected-warning {{null passed}}
26+
str2.rfind(np); // expected-warning {{null passed}}
27+
str2.find_first_of(np); // expected-warning {{null passed}}
28+
str2.find_last_of(np); // expected-warning {{null passed}}
29+
str2.find_first_not_of(np); // expected-warning {{null passed}}
30+
str2.find_last_not_of(np); // expected-warning {{null passed}}
31+
str2.compare(np); // expected-warning {{null passed}}
32+
str2.compare(0, 0, np); // expected-warning {{null passed}}
33+
34+
#if TEST_STD_VER >= 20
35+
str2.starts_with(np); // expected-warning {{null passed}}
36+
str2.ends_with(np); // expected-warning {{null passed}}
37+
#endif
38+
#if TEST_STD_VER >= 23
39+
str2.contains(np); // expected-warning {{null passed}}
40+
#endif
41+
}

libcxx/utils/libcxx/test/params.py

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272

7373
# This doesn't make sense in real code, but we have to test it because the standard requires us to not break
7474
"-Wno-self-move",
75+
76+
# We're not annotating all the APIs, since that's a lot of annotations compared to how many we actually care about
77+
"-Wno-nullability-completeness",
7578
]
7679

7780
_allStandards = ["c++03", "c++11", "c++14", "c++17", "c++20", "c++23", "c++26"]

runtimes/cmake/Modules/WarningFlags.cmake

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ function(cxx_add_warning_flags target enable_werror enable_pedantic)
2525
-Wformat-nonliteral
2626
-Wzero-length-array
2727
-Wdeprecated-redundant-constexpr-static-def
28+
-Wno-nullability-completeness
2829
)
2930

3031
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")

0 commit comments

Comments
 (0)