Skip to content

[libc++] Diagnose when nullptrs are passed to string APIs #122790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions libcxx/include/__config
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 21 additions & 17 deletions libcxx/include/string
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
}
Expand All @@ -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));
}

Expand All @@ -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
Expand All @@ -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
Expand Down
41 changes: 41 additions & 0 deletions libcxx/test/libcxx/strings/basic.string/nonnull.verify.cpp
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions libcxx/utils/libcxx/test/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@

# 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"]
Expand Down
1 change: 1 addition & 0 deletions runtimes/cmake/Modules/WarningFlags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading