[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.
This commit is contained in:
Nikolas Klauser
2025-02-27 22:57:19 +01:00
committed by GitHub
parent 28851edf16
commit f896bd3670
5 changed files with 72 additions and 17 deletions

View File

@@ -1201,6 +1201,12 @@ typedef __char32_t char32_t;
# define _LIBCPP_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK
# endif
# if __has_feature(nullability)
# define _LIBCPP_DIAGNOSE_NULLPTR _Nonnull
# else
# define _LIBCPP_DIAGNOSE_NULLPTR
# endif
// TODO(LLVM 22): Remove this macro once LLVM19 support ends. __cpp_explicit_this_parameter has been set in LLVM20.
// Clang-18 has support for deducing this, but it does not set the FTM.
# if defined(__cpp_explicit_this_parameter) || (defined(_LIBCPP_CLANG_VER) && _LIBCPP_CLANG_VER >= 1800)

View File

@@ -1037,13 +1037,14 @@ public:
# endif // _LIBCPP_CXX03_LANG
template <__enable_if_t<__is_allocator<_Allocator>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(const _CharT* __s) {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(const _CharT* _LIBCPP_DIAGNOSE_NULLPTR __s) {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "basic_string(const char*) detected nullptr");
__init(__s, traits_type::length(__s));
}
template <__enable_if_t<__is_allocator<_Allocator>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(const _CharT* __s, const _Allocator& __a)
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
basic_string(const _CharT* _LIBCPP_DIAGNOSE_NULLPTR __s, const _Allocator& __a)
: __alloc_(__a) {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "basic_string(const char*, allocator) detected nullptr");
__init(__s, traits_type::length(__s));
@@ -1214,7 +1215,8 @@ public:
return assign(__il.begin(), __il.size());
}
# endif
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(const value_type* __s) {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
operator=(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) {
return assign(__s);
}
# if _LIBCPP_STD_VER >= 23
@@ -1340,7 +1342,8 @@ public:
return append(__sv);
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator+=(const value_type* __s) {
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
operator+=(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) {
return append(__s);
}
@@ -1381,7 +1384,7 @@ public:
append(const _Tp& __t, size_type __pos, size_type __n = npos);
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& append(const value_type* __s, size_type __n);
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& append(const value_type* __s);
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& append(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s);
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& append(size_type __n, value_type __c);
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __append_default_init(size_type __n);
@@ -1539,7 +1542,7 @@ public:
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
insert(size_type __pos1, const basic_string& __str, size_type __pos2, size_type __n = npos);
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, const value_type* __s, size_type __n);
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, const value_type* __s);
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s);
_LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, size_type __n, value_type __c);
_LIBCPP_CONSTEXPR_SINCE_CXX20 iterator insert(const_iterator __pos, value_type __c);
@@ -1719,7 +1722,7 @@ public:
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
find(const value_type* __s, size_type __pos = 0) const _NOEXCEPT {
find(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = 0) const _NOEXCEPT {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find(): received nullptr");
return std::__str_find<value_type, size_type, traits_type, npos>(
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1750,7 +1753,7 @@ public:
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
rfind(const value_type* __s, size_type __pos = npos) const _NOEXCEPT {
rfind(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = npos) const _NOEXCEPT {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::rfind(): received nullptr");
return std::__str_rfind<value_type, size_type, traits_type, npos>(
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1783,7 +1786,7 @@ public:
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
find_first_of(const value_type* __s, size_type __pos = 0) const _NOEXCEPT {
find_first_of(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = 0) const _NOEXCEPT {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find_first_of(): received nullptr");
return std::__str_find_first_of<value_type, size_type, traits_type, npos>(
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1817,7 +1820,7 @@ public:
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
find_last_of(const value_type* __s, size_type __pos = npos) const _NOEXCEPT {
find_last_of(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = npos) const _NOEXCEPT {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find_last_of(): received nullptr");
return std::__str_find_last_of<value_type, size_type, traits_type, npos>(
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1851,7 +1854,7 @@ public:
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
find_first_not_of(const value_type* __s, size_type __pos = 0) const _NOEXCEPT {
find_first_not_of(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = 0) const _NOEXCEPT {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find_first_not_of(): received nullptr");
return std::__str_find_first_not_of<value_type, size_type, traits_type, npos>(
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1885,7 +1888,7 @@ public:
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
find_last_not_of(const value_type* __s, size_type __pos = npos) const _NOEXCEPT {
find_last_not_of(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s, size_type __pos = npos) const _NOEXCEPT {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::find_last_not_of(): received nullptr");
return std::__str_find_last_not_of<value_type, size_type, traits_type, npos>(
data(), size(), __s, __pos, traits_type::length(__s));
@@ -1933,12 +1936,13 @@ public:
return __self_view(*this).substr(__pos1, __n1).compare(__sv.substr(__pos2, __n2));
}
_LIBCPP_CONSTEXPR_SINCE_CXX20 int compare(const value_type* __s) const _NOEXCEPT {
_LIBCPP_CONSTEXPR_SINCE_CXX20 int compare(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const _NOEXCEPT {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::compare(): received nullptr");
return compare(0, npos, __s, traits_type::length(__s));
}
_LIBCPP_CONSTEXPR_SINCE_CXX20 int compare(size_type __pos1, size_type __n1, const value_type* __s) const {
_LIBCPP_CONSTEXPR_SINCE_CXX20 int
compare(size_type __pos1, size_type __n1, const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const {
_LIBCPP_ASSERT_NON_NULL(__s != nullptr, "string::compare(): received nullptr");
return compare(__pos1, __n1, __s, traits_type::length(__s));
}
@@ -1957,7 +1961,7 @@ public:
return !empty() && _Traits::eq(front(), __c);
}
constexpr _LIBCPP_HIDE_FROM_ABI bool starts_with(const value_type* __s) const noexcept {
constexpr _LIBCPP_HIDE_FROM_ABI bool starts_with(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const noexcept {
return starts_with(__self_view(__s));
}
@@ -1971,7 +1975,7 @@ public:
return !empty() && _Traits::eq(back(), __c);
}
constexpr _LIBCPP_HIDE_FROM_ABI bool ends_with(const value_type* __s) const noexcept {
constexpr _LIBCPP_HIDE_FROM_ABI bool ends_with(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const noexcept {
return ends_with(__self_view(__s));
}
# endif
@@ -1987,7 +1991,7 @@ public:
return __self_view(typename __self_view::__assume_valid(), data(), size()).contains(__c);
}
constexpr _LIBCPP_HIDE_FROM_ABI bool contains(const value_type* __s) const {
constexpr _LIBCPP_HIDE_FROM_ABI bool contains(const value_type* _LIBCPP_DIAGNOSE_NULLPTR __s) const {
return __self_view(typename __self_view::__assume_valid(), data(), size()).contains(__s);
}
# endif

View File

@@ -0,0 +1,41 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03
// Ensure that APIs which take a CharT* (and no size for it) are diagnosing passing a nullptr to them
#include <string>
#include "test_macros.h"
void func() {
const char* const np = nullptr;
std::string str1(np); // expected-warning {{null passed}}
std::string str2(np, std::allocator<char>{}); // expected-warning {{null passed}}
str2 = np; // expected-warning {{null passed}}
str2 += np; // expected-warning {{null passed}}
str2.append(np); // expected-warning {{null passed}}
str2.insert(0, np); // expected-warning {{null passed}}
str2.find(np); // expected-warning {{null passed}}
str2.rfind(np); // expected-warning {{null passed}}
str2.find_first_of(np); // expected-warning {{null passed}}
str2.find_last_of(np); // expected-warning {{null passed}}
str2.find_first_not_of(np); // expected-warning {{null passed}}
str2.find_last_not_of(np); // expected-warning {{null passed}}
str2.compare(np); // expected-warning {{null passed}}
str2.compare(0, 0, np); // expected-warning {{null passed}}
#if TEST_STD_VER >= 20
str2.starts_with(np); // expected-warning {{null passed}}
str2.ends_with(np); // expected-warning {{null passed}}
#endif
#if TEST_STD_VER >= 23
str2.contains(np); // expected-warning {{null passed}}
#endif
}

View File

@@ -72,6 +72,9 @@ _warningFlags = [
# This doesn't make sense in real code, but we have to test it because the standard requires us to not break
"-Wno-self-move",
# We're not annotating all the APIs, since that's a lot of annotations compared to how many we actually care about
"-Wno-nullability-completeness",
]
_allStandards = ["c++03", "c++11", "c++14", "c++17", "c++20", "c++23", "c++26"]

View File

@@ -25,6 +25,7 @@ function(cxx_add_warning_flags target enable_werror enable_pedantic)
-Wformat-nonliteral
-Wzero-length-array
-Wdeprecated-redundant-constexpr-static-def
-Wno-nullability-completeness
)
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")