harden a bit

This commit is contained in:
Xuan Son Nguyen
2026-08-22 10:08:39 +02:00
parent cdcd63bc03
commit b89d589040
4 changed files with 64 additions and 18 deletions
+44 -12
View File
@@ -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<common_json_value> 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<common_json_item> 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<common_json *>(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 };
});
}
+14 -3
View File
@@ -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
+5 -2
View File
@@ -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<nlohmann::ordered_json>(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) {
+1 -1
View File
@@ -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<T>();
} 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;
}