Skip to content
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

Allow preventing implicit converions to basic_json #4324

Closed
wants to merge 2 commits into from
Closed
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
7 changes: 7 additions & 0 deletions .github/external_ci/appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ environment:
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF"
GENERATOR: Visual Studio 16 2019

- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
configuration: Release
platform: x86
CXX_FLAGS: "/W4 /WX"
CMAKE_OPTIONS: "-DJSON_ImplicitConstructors=OFF"
GENERATOR: Visual Studio 16 2019

- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
configuration: Release
platform: x64
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ jobs:
container: ubuntu:focal
strategy:
matrix:
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls]
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_noimplicitconstructors, ci_test_legacycomparison, ci_test_noglobaludls]
steps:
- name: Install build-essential
run: apt-get update ; apt-get install -y build-essential unzip wget git
Expand Down
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ option(JSON_CI "Enable CI build targets." OFF)
option(JSON_Diagnostics "Use extended diagnostic messages." OFF)
option(JSON_GlobalUDLs "Place use-defined string literals in the global namespace." ON)
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
option(JSON_ImplicitConstructors "Enable implicit constructors." ON)
option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF)
option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF)
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
Expand Down Expand Up @@ -80,6 +81,10 @@ if (NOT JSON_ImplicitConversions)
message(STATUS "Implicit conversions are disabled")
endif()

if (NOT JSON_ImplicitConstructors)
message(STATUS "Implicit constructors are disabled")
endif()

if (JSON_DisableEnumSerialization)
message(STATUS "Enum integer serialization is disabled")
endif()
Expand Down Expand Up @@ -113,6 +118,7 @@ target_compile_definitions(
INTERFACE
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
$<$<NOT:$<BOOL:${JSON_ImplicitConstructors}>>:JSON_USE_IMPLICIT_CONSTRUCTORS=0>
$<$<BOOL:${JSON_DisableEnumSerialization}>:JSON_DISABLE_ENUM_SERIALIZATION=1>
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
Expand Down
16 changes: 15 additions & 1 deletion cmake/ci.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,20 @@ add_custom_target(ci_test_noimplicitconversions
COMMENT "Compile and test with implicit conversions switched off"
)

###############################################################################
# Disable implicit converstructors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constructors

###############################################################################

add_custom_target(ci_test_noimplicitconstructors
COMMAND ${CMAKE_COMMAND}
-DCMAKE_BUILD_TYPE=Debug -GNinja
-DJSON_BuildTests=ON -DJSON_ImplicitConstructors=OFF
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_noimplicitconstructors
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_noimplicitconstructors
COMMAND cd ${PROJECT_BINARY_DIR}/build_noimplicitconstructors && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
COMMENT "Compile and test with implicit constructors switched off"
)

###############################################################################
# Enable improved diagnostics.
###############################################################################
Expand Down Expand Up @@ -855,7 +869,7 @@ endfunction()
ci_get_cmake(3.1.0 CMAKE_3_1_0_BINARY)
ci_get_cmake(3.13.0 CMAKE_3_13_0_BINARY)

set(JSON_CMAKE_FLAGS_3_1_0 JSON_Diagnostics JSON_GlobalUDLs JSON_ImplicitConversions JSON_DisableEnumSerialization
set(JSON_CMAKE_FLAGS_3_1_0 JSON_Diagnostics JSON_GlobalUDLs JSON_ImplicitConversions JSON_ImplicitConstructors JSON_DisableEnumSerialization
JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind)
set(JSON_CMAKE_FLAGS_3_13_0 JSON_BuildTests)

Expand Down
25 changes: 24 additions & 1 deletion docs/mkdocs/docs/api/basic_json/basic_json.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ basic_json(std::nullptr_t = nullptr) noexcept;

// (3)
template<typename CompatibleType>
basic_json(CompatibleType&& val) noexcept(noexcept(
JSON_CONSTRUCT_EXPLICIT basic_json(CompatibleType&& val) noexcept(noexcept(
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
std::forward<CompatibleType>(val))));

Expand Down Expand Up @@ -58,6 +58,7 @@ basic_json(basic_json&& other) noexcept;
3. This is a "catch all" constructor for all compatible JSON types; that is, types for which a `to_json()` method
exists. The constructor forwards the parameter `val` to that method (to `json_serializer<U>::to_json` method with
`U = uncvref_t<CompatibleType>`, to be exact).
See [Notes](#notes) for the meaning of `JSON_CONSTRUCT_EXPLICIT`.

Template type `CompatibleType` includes, but is not limited to, the following types:

Expand Down Expand Up @@ -239,6 +240,28 @@ basic_json(basic_json&& other) noexcept;

## Notes

- Overload 3:

!!! note "Definition of `JSON_CONSTRUCT_EXPLICIT`"

By default, `JSON_CONSTRUCT_EXPLICIT` is defined to the empty string, so the signature is:

```cpp
template<typename CompatibleType>
basic_json(CompatibleType&& val)
```

If [`JSON_USE_IMPLICIT_CONSTRUCTORS`](../macros/json_use_implicit_constructors.md) is set to `0`,
`JSON_CONSTRUCT_EXPLICIT` is defined to `#!cpp explicit`:

```cpp
template<typename CompatibleType>
explicit basic_json(CompatibleType&& val)
```

That is, implicit constructors can be switched off by defining
[`JSON_USE_IMPLICIT_CONSTRUCTORS`](../macros/json_use_implicit_constructors.md) to `0`.

- Overload 5:

!!! note "Empty initializer list"
Expand Down
95 changes: 95 additions & 0 deletions docs/mkdocs/docs/api/macros/json_use_implicit_constructors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# JSON_USE_IMPLICIT_CONSTRUCTORS

```cpp
#define JSON_USE_IMPLICIT_CONSTRUCTORS /* value */
```

When defined to `0`, implicit constructors are switched off. By default, implicit constructors are switched on. The
value directly affects [the constructor of `basic_json`](../basic_json/basic_json.md).

## Default definition

By default, implicit constructors are enabled.

```cpp
#define JSON_USE_IMPLICIT_CONSTRUCTORS 1
```

## Notes

!!! hint "CMake option"

Implicit constructors can also be controlled with the CMake option
[`JSON_ImplicitConstructors`](../../integration/cmake.md#json_legacydiscardedvaluecomparison)
(`ON` by default) which defines `JSON_USE_IMPLICIT_CONSTRUCTORS` accordingly.

## Examples

??? example

This is an example for an implicit construction:

```cpp
json j = "Hello, world!";
```

When `JSON_USE_IMPLICIT_CONSTRUCTORS` is defined to `0`, the code above no longer compiles. Instead, it must be
written like this:

```cpp
json j = json("Hello, world!");
```

??? example

Disabling constructors is particularly useful to avoid implicit allocations that can lead to invalid memory access:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling implicit constructors


```cpp
const json & valueAt(const json & map, const std::string & key) {
return map[key];
}
const json::object_t & getObjectRef(const json & obj) {
return obj.get_ref<const json::object_t &>();
}
const json::string_t & getStringRef(const json & obj) {
return obj.get_ref<const json::string_t &>();
}

int main() {
json j2 = {
{"string", "foo"},
{"object", {
{"currency", "USD"},
{"value", 42.99}
}}
};

json::object_t & objectRef = getObjectRef(valueAt(j2, "object"));
json::string_t & nestedObjectRef = getStringRef(valueAt(objectRef, "currency"));
std::cout << nestedObjectRef << '\n';
}
```

This program causes undefined behavior when trying to access `nestedObjectRef`, because the second call to `valueAt`
implicitly converts the `json::object_t` to a `json`, which is immediately discarded afterwards, causing
`nestedObjectRef` to become a dangling reference.

When `JSON_USE_IMPLICIT_CONSTRUCTORS` is defined to `0`, the code above no longer compiles. Instead, you must
define an override for `valueAt` that takes `json::object_t`.

```cpp
const nlohmann::json & valueAt(const json::object_t & map, const std::string & key) {
return map.at(key);
}
```

This will prevent the implicit construction of an intermediate object, and the code will work as expected.


## See also

- [**basic_json constructor**](../basic_json/basic_json.md) - get a value (implicit)

## Version history

- Added in version 3.11.4.
4 changes: 4 additions & 0 deletions docs/mkdocs/docs/integration/cmake.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ Place user-defined string literals in the global namespace by defining the macro

Enable implicit conversions by defining macro [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md). This option is `ON` by default.

### `JSON_ImplicitConstructors`

Enable implicit constructors by defining macro [`JSON_USE_IMPLICIT_CONSTRUCTORS`](../api/macros/json_use_implicit_constructors.md). This option is `ON` by default.

### `JSON_Install`

Install CMake targets during install step. This option is `ON` by default if the library's CMake project is the top project.
Expand Down
10 changes: 10 additions & 0 deletions include/nlohmann/detail/macro_scope.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,16 @@
#define JSON_EXPLICIT explicit
#endif

#ifndef JSON_USE_IMPLICIT_CONSTRUCTORS
#define JSON_USE_IMPLICIT_CONSTRUCTORS 1
#endif

#if JSON_USE_IMPLICIT_CONSTRUCTORS
#define JSON_CONSTRUCT_EXPLICIT
#else
#define JSON_CONSTRUCT_EXPLICIT explicit
#endif

#ifndef JSON_DISABLE_ENUM_SERIALIZATION
#define JSON_DISABLE_ENUM_SERIALIZATION 0
#endif
Expand Down
4 changes: 2 additions & 2 deletions include/nlohmann/detail/output/binary_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ class binary_writer
// step 2: write each element
for (const auto& el : *j.m_data.m_value.object)
{
write_cbor(el.first);
write_cbor(json(el.first));
write_cbor(el.second);
}
break;
Expand Down Expand Up @@ -717,7 +717,7 @@ class binary_writer
// step 2: write each element
for (const auto& el : *j.m_data.m_value.object)
{
write_msgpack(el.first);
write_msgpack(json(el.first));
write_msgpack(el.second);
}
break;
Expand Down
13 changes: 12 additions & 1 deletion include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
typename U = detail::uncvref_t<CompatibleType>,
detail::enable_if_t <
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
JSON_CONSTRUCT_EXPLICIT basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
std::forward<CompatibleType>(val))))
{
Expand Down Expand Up @@ -1249,6 +1249,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return *this;
}

/// @brief copy assignment
/// @sa https://json.nlohmann.me/api/basic_json/operator=/
template < typename CompatibleType,
typename U = detail::uncvref_t<CompatibleType>,
detail::enable_if_t <
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
basic_json& operator=(CompatibleType && val) {
return this->operator=(basic_json(std::forward<CompatibleType>(val)));
}


/// @brief destructor
/// @sa https://json.nlohmann.me/api/basic_json/~basic_json/
~basic_json() noexcept
Expand Down
2 changes: 1 addition & 1 deletion single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20135,7 +20135,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
typename U = detail::uncvref_t<CompatibleType>,
detail::enable_if_t <
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
JSON_CONSTRUCT_EXPLICIT basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
std::forward<CompatibleType>(val))))
{
Expand Down
8 changes: 8 additions & 0 deletions tests/src/test_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
#include <fstream> // ifstream, istreambuf_iterator, ios
#include <vector> // vector

#if JSON_USE_IMPLICIT_CONSTRUCTORS == 0
#define IMPLICIT_CAST (nlohmann::json)
#define IMPLICIT_REF_CAST(x) std::move(nlohmann::json(x))
#else
#define IMPLICIT_CAST
#define IMPLICIT_REF_CAST
#endif

namespace utils
{

Expand Down
5 changes: 4 additions & 1 deletion tests/src/unit-algorithms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// SPDX-License-Identifier: MIT

#include "doctest_compatibility.h"
#include "test_utils.hpp"

#include <algorithm>
#include <nlohmann/json.hpp>
Expand Down Expand Up @@ -78,7 +79,7 @@ TEST_CASE("algorithms")
{
if (value.is_array())
{
value.push_back(17);
value.push_back(IMPLICIT_CAST 17);
}
};

Expand Down Expand Up @@ -296,6 +297,7 @@ TEST_CASE("algorithms")
CHECK(j_array == json({false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
}

#if JSON_USE_IMPLICIT_CONSTRUCTORS == 1
SECTION("iota")
{
SECTION("int")
Expand All @@ -318,6 +320,7 @@ TEST_CASE("algorithms")
CHECK(json_arr == json({'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}));
}
}
#endif

SECTION("copy")
{
Expand Down
8 changes: 8 additions & 0 deletions tests/src/unit-allocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// SPDX-License-Identifier: MIT

#include "doctest_compatibility.h"
#include "test_utils.hpp"

#define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp>
Expand Down Expand Up @@ -245,6 +246,7 @@ struct allocator_no_forward : std::allocator<T>

TEST_CASE("bad my_allocator::construct")
{

SECTION("my_allocator::construct doesn't forward")
{
using bad_alloc_json = nlohmann::basic_json<std::map,
Expand All @@ -257,7 +259,13 @@ TEST_CASE("bad my_allocator::construct")
allocator_no_forward>;

bad_alloc_json j;

#if JSON_USE_IMPLICIT_CONSTRUCTORS == 1
j["test"] = bad_alloc_json::array_t();
j["test"].push_back("should not leak");
#else
j["test"] = bad_alloc_json(bad_alloc_json::array_t());
j["test"].push_back(bad_alloc_json("should not leak"));
#endif
}
}
Loading