From b89d589040b000f0cd5901c8f97fb307eedffc61 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Sat, 22 Aug 2026 10:08:39 +0200 Subject: [PATCH] harden a bit --- common/json.cpp | 56 ++++++++++++++++++++++++++++-------- common/json.h | 17 +++++++++-- common/peg-parser.cpp | 7 +++-- tools/server/server-common.h | 2 +- 4 files changed, 64 insertions(+), 18 deletions(-) diff --git a/common/json.cpp b/common/json.cpp index 046294892f..62abc38ba1 100644 --- a/common/json.cpp +++ b/common/json.cpp @@ -54,6 +54,7 @@ static ordered_json to_json(const common_json_value & val) { case common_json_value::VAL_STRING: return val.val_string; case common_json_value::VAL_JSON: // one owner means no one else can see this tree, so it is safe to move it out + // note: this makes a value single use, same as the json_ref of the backing library if (val.val_json.use_count() == 1) { return std::move(as_json(val.val_json.get())); } @@ -213,14 +214,19 @@ common_json::~common_json() { common_json common_json::parse(const std::string & text) { try { - return common_json_from_raw(ordered_json::parse(text)); + // the assignment moves the parsed tree in, it does not copy + common_json out; + as_json(&out) = ordered_json::parse(text); + return out; } catch (const std::exception & e) { throw common_json_error(e.what()); } } common_json common_json::parse_no_throw(const std::string & text) { - return common_json_from_raw(ordered_json::parse(text, nullptr, false)); + common_json out; + as_json(&out) = ordered_json::parse(text, nullptr, false); + return out; } bool common_json::is_discarded() const { @@ -228,21 +234,27 @@ bool common_json::is_discarded() const { } common_json common_json::array() { - return common_json_from_raw(ordered_json::array()); + common_json out; + as_json(&out) = ordered_json::array(); + return out; } common_json common_json::array(std::initializer_list vals) { - ordered_json out = ordered_json::array(); + common_json out; + ordered_json & arr = as_json(&out); + arr = ordered_json::array(); for (const auto & val : vals) { - out.push_back(to_json(val)); + arr.push_back(to_json(val)); } - return common_json_from_raw(out); + return out; } common_json common_json::object() { - return common_json_from_raw(ordered_json::object()); + common_json out; + as_json(&out) = ordered_json::object(); + return out; } common_json common_json::object(std::initializer_list items) { @@ -270,6 +282,10 @@ bool common_json::contains(const std::string & key) const { } bool common_json::operator==(const common_json_value & val) const { + // compare a tree in place, to_json() would copy it + if (val.type == common_json_value::VAL_JSON) { + return as_json(this) == as_json(val.val_json.get()); + } return as_json(this) == to_json(val); } @@ -345,11 +361,17 @@ std::string common_json::dump_safe(int indent) const { // an array is indexed directly, an object needs a walk from the start common_json & common_json::iterator::operator*() const { return guard([&]() -> common_json & { - if (as_json(node).is_object()) { - return as_common(std::next(as_json(node).begin(), idx).value()); + ordered_json & j = as_json(node); + + if (j.is_object()) { + return as_common(std::next(j.begin(), idx).value()); + } + if (j.is_array()) { + return as_common(j[idx]); } - return as_common(as_json(node)[idx]); + // a plain value gives itself once, same as the backing library + return *node; }); } @@ -365,11 +387,21 @@ common_json::iterator common_json::end() const { return iterator(const_cast(this), size()); } +// the keys follow the backing library: the index for an array, "" for a plain value common_json::items_view::entry common_json::items_view::iterator::operator*() const { return guard([&]() -> entry { - auto it = std::next(as_json(node).begin(), idx); + ordered_json & j = as_json(node); - return { it.key(), as_common(it.value()) }; + if (j.is_object()) { + auto it = std::next(j.begin(), idx); + + return { it.key(), as_common(it.value()) }; + } + if (j.is_array()) { + return { std::to_string(idx), as_common(j[idx]) }; + } + + return { std::string(), *node }; }); } diff --git a/common/json.h b/common/json.h index 6f2d0841a1..aa2913dfbc 100644 --- a/common/json.h +++ b/common/json.h @@ -30,6 +30,8 @@ struct common_json_error : std::runtime_error { }; // one value, tagged so that this header stays free of the backing library +// note: a value that holds a tree is single use, consuming the same value twice +// (e.g. through a named initializer list) gives null on the second use struct common_json_value { enum value_type { VAL_NULL, @@ -188,8 +190,8 @@ class common_json { const common_json & operator[](const std::string & key) const; common_json & operator[](const char * key) { return (*this)[std::string(key)]; } const common_json & operator[](const char * key) const { return (*this)[std::string(key)]; } - common_json & operator[](int idx) { return (*this)[(size_t) idx]; } - const common_json & operator[](int idx) const { return (*this)[(size_t) idx]; } + common_json & operator[](int idx) { return (*this)[to_idx(idx)]; } + const common_json & operator[](int idx) const { return (*this)[to_idx(idx)]; } common_json & operator[](size_t idx); const common_json & operator[](size_t idx) const; @@ -236,7 +238,7 @@ class common_json { // 1 if the key is there, 0 if not size_t count(const std::string & key) const; - // appends every value of another array + // appends every value of another array; inserting an array into itself throws void insert(const common_json & vals); // a common_json goes through the copy assignment above, everything else becomes a value @@ -252,6 +254,7 @@ class common_json { std::string dump_safe(int indent = -1) const; // walks an array by index, or an object in insertion order + // a plain value gives itself once, same as the backing library class iterator { public: using iterator_category = std::forward_iterator_tag; @@ -325,6 +328,14 @@ class common_json { items_view items() const; private: + // a negative index must not turn into a huge size_t + static size_t to_idx(int idx) { + if (idx < 0) { + throw common_json_error("negative array index"); + } + return (size_t) idx; + } + // the backing value is built here, json.cpp checks that it fits // it cannot be a pointer: a value inside a tree would then not be a common_json, // so at() could only give back a copy instead of a real reference diff --git a/common/peg-parser.cpp b/common/peg-parser.cpp index 96cc267b9d..8194ddb1d8 100644 --- a/common/peg-parser.cpp +++ b/common/peg-parser.cpp @@ -1895,11 +1895,14 @@ common_json common_peg_arena::to_json() const { for (const auto & parser : parsers_) { parsers.push_back(serialize_parser_variant(parser)); } - return common_json_from_raw(nlohmann::ordered_json{ + // the assignment moves the tree in, it does not copy + common_json out; + common_json_raw(out) = nlohmann::ordered_json{ {"parsers", parsers}, {"rules", rules_}, {"root", root_} - }); + }; + return out; } static common_peg_parser_variant deserialize_parser_variant(const nlohmann::ordered_json & j) { diff --git a/tools/server/server-common.h b/tools/server/server-common.h index 6478d0218c..f8ea82ef4c 100644 --- a/tools/server/server-common.h +++ b/tools/server/server-common.h @@ -42,7 +42,7 @@ static T json_value(const json & body, const std::string & key, const T & defaul if (body.contains(key) && !body.at(key).is_null()) { try { return body.at(key).get(); - } catch (const std::exception & err) { + } catch (const common_json_error & err) { LOG_WRN("Wrong type supplied for parameter '%s', using default value: %s\n", key.c_str(), err.what()); return default_value; }