From 2dca48ac300cd1d154b9a07a70ccbacc320d197a Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 10 Nov 2025 19:49:05 +0200 Subject: [PATCH 01/11] use a unicode character type equivalent to `char32_t` to avoid warnings --- lib/include/cpp-json/json_encode.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/include/cpp-json/json_encode.h b/lib/include/cpp-json/json_encode.h index ac67250..79f9941 100644 --- a/lib/include/cpp-json/json_encode.h +++ b/lib/include/cpp-json/json_encode.h @@ -3,6 +3,7 @@ #define JSON_ENCODE_H_ #include "json_value.h" +#include #include #include #include @@ -49,8 +50,8 @@ inline std::string escape_string(std::string_view s, Options options) { reserved : 24; }; - state_t shift_state = {0, 0, 0}; - char32_t result = 0; + state_t shift_state = {0, 0, 0}; + std::uint_least32_t result = 0; for (auto it = s.begin(); it != s.end(); ++it) { From 7191b1f101a26def9f185af657c9fe323e0aa51b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 10 Nov 2025 20:09:45 +0200 Subject: [PATCH 02/11] use unicode character types equivalent to to strong C++ character types C++ unicode character types are defined as being equivalent to underlying types of at least N bits, so it's correct to use those, and improves platform-independency. --- lib/include/cpp-json/json_decode.h | 7 ++++--- lib/include/cpp-json/json_detail.h | 26 +++++++++++++------------- lib/include/cpp-json/json_encode.h | 6 ++---- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/include/cpp-json/json_decode.h b/lib/include/cpp-json/json_decode.h index 33b1746..4d89044 100644 --- a/lib/include/cpp-json/json_decode.h +++ b/lib/include/cpp-json/json_decode.h @@ -14,6 +14,7 @@ #include "json_error.h" #include "json_reader.h" #include "json_value.h" +#include namespace json { @@ -84,7 +85,7 @@ class parser { while (reader_.peek() != '"' && reader_.peek() != '\n') { - char ch = reader_.read(); + const char ch = reader_.read(); if (ch == '\\') { switch (reader_.read()) { case '"': @@ -120,8 +121,8 @@ class parser { if (!std::isxdigit(hex[2] = reader_.read())) JSON_THROW(invalid_unicode_character(reader_.index())); if (!std::isxdigit(hex[3] = reader_.read())) JSON_THROW(invalid_unicode_character(reader_.index())); - uint16_t w1 = 0; - uint16_t w2 = 0; + std::uint_least16_t w1 = 0; + std::uint_least16_t w2 = 0; w1 |= (detail::to_hex(hex[0]) << 12); w1 |= (detail::to_hex(hex[1]) << 8); diff --git a/lib/include/cpp-json/json_detail.h b/lib/include/cpp-json/json_detail.h index d2ec9de..eec1b1b 100644 --- a/lib/include/cpp-json/json_detail.h +++ b/lib/include/cpp-json/json_detail.h @@ -45,12 +45,12 @@ bool is_space(Ch ch) { * @param out */ template -void surrogate_pair_to_utf8(std::uint16_t w1, std::uint16_t w2, Out &out) { +void surrogate_pair_to_utf8(std::uint_least16_t w1, std::uint_least16_t w2, Out &out) { - std::uint32_t cp; + std::uint_least32_t cp; if ((w1 & 0xfc00) == 0xd800) { if ((w2 & 0xfc00) == 0xdc00) { - cp = 0x10000 + (((static_cast(w1) & 0x3ff) << 10) | (w2 & 0x3ff)); + cp = 0x10000 + (((static_cast(w1) & 0x3ff) << 10) | (w2 & 0x3ff)); } else { JSON_THROW(invalid_unicode_character(0)); } @@ -59,19 +59,19 @@ void surrogate_pair_to_utf8(std::uint16_t w1, std::uint16_t w2, Out &out) { } if (cp < 0x80) { - *out++ = static_cast(cp); + *out++ = static_cast(cp); } else if (cp < 0x0800) { - *out++ = static_cast(0xc0 | ((cp >> 6) & 0x1f)); - *out++ = static_cast(0x80 | (cp & 0x3f)); + *out++ = static_cast(0xc0 | ((cp >> 6) & 0x1f)); + *out++ = static_cast(0x80 | (cp & 0x3f)); } else if (cp < 0x10000) { - *out++ = static_cast(0xe0 | ((cp >> 12) & 0x0f)); - *out++ = static_cast(0x80 | ((cp >> 6) & 0x3f)); - *out++ = static_cast(0x80 | (cp & 0x3f)); + *out++ = static_cast(0xe0 | ((cp >> 12) & 0x0f)); + *out++ = static_cast(0x80 | ((cp >> 6) & 0x3f)); + *out++ = static_cast(0x80 | (cp & 0x3f)); } else if (cp < 0x1fffff) { - *out++ = static_cast(0xf0 | ((cp >> 18) & 0x07)); - *out++ = static_cast(0x80 | ((cp >> 12) & 0x3f)); - *out++ = static_cast(0x80 | ((cp >> 6) & 0x3f)); - *out++ = static_cast(0x80 | (cp & 0x3f)); + *out++ = static_cast(0xf0 | ((cp >> 18) & 0x07)); + *out++ = static_cast(0x80 | ((cp >> 12) & 0x3f)); + *out++ = static_cast(0x80 | ((cp >> 6) & 0x3f)); + *out++ = static_cast(0x80 | (cp & 0x3f)); } } diff --git a/lib/include/cpp-json/json_encode.h b/lib/include/cpp-json/json_encode.h index 79f9941..744f85c 100644 --- a/lib/include/cpp-json/json_encode.h +++ b/lib/include/cpp-json/json_encode.h @@ -53,9 +53,7 @@ inline std::string escape_string(std::string_view s, Options options) { state_t shift_state = {0, 0, 0}; std::uint_least32_t result = 0; - for (auto it = s.begin(); it != s.end(); ++it) { - - const auto ch = static_cast(*it); + for (const unsigned char ch : s) { if (shift_state.seen == 0) { @@ -162,7 +160,7 @@ inline std::string escape_string(std::string_view s, Options options) { } } else { - for (char ch : s) { + for (const char ch : s) { switch (ch) { case '\"': From 358495e48bc2b35e88ddaa46d7e09a020e069b40 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 10 Nov 2025 21:12:49 +0200 Subject: [PATCH 03/11] use a constant end iterator --- lib/include/cpp-json/json_encode.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/include/cpp-json/json_encode.h b/lib/include/cpp-json/json_encode.h index 744f85c..7e43ad9 100644 --- a/lib/include/cpp-json/json_encode.h +++ b/lib/include/cpp-json/json_encode.h @@ -215,14 +215,13 @@ inline void value_to_string(std::ostream &os, const object &o, Options options, os << "{\n"; auto it = o.begin(); - auto e = o.end(); ++indent; os << std::string(indent * IndentWidth, ' ') << '"' << escape_string(it->first, options) << "\" : "; value_to_string(os, it->second, options, indent, true); ++it; - for (; it != e; ++it) { + for (auto e = o.end(); it != e; ++it) { os << ','; os << '\n'; os << std::string(indent * IndentWidth, ' ') << '"' << escape_string(it->first, options) << "\" : "; @@ -247,12 +246,11 @@ inline void value_to_string(std::ostream &os, const array &a, Options options, i os << "[\n"; auto it = a.begin(); - auto e = a.end(); ++indent; value_to_string(os, *it++, options, indent, false); - for (; it != e; ++it) { + for (auto e = a.end(); it != e; ++it) { os << ','; os << '\n'; value_to_string(os, *it, options, indent, false); @@ -314,11 +312,10 @@ inline void serialize(std::ostream &os, const array &a, Options options) { os << "["; if (!a.empty()) { auto it = a.begin(); - auto e = a.end(); serialize(os, *it++, options); - for (; it != e; ++it) { + for (const auto e = a.end(); it != e; ++it) { os << ','; serialize(os, *it, options); } @@ -330,12 +327,11 @@ inline void serialize(std::ostream &os, const object &o, Options options) { os << "{"; if (!o.empty()) { auto it = o.begin(); - auto e = o.end(); os << '"' << escape_string(it->first, options) << "\":"; serialize(os, it->second, options); ++it; - for (; it != e; ++it) { + for (const auto e = o.end(); it != e; ++it) { os << ','; os << '"' << escape_string(it->first, options) << "\":"; serialize(os, it->second, options); From 3efe324d89749d2860a792661441fe8679995ebb Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 10 Nov 2025 21:13:00 +0200 Subject: [PATCH 04/11] fix a crash in `json::basic_reader<>::match()` --- lib/include/cpp-json/json_reader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/include/cpp-json/json_reader.h b/lib/include/cpp-json/json_reader.h index a1f5dbc..3e97700 100644 --- a/lib/include/cpp-json/json_reader.h +++ b/lib/include/cpp-json/json_reader.h @@ -176,7 +176,7 @@ class basic_reader { std::match_results matches; const Ch *first = &input_[index_]; - const Ch *last = &input_[input_.size()]; + const Ch *last = &input_.back(); if (std::regex_search(first, last, matches, regex, std::regex_constants::match_continuous)) { std::basic_string m(matches[0].first, matches[0].second); From 6df55fcd5d2ddb536a004832256f3692a0a83c6a Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 10 Nov 2025 21:14:30 +0200 Subject: [PATCH 05/11] consider msvc in CMake --- CMakeLists.txt | 7 +++++++ lib/CMakeLists.txt | 10 ++++++++++ test/CMakeLists.txt | 16 ++++++++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 589949d..86bd133 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,5 +3,12 @@ project(cpp-json CXX) enable_testing() +if (MSVC) + add_compile_options(/EHsc) + if (MSVC_VERSION GREATER_EQUAL 1914) + add_compile_options(/Zc:__cplusplus /Zc:throwingNew /Zc:externConstexpr) + endif() +endif() + add_subdirectory(lib) add_subdirectory(test) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3ff0070..fa4be2f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -10,3 +10,13 @@ target_include_directories(cpp-json target_sources(cpp-json INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/cpp-json/json.h ) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(cpp-json INTERFACE + -Werror=reorder + ) +elseif(MSVC) + target_compile_options(cpp-json INTERFACE + /we5038 + ) +endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a0f516a..46d6d2b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,10 +15,18 @@ set_property(TARGET example2 PROPERTY CXX_STANDARD 17) set_property(TARGET example3 PROPERTY CXX_STANDARD 17) set_property(TARGET example4 PROPERTY CXX_STANDARD 17) -target_compile_options(example1 PUBLIC -pedantic -W -Wall -Wmissing-field-initializers -Wunused -Wshadow) -target_compile_options(example2 PUBLIC -pedantic -W -Wall -Wmissing-field-initializers -Wunused -Wshadow) -target_compile_options(example3 PUBLIC -pedantic -W -Wall -Wmissing-field-initializers -Wunused -Wshadow) -target_compile_options(example4 PUBLIC -pedantic -W -Wall -Wmissing-field-initializers -Wunused -Wshadow) +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(example1 PUBLIC -pedantic -W -Wall -Wmissing-field-initializers -Wunused -Wshadow) + target_compile_options(example2 PUBLIC -pedantic -W -Wall -Wmissing-field-initializers -Wunused -Wshadow) + target_compile_options(example3 PUBLIC -pedantic -W -Wall -Wmissing-field-initializers -Wunused -Wshadow) + target_compile_options(example4 PUBLIC -pedantic -W -Wall -Wmissing-field-initializers -Wunused -Wshadow) +elseif(MSVC) + # multi-processor compilation, warning-level 4 + target_compile_options(example1 PUBLIC /MP /W4) + target_compile_options(example2 PUBLIC /MP /W4) + target_compile_options(example3 PUBLIC /MP /W4) + target_compile_options(example4 PUBLIC /MP /W4) +endif() add_test( NAME example1 From 2f89f0d25deda8b30c09526d3d93ea1a86711d2a Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 10 Nov 2025 22:29:30 +0200 Subject: [PATCH 06/11] updated reader * replaced character-pointer logic with string_view functionality. * corrected non-compiling `reader::match_any()`. * simplified consumption loop. --- lib/include/cpp-json/json_reader.h | 50 +++++++++++------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/lib/include/cpp-json/json_reader.h b/lib/include/cpp-json/json_reader.h index 3e97700..5f7b362 100644 --- a/lib/include/cpp-json/json_reader.h +++ b/lib/include/cpp-json/json_reader.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -79,8 +80,8 @@ class basic_reader { * @return size_t */ size_t consume(std::basic_string_view chars) noexcept { - return consume_while([chars](Ch ch) { - return chars.find(ch) != std::basic_string_view::npos; + return consume_while([&chars](Ch ch) { + return chars.find(ch) != chars.npos; }); } @@ -105,17 +106,11 @@ class basic_reader { */ template size_t consume_while(Pred pred) noexcept { - size_t count = 0; - while (!eof()) { - const Ch ch = peek(); - if (!pred(ch)) { - break; - } - + const size_t start = index_; + while (!eof() && pred(peek())) { ++index_; - ++count; } - return count; + return index_ - start; } /** @@ -160,7 +155,7 @@ class basic_reader { return {}; } - std::basic_string m = input_.substr(index_); + std::basic_string m{ input_.substr(index_) }; index_ += m.size(); return m; } @@ -173,15 +168,13 @@ class basic_reader { * @return bool */ std::optional> match(const std::basic_regex ®ex) { - std::match_results matches; + std::match_results::const_iterator> matches; - const Ch *first = &input_[index_]; - const Ch *last = &input_.back(); + const auto first = std::next(input_.cbegin(), index_); - if (std::regex_search(first, last, matches, regex, std::regex_constants::match_continuous)) { - std::basic_string m(matches[0].first, matches[0].second); - index_ += m.size(); - return m; + if (std::regex_search(first, input_.cend(), matches, regex, std::regex_constants::match_continuous)) { + index_ += matches[0].length(); + return matches[0].str(); } return {}; @@ -196,23 +189,16 @@ class basic_reader { */ template std::optional> match_while(Pred pred) { + using return_type = std::optional>; - size_t start = index_; - while (!eof()) { - const Ch ch = peek(); - if (!pred(ch)) { - break; - } + const size_t count = consume_while(std::move(pred)); - ++index_; + if (count > 0) { + return return_type{ input_.substr(index_ - count, count) }; } - - std::basic_string m(&input_[start], &input_[index_]); - if (!m.empty()) { - return m; + else { + return {}; } - - return {}; } /** From 75805bbf33343c897f1bfcf714f9e879c0fde0c1 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 10 Nov 2025 22:29:52 +0200 Subject: [PATCH 07/11] corrected a warning about uninitialized character variable --- lib/include/cpp-json/json_detail.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/include/cpp-json/json_detail.h b/lib/include/cpp-json/json_detail.h index eec1b1b..4b9d508 100644 --- a/lib/include/cpp-json/json_detail.h +++ b/lib/include/cpp-json/json_detail.h @@ -47,7 +47,7 @@ bool is_space(Ch ch) { template void surrogate_pair_to_utf8(std::uint_least16_t w1, std::uint_least16_t w2, Out &out) { - std::uint_least32_t cp; + std::uint_least32_t cp = '\0'; if ((w1 & 0xfc00) == 0xd800) { if ((w2 & 0xfc00) == 0xdc00) { cp = 0x10000 + (((static_cast(w1) & 0x3ff) << 10) | (w2 & 0x3ff)); From c77d297b9369e94fe955c5f191e57d3c945a8771 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 10 Nov 2025 22:51:39 +0200 Subject: [PATCH 08/11] updated msvc version checks that determine compiler options --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 86bd133..cf5800b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,9 +4,12 @@ project(cpp-json CXX) enable_testing() if (MSVC) - add_compile_options(/EHsc) + add_compile_options(/EHsc /Zc:throwingNew) + if (MSVC_VERSION GREATER_EQUAL 1913) + add_compile_options(/Zc:externConstexpr) + endif() if (MSVC_VERSION GREATER_EQUAL 1914) - add_compile_options(/Zc:__cplusplus /Zc:throwingNew /Zc:externConstexpr) + add_compile_options(/Zc:__cplusplus) endif() endif() From e393653fa6f2ddd0967cdb4221e4ef5482870b84 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 11 Nov 2025 14:15:09 +0200 Subject: [PATCH 09/11] updates after code review --- lib/include/cpp-json/json_reader.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/include/cpp-json/json_reader.h b/lib/include/cpp-json/json_reader.h index 5f7b362..81d0050 100644 --- a/lib/include/cpp-json/json_reader.h +++ b/lib/include/cpp-json/json_reader.h @@ -80,8 +80,8 @@ class basic_reader { * @return size_t */ size_t consume(std::basic_string_view chars) noexcept { - return consume_while([&chars](Ch ch) { - return chars.find(ch) != chars.npos; + return consume_while([chars](Ch ch) { + return chars.find(ch) != std::basic_string_view::npos; }); } @@ -196,9 +196,8 @@ class basic_reader { if (count > 0) { return return_type{ input_.substr(index_ - count, count) }; } - else { - return {}; - } + + return {}; } /** From 82431faa32a3fcbe885eb2199a98855bbadefc5a Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 12 Nov 2025 13:07:16 +0200 Subject: [PATCH 10/11] be explicit about `noexcept` specification In particular, some code analyzers flag functions that are declared with `noexcept` and call other functions that are not declared as such. --- lib/include/cpp-json/json_reader.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/include/cpp-json/json_reader.h b/lib/include/cpp-json/json_reader.h index 81d0050..a5857cd 100644 --- a/lib/include/cpp-json/json_reader.h +++ b/lib/include/cpp-json/json_reader.h @@ -80,7 +80,7 @@ class basic_reader { * @return size_t */ size_t consume(std::basic_string_view chars) noexcept { - return consume_while([chars](Ch ch) { + return consume_while([chars](Ch ch) noexcept { return chars.find(ch) != std::basic_string_view::npos; }); } @@ -92,7 +92,7 @@ class basic_reader { * @return size_t */ size_t consume_whitespace() noexcept { - return consume_while([](Ch ch) { + return consume_while([](Ch ch) noexcept { return (ch == ' ' || ch == '\t'); }); } @@ -105,7 +105,7 @@ class basic_reader { * @return size_t */ template - size_t consume_while(Pred pred) noexcept { + size_t consume_while(Pred pred) noexcept(noexcept(pred(Ch{'\0'}))) { const size_t start = index_; while (!eof() && pred(peek())) { ++index_; From ace5998c5131b172cbed657967a67f6c09369636 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 12 Nov 2025 13:23:50 +0200 Subject: [PATCH 11/11] replaced `sizeof` with `std::size()` when passing character buffer size --- lib/include/cpp-json/json_encode.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/include/cpp-json/json_encode.h b/lib/include/cpp-json/json_encode.h index 7e43ad9..66b5f74 100644 --- a/lib/include/cpp-json/json_encode.h +++ b/lib/include/cpp-json/json_encode.h @@ -4,6 +4,7 @@ #include "json_value.h" #include +#include #include #include #include @@ -87,7 +88,7 @@ inline std::string escape_string(std::string_view s, Options options) { if (!isprint(ch)) { r += "\\u"; char buf[5]; - snprintf(buf, sizeof(buf), "%04X", ch); + snprintf(buf, std::size(buf), "%04X", ch); r += buf; } else { r += static_cast(ch); @@ -132,17 +133,17 @@ inline std::string escape_string(std::string_view s, Options options) { if (result < 0xd800 || (result >= 0xe000 && result < 0x10000)) { r += "\\u"; - snprintf(buf, sizeof(buf), "%04X", result); + snprintf(buf, std::size(buf), "%04X", result); r += buf; } else { result = (result - 0x10000); r += "\\u"; - snprintf(buf, sizeof(buf), "%04X", 0xd800 + ((result >> 10) & 0x3ff)); + snprintf(buf, std::size(buf), "%04X", 0xd800 + ((result >> 10) & 0x3ff)); r += buf; r += "\\u"; - snprintf(buf, sizeof(buf), "%04X", 0xdc00 + (result & 0x3ff)); + snprintf(buf, std::size(buf), "%04X", 0xdc00 + (result & 0x3ff)); r += buf; }