From b6a2b5d3c184003f259abbb58536f3466b1ee97f Mon Sep 17 00:00:00 2001 From: m-holger Date: Sun, 14 Jan 2024 16:38:29 +0000 Subject: [PATCH] Handle default-constructed JSON objects --- libqpdf/JSON.cc | 95 ++++++++++++++++++++++++------------------------ libtests/json.cc | 47 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 47 deletions(-) diff --git a/libqpdf/JSON.cc b/libqpdf/JSON.cc index 35b8fd76..c9816809 100644 --- a/libqpdf/JSON.cc +++ b/libqpdf/JSON.cc @@ -191,7 +191,7 @@ JSON::JSON_blob::write(Pipeline* p, size_t) const void JSON::write(Pipeline* p, size_t depth) const { - if (nullptr == m->value) { + if (!m) { *p << "null"; } else { m->value->write(p, depth); @@ -201,6 +201,9 @@ JSON::write(Pipeline* p, size_t depth) const std::string JSON::unparse() const { + if (!m) { + return "null"; + } std::string s; Pl_String p("unparse", nullptr, s); write(&p, 0); @@ -275,8 +278,8 @@ JSON::makeDictionary() JSON JSON::addDictionaryMember(std::string const& key, JSON const& val) { - if (auto* obj = dynamic_cast(m->value.get())) { - return obj->members[encode_string(key)] = val.m->value ? val : makeNull(); + if (auto* obj = m ? dynamic_cast(m->value.get()) : nullptr) { + return obj->members[encode_string(key)] = val.m ? val : makeNull(); } else { throw std::runtime_error("JSON::addDictionaryMember called on non-dictionary"); } @@ -285,15 +288,11 @@ JSON::addDictionaryMember(std::string const& key, JSON const& val) bool JSON::checkDictionaryKeySeen(std::string const& key) { - auto* obj = dynamic_cast(m->value.get()); - if (nullptr == obj) { - throw std::logic_error("JSON::checkDictionaryKey called on non-dictionary"); + if (auto* obj = m ? dynamic_cast(m->value.get()) : nullptr) { + return !obj->parsed_keys.insert(key).second; } - if (obj->parsed_keys.count(key)) { - return true; - } - obj->parsed_keys.insert(key); - return false; + throw std::logic_error("JSON::checkDictionaryKey called on non-dictionary"); + return false; // unreachable } JSON @@ -305,16 +304,16 @@ JSON::makeArray() JSON JSON::addArrayElement(JSON const& val) { - auto* arr = dynamic_cast(m->value.get()); - if (nullptr == arr) { - throw std::runtime_error("JSON::addArrayElement called on non-array"); + if (auto* arr = m ? dynamic_cast(m->value.get()) : nullptr) { + if (val.m) { + arr->elements.push_back(val); + } else { + arr->elements.push_back(makeNull()); + } + return arr->elements.back(); } - if (val.m->value.get()) { - arr->elements.push_back(val); - } else { - arr->elements.push_back(makeNull()); - } - return arr->elements.back(); + throw std::runtime_error("JSON::addArrayElement called on non-array"); + return {}; // unreachable } JSON @@ -362,19 +361,19 @@ JSON::makeBlob(std::function fn) bool JSON::isArray() const { - return m->value->type_code == vt_array; + return m ? m->value->type_code == vt_array : false; } bool JSON::isDictionary() const { - return m->value->type_code == vt_dictionary; + return m && m->value->type_code == vt_dictionary; } bool JSON::getString(std::string& utf8) const { - if (m->value->type_code == vt_string) { + if (m && m->value->type_code == vt_string) { auto v = dynamic_cast(m->value.get()); utf8 = v->utf8; return true; @@ -385,7 +384,7 @@ JSON::getString(std::string& utf8) const bool JSON::getNumber(std::string& value) const { - if (m->value->type_code == vt_number) { + if (m && m->value->type_code == vt_number) { auto v = dynamic_cast(m->value.get()); value = v->encoded; return true; @@ -396,7 +395,7 @@ JSON::getNumber(std::string& value) const bool JSON::getBool(bool& value) const { - if (m->value->type_code == vt_bool) { + if (m && m->value->type_code == vt_bool) { auto v = dynamic_cast(m->value.get()); value = v->value; return true; @@ -407,13 +406,13 @@ JSON::getBool(bool& value) const bool JSON::isNull() const { - return m->value->type_code == vt_null; + return m && m->value->type_code == vt_null; } JSON JSON::getDictItem(std::string const& key) const { - if (auto v = dynamic_cast(m->value.get())) { + if (auto v = m ? dynamic_cast(m->value.get()) : nullptr) { if (auto it = v->members.find(key); it != v->members.end()) { return it->second; } @@ -424,39 +423,37 @@ JSON::getDictItem(std::string const& key) const bool JSON::forEachDictItem(std::function fn) const { - auto v = dynamic_cast(m->value.get()); - if (v == nullptr) { - return false; + if (auto v = m ? dynamic_cast(m->value.get()) : nullptr) { + for (auto const& [key, value]: v->members) { + fn(key, value); + } + return true; } - for (auto const& k: v->members) { - fn(k.first, JSON(k.second)); - } - return true; + return false; } bool JSON::forEachArrayItem(std::function fn) const { - auto v = dynamic_cast(m->value.get()); - if (v == nullptr) { - return false; + if (auto v = m ? dynamic_cast(m->value.get()) : nullptr) { + for (auto const& i: v->elements) { + fn(JSON(i)); + } + return true; } - for (auto const& i: v->elements) { - fn(JSON(i)); - } - return true; + return false; } bool JSON::checkSchema(JSON schema, std::list& errors) { - return checkSchemaInternal(m->value.get(), schema.m->value.get(), 0, errors, ""); + return m && checkSchemaInternal(m->value.get(), schema.m->value.get(), 0, errors, ""); } bool JSON::checkSchema(JSON schema, unsigned long flags, std::list& errors) { - return checkSchemaInternal(m->value.get(), schema.m->value.get(), flags, errors, ""); + return m && checkSchemaInternal(m->value.get(), schema.m->value.get(), flags, errors, ""); } bool @@ -1374,23 +1371,27 @@ JSON::parse(std::string const& s) void JSON::setStart(qpdf_offset_t start) { - m->start = start; + if (m) { + m->start = start; + } } void JSON::setEnd(qpdf_offset_t end) { - m->end = end; + if (m) { + m->end = end; + } } qpdf_offset_t JSON::getStart() const { - return m->start; + return m ? m->start : 0; } qpdf_offset_t JSON::getEnd() const { - return m->end; + return m ? m->end : 0; } diff --git a/libtests/json.cc b/libtests/json.cc index f265f6f6..67c8534f 100644 --- a/libtests/json.cc +++ b/libtests/json.cc @@ -2,8 +2,10 @@ #include #include +#include #include #include + #include static void @@ -131,6 +133,51 @@ test_main() " \"blob\": \"AQIDBAX//v38+w==\",\n" " \"normal\": \"string\"\n" "}"); + + // Check default constructed JSON object (order as per JSON.hh). + JSON uninitialized; + std::string ws; + auto pl = Pl_String ("", nullptr, ws); + uninitialized.write(&pl); + assert(ws == "null"); + assert(uninitialized.unparse() == "null"); + try { + uninitialized.addDictionaryMember("key", jarr); + assert(false); + } catch (std::runtime_error&) { + } + assert(jmap.addDictionaryMember("42", uninitialized).isNull()); + try { + uninitialized.addArrayElement(jarr); + assert(false); + } catch (std::runtime_error&) { + } + assert(jarr.addArrayElement(uninitialized).isNull()); + assert(!uninitialized.isArray()); + assert(!uninitialized.isDictionary()); + try { + uninitialized.checkDictionaryKeySeen("key"); + assert(false); + } catch (std::logic_error&) { + } + std::string st_out = "unchanged"; + assert(!uninitialized.getString(st_out)); + assert(!uninitialized.getNumber(st_out)); + bool b_out = true; + assert(!uninitialized.getBool(b_out)); + assert(b_out && st_out == "unchanged"); + assert(!uninitialized.isNull()); + assert(uninitialized.getDictItem("42").isNull()); + assert(!uninitialized.forEachDictItem([](auto k, auto v) {})); + assert(!uninitialized.forEachArrayItem([](auto v) {})); + std::list e; + assert(!uninitialized.checkSchema(JSON(), 0, e)); + assert(!uninitialized.checkSchema(JSON(), e)); + assert(e.empty()); + uninitialized.setStart(0); + uninitialized.setEnd(0); + assert(uninitialized.getStart() == 0); + assert(uninitialized.getEnd() == 0); } static void