From eff5301990d5a12f8eb93ad6c51188afd2352985 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Sun, 6 Nov 2022 13:49:15 +0300 Subject: [PATCH] option to check duplicate object keys --- include/boost/json/detail/handler.hpp | 1 + include/boost/json/detail/impl/handler.ipp | 7 ++++- include/boost/json/error.hpp | 3 ++ include/boost/json/impl/error.ipp | 2 ++ include/boost/json/impl/parser.ipp | 11 +++---- include/boost/json/impl/stream_parser.ipp | 11 +++---- include/boost/json/impl/value_stack.ipp | 36 ++++++++++++++++++++++ include/boost/json/parse_options.hpp | 14 +++++++++ include/boost/json/value_stack.hpp | 5 +++ test/error.cpp | 1 + test/parse.cpp | 15 +++++++++ 11 files changed, 93 insertions(+), 13 deletions(-) diff --git a/include/boost/json/detail/handler.hpp b/include/boost/json/detail/handler.hpp index 6880d723d..2cf2f58d4 100644 --- a/include/boost/json/detail/handler.hpp +++ b/include/boost/json/detail/handler.hpp @@ -35,6 +35,7 @@ struct handler max_string_size = string::max_size(); value_stack st; + bool ignore_duplicate_keys = true; template explicit diff --git a/include/boost/json/detail/impl/handler.ipp b/include/boost/json/detail/impl/handler.ipp index 20b40efa3..dba0841bb 100644 --- a/include/boost/json/detail/impl/handler.ipp +++ b/include/boost/json/detail/impl/handler.ipp @@ -51,8 +51,13 @@ bool handler:: on_object_end( std::size_t n, - error_code&) + error_code& ec) { + if( !ignore_duplicate_keys ) + ec = st.check_duplicates(n); + if( ec.failed() ) + return false; + st.push_object(n); return true; } diff --git a/include/boost/json/error.hpp b/include/boost/json/error.hpp index 6b19ee039..863ca8e5c 100644 --- a/include/boost/json/error.hpp +++ b/include/boost/json/error.hpp @@ -66,6 +66,9 @@ enum class error /// error occured when trying to read input input_error, + /// duplicate object key + duplicate_key, + // // generic errors // diff --git a/include/boost/json/impl/error.ipp b/include/boost/json/impl/error.ipp index 09d57fcfb..ec508ac96 100644 --- a/include/boost/json/impl/error.ipp +++ b/include/boost/json/impl/error.ipp @@ -45,6 +45,7 @@ case error::array_too_large: return "array too large"; case error::key_too_large: return "key too large"; case error::string_too_large: return "string too large"; case error::input_error: return "input error"; +case error::duplicate_key: return "duplicate key"; case error::exception: return "got exception"; case error::test_failure: return "test failure"; @@ -93,6 +94,7 @@ case error::array_too_large: case error::key_too_large: case error::string_too_large: case error::input_error: +case error::duplicate_key: return condition::parse_error; case error::missing_slash: diff --git a/include/boost/json/impl/parser.ipp b/include/boost/json/impl/parser.ipp index 6c17bd42c..399b5f8cd 100644 --- a/include/boost/json/impl/parser.ipp +++ b/include/boost/json/impl/parser.ipp @@ -32,20 +32,19 @@ parser( size) { reset(); + p_.handler().ignore_duplicate_keys = opt.ignore_duplicate_keys; } parser:: parser( storage_ptr sp, parse_options const& opt) noexcept - : p_( - opt, + : parser( std::move(sp), - nullptr, + opt, + static_cast(nullptr), 0) -{ - reset(); -} +{ } void parser:: diff --git a/include/boost/json/impl/stream_parser.ipp b/include/boost/json/impl/stream_parser.ipp index b5b798ac2..37fe6c0ea 100644 --- a/include/boost/json/impl/stream_parser.ipp +++ b/include/boost/json/impl/stream_parser.ipp @@ -32,20 +32,19 @@ stream_parser( size) { reset(); + p_.handler().ignore_duplicate_keys = opt.ignore_duplicate_keys; } stream_parser:: stream_parser( storage_ptr sp, parse_options const& opt) noexcept - : p_( - opt, + : stream_parser( std::move(sp), - nullptr, + opt, + static_cast(nullptr), 0) -{ - reset(); -} +{ } void stream_parser:: diff --git a/include/boost/json/impl/value_stack.ipp b/include/boost/json/impl/value_stack.ipp index 1e23b0042..551594d5d 100644 --- a/include/boost/json/impl/value_stack.ipp +++ b/include/boost/json/impl/value_stack.ipp @@ -82,6 +82,35 @@ has_chars() return chars_ != 0; } +error_code +value_stack:: +stack:: +check_duplicates(std::size_t n) +{ + error_code ec; + + for( value* first = top_ - 2 * n; first != top_; first += 2 ) + { + value* other = first + 2; + while( true ) + { + if( first->as_string() == other->as_string() ) + { + BOOST_JSON_FAIL( ec, error::duplicate_key ); + goto before_return; + } + + if( other == top_ ) + break; + + other += 2; + } + } + +before_return: + return ec; +} + //-------------------------------------- // destroy the values but @@ -467,6 +496,13 @@ push_null() st_.push(nullptr, sp_); } +error_code +value_stack:: +check_duplicates(std::size_t n) +{ + return st_.check_duplicates(n); +} + BOOST_JSON_NS_END #endif diff --git a/include/boost/json/parse_options.hpp b/include/boost/json/parse_options.hpp index 2b3fee182..206a29eb5 100644 --- a/include/boost/json/parse_options.hpp +++ b/include/boost/json/parse_options.hpp @@ -76,6 +76,20 @@ struct parse_options @ref stream_parser. */ bool allow_invalid_utf8 = false; + + + /** Unique keys restriction setting + + Forbid duplicate keys to appear in objects. + + @note Since @ref basic_parser doesn't store parsed elements directly, + this option has to be taken account by implementers of handlers. + + @see + @ref basic_parser, + @ref stream_parser. + */ + bool ignore_duplicate_keys = true; }; BOOST_JSON_NS_END diff --git a/include/boost/json/value_stack.hpp b/include/boost/json/value_stack.hpp index 885f8d2b3..bd3b447e5 100644 --- a/include/boost/json/value_stack.hpp +++ b/include/boost/json/value_stack.hpp @@ -140,6 +140,7 @@ class value_stack inline void run_dtors(bool b) noexcept; inline std::size_t size() const noexcept; inline bool has_chars(); + inline error_code check_duplicates(std::size_t n); inline void clear() noexcept; inline void maybe_grow(); @@ -501,6 +502,10 @@ class value_stack BOOST_JSON_DECL void push_null(); + + BOOST_JSON_DECL + error_code + check_duplicates(std::size_t n); }; BOOST_JSON_NS_END diff --git a/test/error.cpp b/test/error.cpp index ad57dc3d0..d2f37b24b 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -61,6 +61,7 @@ class error_test check(condition::parse_error, error::key_too_large); check(condition::parse_error, error::string_too_large); check(condition::parse_error, error::input_error); + check(condition::parse_error, error::duplicate_key); check(condition::pointer_parse_error, error::missing_slash); check(condition::pointer_parse_error, error::invalid_escape); diff --git a/test/parse.cpp b/test/parse.cpp index 41405f49b..1a7ef19b2 100644 --- a/test/parse.cpp +++ b/test/parse.cpp @@ -203,6 +203,20 @@ class parse_test BOOST_TEST_THROWS( parse(ss), system_error ); } + void + testDuplicates() + { + value jv = parse( R"( {"a": 1, "a": 2} )" ); + BOOST_TEST( jv.as_object().size() == 1 ); + + parse_options opt; + + error_code ec; + opt.ignore_duplicate_keys = false; + jv = parse( R"( {"a": 1, "a": 2} )", ec, {}, opt ); + BOOST_TEST( ec == error::duplicate_key ); + } + void run() { @@ -210,6 +224,7 @@ class parse_test testMemoryUsage(); testIssue726(); testIstream(); + testDuplicates(); } };