mirror of
https://github.com/nlohmann/json.git
synced 2026-10-05 14:10:31 +00:00
Reject integral keys for contains(), find(), and count() at compile time
j.contains(0), j.find(0), and j.count(0) used to compile: the literal 0 is a null pointer constant, so it converts to a null const char*, and the overloads taking const typename object_t::key_type& accepted it by constructing a std::string from that null pointer, which is undefined behavior (a crash with both libc++ and libstdc++). value(0, default_value) had the same problem in C++11, where the object comparator is not transparent. Add deleted overloads for integral arguments to contains(), find() (const and non-const), count(), and value() so that these calls are compile errors in every supported language mode instead of crashing. Calls with string, string_view, json_pointer, and size-typed element access (at(), operator[](), erase()) are unaffected. Fixes #5657. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -58,6 +58,10 @@ Logarithmic in the size of the JSON object.
|
|||||||
|
|
||||||
- This method always returns `#!cpp false` when executed on a JSON type that is not an object.
|
- This method always returns `#!cpp false` when executed on a JSON type that is not an object.
|
||||||
- This method can be executed on any JSON value type.
|
- This method can be executed on any JSON value type.
|
||||||
|
- Calling this function with an integer argument (for example, `#!cpp contains(0)`) does not compile: such an argument
|
||||||
|
would otherwise implicitly convert to a null `#!cpp const char*` and, from there, cause undefined behavior when
|
||||||
|
constructing a `#!cpp std::string` for the object key. To check for an array element instead, use [`at`](at.md),
|
||||||
|
[`operator[]`](operator%5B%5D.md), or compare against [`size`](size.md).
|
||||||
|
|
||||||
!!! info "Postconditions"
|
!!! info "Postconditions"
|
||||||
|
|
||||||
@@ -117,3 +121,5 @@ Logarithmic in the size of the JSON object.
|
|||||||
1. Added in version 3.11.0.
|
1. Added in version 3.11.0.
|
||||||
2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0.
|
2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0.
|
||||||
3. Added in version 3.7.0.
|
3. Added in version 3.7.0.
|
||||||
|
4. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of
|
||||||
|
causing undefined behavior at runtime.
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ Logarithmic in the size of the JSON object.
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
This method always returns `0` when executed on a JSON type that is not an object.
|
- This method always returns `0` when executed on a JSON type that is not an object.
|
||||||
|
- Calling this function with an integer argument (for example, `#!cpp count(0)`) does not compile: such an argument
|
||||||
|
would otherwise implicitly convert to a null `#!cpp const char*` and, from there, cause undefined behavior when
|
||||||
|
constructing a `#!cpp std::string` for the object key. To check for an array element instead, use [`at`](at.md),
|
||||||
|
[`operator[]`](operator%5B%5D.md), or compare against [`size`](size.md).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -81,3 +85,5 @@ This method always returns `0` when executed on a JSON type that is not an objec
|
|||||||
|
|
||||||
1. Added in version 3.11.0.
|
1. Added in version 3.11.0.
|
||||||
2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0.
|
2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0.
|
||||||
|
3. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time instead of
|
||||||
|
causing undefined behavior at runtime.
|
||||||
|
|||||||
@@ -44,7 +44,11 @@ Logarithmic in the size of the JSON object.
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
This method always returns `end()` when executed on a JSON type that is not an object.
|
- This method always returns `end()` when executed on a JSON type that is not an object.
|
||||||
|
- Calling this function with an integer argument (for example, `#!cpp find(0)`) does not compile: such an argument
|
||||||
|
would otherwise implicitly convert to a null `#!cpp const char*` and, from there, cause undefined behavior when
|
||||||
|
constructing a `#!cpp std::string` for the object key. To access an array element instead, use [`at`](at.md),
|
||||||
|
[`operator[]`](operator%5B%5D.md), or compare against [`size`](size.md).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -85,3 +89,5 @@ This method always returns `end()` when executed on a JSON type that is not an o
|
|||||||
|
|
||||||
1. Added in version 3.11.0.
|
1. Added in version 3.11.0.
|
||||||
2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0.
|
2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0.
|
||||||
|
3. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of
|
||||||
|
causing undefined behavior at runtime.
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ This is equivalent to Python's `dict.get(key, default)`.
|
|||||||
- Unlike [`operator[]`](operator[].md), this function does not implicitly add an element to the position defined by
|
- Unlike [`operator[]`](operator[].md), this function does not implicitly add an element to the position defined by
|
||||||
`key`/`ptr` key. This function is furthermore also applicable to const objects.
|
`key`/`ptr` key. This function is furthermore also applicable to const objects.
|
||||||
|
|
||||||
|
!!! note "Integer keys"
|
||||||
|
|
||||||
|
Calling this function with an integer `key` argument (for example, `#!cpp value(0, 1)`) does not compile in
|
||||||
|
C++11, where `object_comparator_t` is not transparent: such an argument would otherwise implicitly convert to a
|
||||||
|
null `#!cpp const char*` and, from there, cause undefined behavior when constructing a `#!cpp std::string` for the
|
||||||
|
object key. To access an array element with a default value, use [`at`](at.md) together with a `#!cpp try`/`#!cpp
|
||||||
|
catch` block, or compare against [`size`](size.md) instead.
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
`KeyType`
|
`KeyType`
|
||||||
@@ -184,7 +192,9 @@ changes to any JSON value.
|
|||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
1. Added in version 1.0.0. Changed parameter `default_value` type from `const ValueType&` to `ValueType&&` in version 3.11.0.
|
1. Added in version 1.0.0. Changed parameter `default_value` type from `const ValueType&` to `ValueType&&` in version
|
||||||
|
3.11.0. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time
|
||||||
|
instead of causing undefined behavior at runtime.
|
||||||
2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2.
|
2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2.
|
||||||
3. Added in version 2.0.2. Extended to work with arrays in version 3.13.0, including fixing an issue where resolving
|
3. Added in version 2.0.2. Extended to work with arrays in version 3.13.0, including fixing an issue where resolving
|
||||||
`ptr` through an array unexpectedly threw `out_of_range` instead of returning the resolved element (or
|
`ptr` through an array unexpectedly threw `out_of_range` instead of returning the resolved element (or
|
||||||
|
|||||||
@@ -2975,6 +2975,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
string_t, typename std::decay<ValueType>::type >;
|
string_t, typename std::decay<ValueType>::type >;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// an integer literal 0 would otherwise convert to a null const char* and from there to key_type
|
||||||
|
template<typename T, typename ValueType, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
ValueType value(T, ValueType&&) const = delete;
|
||||||
|
|
||||||
/// @brief access specified object element with default value
|
/// @brief access specified object element with default value
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/value/
|
/// @sa https://json.nlohmann.me/api/basic_json/value/
|
||||||
template < class ValueType, detail::enable_if_t <
|
template < class ValueType, detail::enable_if_t <
|
||||||
@@ -3409,6 +3413,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @name lookup
|
/// @name lookup
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
|
// an integer literal 0 would otherwise convert to a null const char* and from there to key_type
|
||||||
|
template<typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
iterator find(T) = delete;
|
||||||
|
template<typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
const_iterator find(T) const = delete;
|
||||||
|
template<typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
size_type count(T) const = delete;
|
||||||
|
template<typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
bool contains(T) const = delete;
|
||||||
|
|
||||||
/// @brief find an element in a JSON object
|
/// @brief find an element in a JSON object
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/find/
|
/// @sa https://json.nlohmann.me/api/basic_json/find/
|
||||||
iterator find(const typename object_t::key_type& key)
|
iterator find(const typename object_t::key_type& key)
|
||||||
|
|||||||
@@ -29056,6 +29056,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
string_t, typename std::decay<ValueType>::type >;
|
string_t, typename std::decay<ValueType>::type >;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// an integer literal 0 would otherwise convert to a null const char* and from there to key_type
|
||||||
|
template<typename T, typename ValueType, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
ValueType value(T, ValueType&&) const = delete;
|
||||||
|
|
||||||
/// @brief access specified object element with default value
|
/// @brief access specified object element with default value
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/value/
|
/// @sa https://json.nlohmann.me/api/basic_json/value/
|
||||||
template < class ValueType, detail::enable_if_t <
|
template < class ValueType, detail::enable_if_t <
|
||||||
@@ -29490,6 +29494,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @name lookup
|
/// @name lookup
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
|
// an integer literal 0 would otherwise convert to a null const char* and from there to key_type
|
||||||
|
template<typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
iterator find(T) = delete;
|
||||||
|
template<typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
const_iterator find(T) const = delete;
|
||||||
|
template<typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
size_type count(T) const = delete;
|
||||||
|
template<typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
|
bool contains(T) const = delete;
|
||||||
|
|
||||||
/// @brief find an element in a JSON object
|
/// @brief find an element in a JSON object
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/find/
|
/// @sa https://json.nlohmann.me/api/basic_json/find/
|
||||||
iterator find(const typename object_t::key_type& key)
|
iterator find(const typename object_t::key_type& key)
|
||||||
|
|||||||
@@ -16,6 +16,40 @@
|
|||||||
// build test with C++14
|
// build test with C++14
|
||||||
// JSON_HAS_CPP_14
|
// JSON_HAS_CPP_14
|
||||||
|
|
||||||
|
// used to check at compile time (via is_detected) whether a call is well-formed; see
|
||||||
|
// https://github.com/nlohmann/json/issues/5657
|
||||||
|
//
|
||||||
|
// note: an integer *literal* (rather than a std::declval<T>() of integral type T) is required to
|
||||||
|
// reproduce the bug, because only a null pointer constant--an integer literal with value zero, not
|
||||||
|
// merely a runtime value that happens to be zero--implicitly converts to a null const char*; that is
|
||||||
|
// why can_call_*_with_0 below hard-code the literal 0 instead of taking it as a template argument
|
||||||
|
template<typename BasicJsonType, typename T>
|
||||||
|
using can_call_find = decltype(std::declval<BasicJsonType>().find(std::declval<T>()));
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename T>
|
||||||
|
using can_call_count = decltype(std::declval<BasicJsonType>().count(std::declval<T>()));
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename T>
|
||||||
|
using can_call_contains = decltype(std::declval<BasicJsonType>().contains(std::declval<T>()));
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename KeyType, typename ValueType>
|
||||||
|
using can_call_value = decltype(std::declval<BasicJsonType>().value(std::declval<KeyType>(), std::declval<ValueType>()));
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
using can_call_find_with_0 = decltype(std::declval<BasicJsonType>().find(0));
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
using can_call_count_with_0 = decltype(std::declval<BasicJsonType>().count(0));
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
using can_call_contains_with_0 = decltype(std::declval<BasicJsonType>().contains(0));
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
using can_call_contains_with_0L = decltype(std::declval<BasicJsonType>().contains(0L));
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
using can_call_value_with_0 = decltype(std::declval<BasicJsonType>().value(0, 1));
|
||||||
|
|
||||||
TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_json) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_json) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||||
{
|
{
|
||||||
SECTION("object")
|
SECTION("object")
|
||||||
@@ -1493,6 +1527,53 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("integral keys for object lookup are rejected at compile time")
|
||||||
|
{
|
||||||
|
// https://github.com/nlohmann/json/issues/5657: an integer literal like 0 is a null pointer
|
||||||
|
// constant, which used to convert to a null const char* and from there--via undefined
|
||||||
|
// behavior in the std::string constructor--to key_type, so contains(0), find(0), and
|
||||||
|
// count(0) used to compile and then crash instead of failing to compile
|
||||||
|
using nlohmann::detail::is_detected;
|
||||||
|
|
||||||
|
CHECK_FALSE(is_detected<can_call_find_with_0, Json&>::value);
|
||||||
|
CHECK_FALSE(is_detected<can_call_find_with_0, const Json&>::value);
|
||||||
|
CHECK_FALSE(is_detected<can_call_count_with_0, Json&>::value);
|
||||||
|
CHECK_FALSE(is_detected<can_call_contains_with_0, Json&>::value);
|
||||||
|
// value(0, ...) is only affected in C++11, where the comparator is not transparent; with a
|
||||||
|
// transparent comparator (C++14 and later), int is already rejected for lacking a
|
||||||
|
// comparison with the key type, independently of this fix
|
||||||
|
CHECK_FALSE(is_detected<can_call_value_with_0, const Json&>::value);
|
||||||
|
|
||||||
|
// another integral literal type must be rejected as well, not just int
|
||||||
|
CHECK_FALSE(is_detected<can_call_contains_with_0L, Json&>::value);
|
||||||
|
|
||||||
|
// the valid overloads must remain callable
|
||||||
|
CHECK(is_detected<can_call_find, Json&, const char*>::value);
|
||||||
|
CHECK(is_detected<can_call_find, Json&, std::string>::value);
|
||||||
|
CHECK(is_detected<can_call_count, Json&, const char*>::value);
|
||||||
|
CHECK(is_detected<can_call_contains, Json&, const char*>::value);
|
||||||
|
CHECK(is_detected<can_call_contains, Json&, typename Json::json_pointer>::value);
|
||||||
|
CHECK(is_detected<can_call_value, const Json&, const char*, int>::value);
|
||||||
|
CHECK(is_detected<can_call_value, const Json&, typename Json::json_pointer, int>::value);
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
CHECK(is_detected<can_call_find, Json&, std::string_view>::value);
|
||||||
|
CHECK(is_detected<can_call_count, Json&, std::string_view>::value);
|
||||||
|
CHECK(is_detected<can_call_contains, Json&, std::string_view>::value);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the neighboring size_type overloads for array access are unaffected by the new
|
||||||
|
// integral-key overloads above (at(), operator[](), and erase() take a size_type)
|
||||||
|
Json arr = {10, 20, 30};
|
||||||
|
const Json arr_const = arr;
|
||||||
|
CHECK(arr.at(0) == 10);
|
||||||
|
CHECK(arr_const.at(0) == 10);
|
||||||
|
CHECK(arr[0] == 10);
|
||||||
|
CHECK(arr_const[0] == 10);
|
||||||
|
arr.erase(0);
|
||||||
|
CHECK(arr.size() == 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
|||||||
Reference in New Issue
Block a user