diff --git a/extras/tests/JsonDocument/MemberProxy.cpp b/extras/tests/JsonDocument/MemberProxy.cpp index 49112237..261cb271 100644 --- a/extras/tests/JsonDocument/MemberProxy.cpp +++ b/extras/tests/JsonDocument/MemberProxy.cpp @@ -10,6 +10,8 @@ #include +#include "Allocators.hpp" + using ArduinoJson::detail::sizeofArray; using ArduinoJson::detail::sizeofObject; using ArduinoJson::detail::sizeofString; @@ -388,3 +390,19 @@ TEST_CASE("Deduplicate keys") { CHECK(key1 == key2); } } + +TEST_CASE("MemberProxy under memory constraints") { + ControllableAllocator allocator; + JsonDocument doc(4096, &allocator); + + SECTION("key allocation fails") { + allocator.disable(); + + doc[std::string("hello")] = "world"; + + REQUIRE(doc.is()); + REQUIRE(doc.size() == 0); + REQUIRE(doc.memoryUsage() == sizeofObject(1)); + REQUIRE(doc.overflowed() == true); + } +} diff --git a/extras/tests/JsonVariant/copy.cpp b/extras/tests/JsonVariant/copy.cpp index 3e7a93ec..636fe1b6 100644 --- a/extras/tests/JsonVariant/copy.cpp +++ b/extras/tests/JsonVariant/copy.cpp @@ -4,12 +4,15 @@ #include #include +#include "Allocators.hpp" using ArduinoJson::detail::sizeofString; TEST_CASE("JsonVariant::set(JsonVariant)") { - JsonDocument doc1(4096); - JsonDocument doc2(4096); + ControllableAllocator allocator; + SpyingAllocator spyingAllocator(&allocator); + JsonDocument doc1(4096, &spyingAllocator); + JsonDocument doc2(4096, &spyingAllocator); JsonVariant var1 = doc1.to(); JsonVariant var2 = doc2.to(); @@ -37,53 +40,103 @@ TEST_CASE("JsonVariant::set(JsonVariant)") { SECTION("stores const char* by reference") { var1.set("hello!!"); + spyingAllocator.clearLog(); + var2.set(var1); REQUIRE(doc1.memoryUsage() == 0); REQUIRE(doc2.memoryUsage() == 0); + REQUIRE(spyingAllocator.log() == AllocatorLog()); } SECTION("stores char* by copy") { char str[] = "hello!!"; - var1.set(str); + spyingAllocator.clearLog(); + var2.set(var1); REQUIRE(doc1.memoryUsage() == sizeofString(7)); REQUIRE(doc2.memoryUsage() == sizeofString(7)); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofString((7)))); + } + + SECTION("fails gracefully if string allocation fails") { + char str[] = "hello!!"; + var1.set(str); + allocator.disable(); + spyingAllocator.clearLog(); + + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == sizeofString(7)); + REQUIRE(doc2.memoryUsage() == 0); + REQUIRE(doc2.overflowed() == true); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::AllocateFail(sizeofString((7)))); } SECTION("stores std::string by copy") { var1.set(std::string("hello!!")); + spyingAllocator.clearLog(); + var2.set(var1); REQUIRE(doc1.memoryUsage() == sizeofString(7)); REQUIRE(doc2.memoryUsage() == sizeofString(7)); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofString((7)))); } SECTION("stores Serialized by reference") { var1.set(serialized("hello!!", 8)); + spyingAllocator.clearLog(); + var2.set(var1); REQUIRE(doc1.memoryUsage() == 0); REQUIRE(doc2.memoryUsage() == 0); + REQUIRE(spyingAllocator.log() == AllocatorLog()); } SECTION("stores Serialized by copy") { char str[] = "hello!!"; var1.set(serialized(str, 7)); + spyingAllocator.clearLog(); + var2.set(var1); REQUIRE(doc1.memoryUsage() == sizeofString(7)); REQUIRE(doc2.memoryUsage() == sizeofString(7)); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofString((7)))); } SECTION("stores Serialized by copy") { var1.set(serialized(std::string("hello!!"))); + spyingAllocator.clearLog(); + var2.set(var1); REQUIRE(doc1.memoryUsage() == sizeofString(7)); REQUIRE(doc2.memoryUsage() == sizeofString(7)); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofString((7)))); + } + + SECTION("fails gracefully if raw string allocation fails") { + var1.set(serialized(std::string("hello!!"))); + allocator.disable(); + spyingAllocator.clearLog(); + + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == sizeofString(7)); + REQUIRE(doc2.memoryUsage() == 0); + REQUIRE(doc2.overflowed() == true); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::AllocateFail(sizeofString((7)))); } SECTION("destination is unbound") { diff --git a/src/ArduinoJson/Array/JsonArray.hpp b/src/ArduinoJson/Array/JsonArray.hpp index 00df67ce..96c04a0e 100644 --- a/src/ArduinoJson/Array/JsonArray.hpp +++ b/src/ArduinoJson/Array/JsonArray.hpp @@ -6,6 +6,7 @@ #include #include +#include ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE @@ -43,9 +44,7 @@ class JsonArray : public detail::VariantOperators { // Returns a reference to the new element. // https://arduinojson.org/v6/api/jsonarray/add/ JsonVariant add() const { - if (!_data) - return JsonVariant(); - return JsonVariant(_pool, _data->addElement(_pool)); + return JsonVariant(_pool, collectionAddElement(_data, _pool)); } // Appends a value to the array. @@ -79,9 +78,7 @@ class JsonArray : public detail::VariantOperators { // Copies an array. // https://arduinojson.org/v6/api/jsonarray/set/ FORCE_INLINE bool set(JsonArrayConst src) const { - if (!_data || !src._data) - return false; - return _data->copyFrom(*src._data, _pool); + return collectionCopy(_data, src._data, _pool); } // Compares the content of two arrays. @@ -93,27 +90,21 @@ class JsonArray : public detail::VariantOperators { // ⚠️ Doesn't release the memory associated with the removed element. // https://arduinojson.org/v6/api/jsonarray/remove/ FORCE_INLINE void remove(iterator it) const { - if (!_data) - return; - _data->removeSlot(it._slot); + collectionRemove(_data, it._slot, _pool); } // Removes the element at the specified index. // ⚠️ Doesn't release the memory associated with the removed element. // https://arduinojson.org/v6/api/jsonarray/remove/ FORCE_INLINE void remove(size_t index) const { - if (!_data) - return; - _data->removeElement(index); + collectionRemoveElement(_data, index, _pool); } // Removes all the elements of the array. // ⚠️ Doesn't release the memory associated with the removed elements. // https://arduinojson.org/v6/api/jsonarray/clear/ void clear() const { - if (!_data) - return; - _data->clear(); + collectionClear(_data, _pool); } // Gets or sets the element at the specified index. diff --git a/src/ArduinoJson/Collection/CollectionData.hpp b/src/ArduinoJson/Collection/CollectionData.hpp index 2c5c8f41..4ca334ef 100644 --- a/src/ArduinoJson/Collection/CollectionData.hpp +++ b/src/ArduinoJson/Collection/CollectionData.hpp @@ -11,7 +11,6 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE -class MemoryPool; class VariantData; class VariantSlot; @@ -28,30 +27,13 @@ class CollectionData { // Array only - VariantData* addElement(MemoryPool* pool); - VariantData* getElement(size_t index) const; - VariantData* getOrAddElement(size_t index, MemoryPool* pool); - - void removeElement(size_t index); - // Object only - template - VariantData* addMember(TAdaptedString key, MemoryPool* pool); - template VariantData* getMember(TAdaptedString key) const; - template - VariantData* getOrAddMember(TAdaptedString key, MemoryPool* pool); - - template - void removeMember(TAdaptedString key) { - removeSlot(getSlot(key)); - } - template bool containsKey(const TAdaptedString& key) const; @@ -61,23 +43,21 @@ class CollectionData { size_t memoryUsage() const; size_t size() const; - VariantSlot* addSlot(MemoryPool*); + void addSlot(VariantSlot*); void removeSlot(VariantSlot* slot); - bool copyFrom(const CollectionData& src, MemoryPool* pool); - VariantSlot* head() const { return _head; } void movePointers(ptrdiff_t variantDistance); - private: VariantSlot* getSlot(size_t index) const; template VariantSlot* getSlot(TAdaptedString key) const; + private: VariantSlot* getPreviousSlot(VariantSlot*) const; }; diff --git a/src/ArduinoJson/Collection/CollectionFunctions.hpp b/src/ArduinoJson/Collection/CollectionFunctions.hpp new file mode 100644 index 00000000..9ae96e21 --- /dev/null +++ b/src/ArduinoJson/Collection/CollectionFunctions.hpp @@ -0,0 +1,93 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2023, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE + +inline VariantData* collectionAddElement(CollectionData* array, + MemoryPool* pool) { + if (!array) + return nullptr; + auto slot = pool->allocVariant(); + if (!slot) + return nullptr; + array->addSlot(slot); + return slot->data(); +} + +template +inline VariantData* collectionAddMember(CollectionData* obj, TAdaptedString key, + MemoryPool* pool) { + ARDUINOJSON_ASSERT(!key.isNull()); + ARDUINOJSON_ASSERT(obj != nullptr); + if (!obj) + return nullptr; + auto slot = pool->allocVariant(); + if (!slot) + return nullptr; + auto storedKey = storeString(pool, key); + if (!storedKey) + return nullptr; + slot->setKey(storedKey); + obj->addSlot(slot); + return slot->data(); +} + +inline void collectionClear(CollectionData* c, MemoryPool* pool) { + if (!c) + return; + for (auto slot = c->head(); slot; slot = slot->next()) + slotRelease(slot, pool); + c->clear(); +} + +inline bool collectionCopy(CollectionData* dst, const CollectionData* src, + MemoryPool* pool) { + if (!dst || !src) + return false; + + collectionClear(dst, pool); + + for (VariantSlot* s = src->head(); s; s = s->next()) { + VariantData* var; + if (s->key() != 0) { + JsonString key(s->key(), + s->ownsKey() ? JsonString::Copied : JsonString::Linked); + var = collectionAddMember(dst, adaptString(key), pool); + } else { + var = collectionAddElement(dst, pool); + } + if (!variantCopyFrom(var, s->data(), pool)) + return false; + } + return true; +} + +inline void collectionRemove(CollectionData* data, VariantSlot* slot, + MemoryPool* pool) { + if (!data || !slot) + return; + data->removeSlot(slot); + slotRelease(slot, pool); +} + +inline void collectionRemoveElement(CollectionData* array, size_t index, + MemoryPool* pool) { + if (!array) + return; + collectionRemove(array, array->getSlot(index), pool); +} + +template +inline void collectionRemoveMember(CollectionData* obj, TAdaptedString key, + MemoryPool* pool) { + if (!obj) + return; + collectionRemove(obj, obj->getSlot(key), pool); +} + +ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/src/ArduinoJson/Collection/CollectionImpl.hpp b/src/ArduinoJson/Collection/CollectionImpl.hpp index 0e77fe35..27701688 100644 --- a/src/ArduinoJson/Collection/CollectionImpl.hpp +++ b/src/ArduinoJson/Collection/CollectionImpl.hpp @@ -11,41 +11,16 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE -inline VariantSlot* CollectionData::addSlot(MemoryPool* pool) { - VariantSlot* slot = pool->allocVariant(); - if (!slot) - return 0; +inline void CollectionData::addSlot(VariantSlot* slot) { + ARDUINOJSON_ASSERT(slot != nullptr); if (_tail) { - ARDUINOJSON_ASSERT(pool->owns(_tail)); // Can't alter a linked array/object _tail->setNextNotNull(slot); _tail = slot; } else { _head = slot; _tail = slot; } - - slot->clear(); - return slot; -} - -inline VariantData* CollectionData::addElement(MemoryPool* pool) { - return slotData(addSlot(pool)); -} - -template -inline VariantData* CollectionData::addMember(TAdaptedString key, - MemoryPool* pool) { - VariantSlot* slot = addSlot(pool); - if (!slot) - return 0; - auto storedKey = storeString(pool, key); - if (!storedKey) { - removeSlot(slot); - return 0; - } - slot->setKey(storedKey); - return slot->data(); } inline void CollectionData::clear() { @@ -58,26 +33,6 @@ inline bool CollectionData::containsKey(const TAdaptedString& key) const { return getSlot(key) != 0; } -inline bool CollectionData::copyFrom(const CollectionData& src, - MemoryPool* pool) { - clear(); - for (VariantSlot* s = src._head; s; s = s->next()) { - VariantData* var; - if (s->key() != 0) { - JsonString key(s->key(), - s->ownsKey() ? JsonString::Copied : JsonString::Linked); - var = addMember(adaptString(key), pool); - } else { - var = addElement(pool); - } - if (!var) - return false; - if (!var->copyFrom(*s->data(), pool)) - return false; - } - return true; -} - template inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const { if (key.isNull()) @@ -114,42 +69,11 @@ inline VariantData* CollectionData::getMember(TAdaptedString key) const { return slot ? slot->data() : 0; } -template -inline VariantData* CollectionData::getOrAddMember(TAdaptedString key, - MemoryPool* pool) { - // ignore null key - if (key.isNull()) - return 0; - - // search a matching key - VariantSlot* slot = getSlot(key); - if (slot) - return slot->data(); - - return addMember(key, pool); -} - inline VariantData* CollectionData::getElement(size_t index) const { VariantSlot* slot = getSlot(index); return slot ? slot->data() : 0; } -inline VariantData* CollectionData::getOrAddElement(size_t index, - MemoryPool* pool) { - VariantSlot* slot = _head; - while (slot && index > 0) { - slot = slot->next(); - index--; - } - if (!slot) - index++; - while (index > 0) { - slot = addSlot(pool); - index--; - } - return slotData(slot); -} - inline void CollectionData::removeSlot(VariantSlot* slot) { if (!slot) return; @@ -163,10 +87,6 @@ inline void CollectionData::removeSlot(VariantSlot* slot) { _tail = prev; } -inline void CollectionData::removeElement(size_t index) { - removeSlot(getSlot(index)); -} - inline size_t CollectionData::memoryUsage() const { size_t total = 0; for (VariantSlot* s = _head; s; s = s->next()) { diff --git a/src/ArduinoJson/Document/JsonDocument.hpp b/src/ArduinoJson/Document/JsonDocument.hpp index 0fcf261e..4802ece2 100644 --- a/src/ArduinoJson/Document/JsonDocument.hpp +++ b/src/ArduinoJson/Document/JsonDocument.hpp @@ -119,7 +119,7 @@ class JsonDocument : public detail::VariantOperators { // https://arduinojson.org/v6/api/jsondocument/clear/ void clear() { _pool.clear(); - _data.setNull(); + _data.reset(); } // Returns true if the root is of the specified type. @@ -297,7 +297,7 @@ class JsonDocument : public detail::VariantOperators { // Returns a reference to the new element. // https://arduinojson.org/v6/api/jsondocument/add/ FORCE_INLINE JsonVariant add() { - return JsonVariant(&_pool, _data.addElement(&_pool)); + return JsonVariant(&_pool, variantAddElement(&_data, &_pool)); } // Appends a value to the root array. @@ -318,7 +318,7 @@ class JsonDocument : public detail::VariantOperators { // ⚠️ Doesn't release the memory associated with the removed element. // https://arduinojson.org/v6/api/jsondocument/remove/ FORCE_INLINE void remove(size_t index) { - _data.remove(index); + variantRemoveElement(getData(), index, getPool()); } // Removes a member of the root object. @@ -327,7 +327,7 @@ class JsonDocument : public detail::VariantOperators { template FORCE_INLINE typename detail::enable_if::value>::type remove(TChar* key) { - _data.remove(detail::adaptString(key)); + variantRemoveMember(getData(), detail::adaptString(key), getPool()); } // Removes a member of the root object. @@ -337,7 +337,7 @@ class JsonDocument : public detail::VariantOperators { FORCE_INLINE typename detail::enable_if::value>::type remove(const TString& key) { - _data.remove(detail::adaptString(key)); + variantRemoveMember(getData(), detail::adaptString(key), getPool()); } FORCE_INLINE operator JsonVariant() { @@ -364,7 +364,7 @@ class JsonDocument : public detail::VariantOperators { void moveAssignFrom(JsonDocument& src) { _data = src._data; - src._data.setNull(); + src._data.reset(); _pool = move(src._pool); } diff --git a/src/ArduinoJson/Json/JsonDeserializer.hpp b/src/ArduinoJson/Json/JsonDeserializer.hpp index 4b8e83be..dfee6d9b 100644 --- a/src/ArduinoJson/Json/JsonDeserializer.hpp +++ b/src/ArduinoJson/Json/JsonDeserializer.hpp @@ -173,7 +173,7 @@ class JsonDeserializer { for (;;) { if (memberFilter.allow()) { // Allocate slot in array - VariantData* value = array.addElement(_pool); + VariantData* value = collectionAddElement(&array, _pool); if (!value) return DeserializationError::NoMemory; @@ -280,11 +280,12 @@ class JsonDeserializer { key = _stringStorage.save(); // Allocate slot in object - VariantSlot* slot = object.addSlot(_pool); + VariantSlot* slot = _pool->allocVariant(); if (!slot) return DeserializationError::NoMemory; slot->setKey(key); + object.addSlot(slot); variant = slot->data(); } diff --git a/src/ArduinoJson/Memory/MemoryPool.hpp b/src/ArduinoJson/Memory/MemoryPool.hpp index 2ea1d68b..2a294f97 100644 --- a/src/ArduinoJson/Memory/MemoryPool.hpp +++ b/src/ArduinoJson/Memory/MemoryPool.hpp @@ -99,7 +99,10 @@ class MemoryPool { } VariantSlot* allocVariant() { - return allocRight(); + auto slot = allocRight(); + if (slot) + slot->clear(); + return slot; } template diff --git a/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp b/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp index 089bf496..71c52edf 100644 --- a/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp +++ b/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp @@ -435,7 +435,7 @@ class MsgPackDeserializer { if (memberFilter.allow()) { ARDUINOJSON_ASSERT(array != 0); - value = array->addElement(_pool); + value = collectionAddElement(array, _pool); if (!value) return DeserializationError::NoMemory; } else { @@ -496,11 +496,12 @@ class MsgPackDeserializer { // Save key in memory pool. key = _stringStorage.save(); - VariantSlot* slot = object->addSlot(_pool); + VariantSlot* slot = _pool->allocVariant(); if (!slot) return DeserializationError::NoMemory; slot->setKey(key); + object->addSlot(slot); member = slot->data(); } else { diff --git a/src/ArduinoJson/Object/JsonObject.hpp b/src/ArduinoJson/Object/JsonObject.hpp index f2c73b7e..a14b76c0 100644 --- a/src/ArduinoJson/Object/JsonObject.hpp +++ b/src/ArduinoJson/Object/JsonObject.hpp @@ -87,17 +87,13 @@ class JsonObject : public detail::VariantOperators { // ⚠️ Doesn't release the memory associated with the removed members. // https://arduinojson.org/v6/api/jsonobject/clear/ void clear() const { - if (!_data) - return; - _data->clear(); + collectionClear(_data, _pool); } // Copies an object. // https://arduinojson.org/v6/api/jsonobject/set/ FORCE_INLINE bool set(JsonObjectConst src) { - if (!_data || !src._data) - return false; - return _data->copyFrom(*src._data, _pool); + return collectionCopy(_data, src._data, _pool); } // Compares the content of two objects. @@ -129,9 +125,7 @@ class JsonObject : public detail::VariantOperators { // ⚠️ Doesn't release the memory associated with the removed member. // https://arduinojson.org/v6/api/jsonobject/remove/ FORCE_INLINE void remove(iterator it) const { - if (!_data) - return; - _data->removeSlot(it._slot); + collectionRemove(_data, it._slot, _pool); } // Removes the member with the specified key. @@ -139,7 +133,7 @@ class JsonObject : public detail::VariantOperators { // https://arduinojson.org/v6/api/jsonobject/remove/ template FORCE_INLINE void remove(const TString& key) const { - removeMember(detail::adaptString(key)); + collectionRemoveMember(_data, detail::adaptString(key), _pool); } // Removes the member with the specified key. @@ -147,7 +141,7 @@ class JsonObject : public detail::VariantOperators { // https://arduinojson.org/v6/api/jsonobject/remove/ template FORCE_INLINE void remove(TChar* key) const { - removeMember(detail::adaptString(key)); + collectionRemoveMember(_data, detail::adaptString(key), _pool); } // Returns true if the object contains the specified key. @@ -214,9 +208,7 @@ class JsonObject : public detail::VariantOperators { template void removeMember(TAdaptedString key) const { - if (!_data) - return; - _data->removeMember(key); + collectionRemove(_data, _data->getSlot(key), _pool); } detail::CollectionData* _data; diff --git a/src/ArduinoJson/Variant/ConverterImpl.hpp b/src/ArduinoJson/Variant/ConverterImpl.hpp index 8095cc30..32bdc9ee 100644 --- a/src/ArduinoJson/Variant/ConverterImpl.hpp +++ b/src/ArduinoJson/Variant/ConverterImpl.hpp @@ -42,10 +42,8 @@ struct Converter< !detail::is_same::value>::type> : private detail::VariantAttorney { static void toJson(T src, JsonVariant dst) { - auto data = getData(dst); ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T); - if (data) - data->setInteger(src); + variantSetInteger(getData(dst), src, getPool(dst)); } static T fromJson(JsonVariantConst src) { @@ -81,9 +79,7 @@ struct Converter::value>::type> template <> struct Converter : private detail::VariantAttorney { static void toJson(bool src, JsonVariant dst) { - auto data = getData(dst); - if (data) - data->setBoolean(src); + variantSetBoolean(getData(dst), src, getPool(dst)); } static bool fromJson(JsonVariantConst src) { @@ -102,9 +98,7 @@ struct Converter< T, typename detail::enable_if::value>::type> : private detail::VariantAttorney { static void toJson(T src, JsonVariant dst) { - auto data = getData(dst); - if (data) - data->setFloat(static_cast(src)); + variantSetFloat(getData(dst), static_cast(src), getPool(dst)); } static T fromJson(JsonVariantConst src) { @@ -165,9 +159,7 @@ template <> struct Converter> : private detail::VariantAttorney { static void toJson(SerializedValue src, JsonVariant dst) { - auto data = getData(dst); - if (data) - data->setLinkedRaw(src); + variantSetLinkedRaw(getData(dst), src, getPool(dst)); } }; @@ -180,17 +172,14 @@ struct Converter< typename detail::enable_if::value>::type> : private detail::VariantAttorney { static void toJson(SerializedValue src, JsonVariant dst) { - auto data = getData(dst); - auto pool = getPool(dst); - if (data) - data->storeOwnedRaw(src, pool); + variantSetOwnedRaw(getData(dst), src, getPool(dst)); } }; template <> struct Converter : private detail::VariantAttorney { static void toJson(decltype(nullptr), JsonVariant dst) { - variantSetNull(getData(dst)); + variantSetNull(getData(dst), getPool(dst)); } static decltype(nullptr) fromJson(JsonVariantConst) { return nullptr; diff --git a/src/ArduinoJson/Variant/SlotFunctions.hpp b/src/ArduinoJson/Variant/SlotFunctions.hpp index 29102c92..efa3f76e 100644 --- a/src/ArduinoJson/Variant/SlotFunctions.hpp +++ b/src/ArduinoJson/Variant/SlotFunctions.hpp @@ -21,4 +21,10 @@ inline size_t slotSize(const VariantSlot* var) { inline VariantData* slotData(VariantSlot* slot) { return reinterpret_cast(slot); } + +inline void slotRelease(const VariantSlot* slot, MemoryPool* pool) { + ARDUINOJSON_ASSERT(slot != nullptr); + variantRelease(slot->data(), pool); +} + ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/src/ArduinoJson/Variant/VariantData.hpp b/src/ArduinoJson/Variant/VariantData.hpp index b61774d1..cfc8af41 100644 --- a/src/ArduinoJson/Variant/VariantData.hpp +++ b/src/ArduinoJson/Variant/VariantData.hpp @@ -4,7 +4,6 @@ #pragma once -#include #include #include #include @@ -20,6 +19,10 @@ class VariantData { public: VariantData() : _flags(VALUE_IS_NULL) {} + void reset() { + _flags = VALUE_IS_NULL; + } + void operator=(const VariantData& src) { _content = src._content; _flags = uint8_t((_flags & OWNED_KEY_BIT) | (src._flags & ~OWNED_KEY_BIT)); @@ -68,9 +71,17 @@ class VariantData { T asFloat() const; JsonString asString() const; + JsonString asRaw() const; bool asBoolean() const; + const char* getOwnedString() const { + if (_flags & OWNED_VALUE_BIT) + return _content.asString.data; + else + return nullptr; + } + CollectionData* asArray() { return isArray() ? &_content.asCollection : 0; } @@ -91,8 +102,6 @@ class VariantData { return const_cast(this)->asObject(); } - bool copyFrom(const VariantData& src, MemoryPool* pool); - bool isArray() const { return (_flags & VALUE_IS_ARRAY) != 0; } @@ -139,17 +148,6 @@ class VariantData { return !isFloat(); } - void remove(size_t index) { - if (isArray()) - _content.asCollection.removeElement(index); - } - - template - void remove(TAdaptedString key) { - if (isObject()) - _content.asCollection.removeMember(key); - } - void setBoolean(bool value) { setType(VALUE_IS_BOOLEAN); _content.asBoolean = value; @@ -160,28 +158,16 @@ class VariantData { _content.asFloat = value; } - void setLinkedRaw(SerializedValue value) { - if (value.data()) { - setType(VALUE_IS_LINKED_RAW); - _content.asString.data = value.data(); - _content.asString.size = value.size(); - } else { - setType(VALUE_IS_NULL); - } + void setLinkedRaw(const char* data, size_t n) { + setType(VALUE_IS_LINKED_RAW); + _content.asString.data = data; + _content.asString.size = n; } - template - bool storeOwnedRaw(SerializedValue value, MemoryPool* pool) { - const char* dup = pool->saveString(adaptString(value.data(), value.size())); - if (dup) { - setType(VALUE_IS_OWNED_RAW); - _content.asString.data = dup; - _content.asString.size = value.size(); - return true; - } else { - setType(VALUE_IS_NULL); - return false; - } + void setOwnedRaw(const char* data, size_t n) { + setType(VALUE_IS_OWNED_RAW); + _content.asString.data = data; + _content.asString.size = n; } template @@ -239,42 +225,17 @@ class VariantData { return isCollection() ? _content.asCollection.size() : 0; } - VariantData* addElement(MemoryPool* pool) { - if (isNull()) - toArray(); - if (!isArray()) - return 0; - return _content.asCollection.addElement(pool); - } - VariantData* getElement(size_t index) const { const CollectionData* col = asArray(); return col ? col->getElement(index) : 0; } - VariantData* getOrAddElement(size_t index, MemoryPool* pool) { - if (isNull()) - toArray(); - if (!isArray()) - return 0; - return _content.asCollection.getOrAddElement(index, pool); - } - template VariantData* getMember(TAdaptedString key) const { const CollectionData* col = asObject(); return col ? col->getMember(key) : 0; } - template - VariantData* getOrAddMember(TAdaptedString key, MemoryPool* pool) { - if (isNull()) - toObject(); - if (!isObject()) - return 0; - return _content.asCollection.getOrAddMember(key, pool); - } - void movePointers(ptrdiff_t variantDistance) { if (_flags & COLLECTION_MASK) _content.asCollection.movePointers(variantDistance); diff --git a/src/ArduinoJson/Variant/VariantFunctions.hpp b/src/ArduinoJson/Variant/VariantFunctions.hpp index 6191b650..6ccfad1f 100644 --- a/src/ArduinoJson/Variant/VariantFunctions.hpp +++ b/src/ArduinoJson/Variant/VariantFunctions.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -11,6 +12,16 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +void slotRelease(const VariantSlot* slot, MemoryPool* pool); +bool collectionCopy(CollectionData* dst, const CollectionData* src, + MemoryPool* pool); +VariantData* collectionAddElement(CollectionData* array, MemoryPool* pool); +void collectionRemoveElement(CollectionData* data, size_t index, + MemoryPool* pool); +template +void collectionRemoveMember(CollectionData* data, TAdaptedString key, + MemoryPool* pool); + template inline typename TVisitor::result_type variantAccept(const VariantData* var, TVisitor& visitor) { @@ -20,6 +31,15 @@ inline typename TVisitor::result_type variantAccept(const VariantData* var, return visitor.visitNull(); } +inline void variantRelease(const VariantData* var, MemoryPool* pool) { + ARDUINOJSON_ASSERT(var != nullptr); + auto c = var->asCollection(); + if (c) { + for (auto slot = c->head(); slot; slot = slot->next()) + slotRelease(slot, pool); + } +} + inline bool variantCopyFrom(VariantData* dst, const VariantData* src, MemoryPool* pool) { if (!dst) @@ -28,20 +48,69 @@ inline bool variantCopyFrom(VariantData* dst, const VariantData* src, dst->setNull(); return true; } - return dst->copyFrom(*src, pool); + switch (src->type()) { + case VALUE_IS_ARRAY: + return collectionCopy(&dst->toArray(), src->asArray(), pool); + case VALUE_IS_OBJECT: + return collectionCopy(&dst->toObject(), src->asObject(), pool); + case VALUE_IS_OWNED_STRING: { + auto str = adaptString(src->asString()); + auto dup = storeString(pool, str, StringStoragePolicy::Copy()); + if (!dup) + return false; + dst->setString(dup); + return true; + } + case VALUE_IS_OWNED_RAW: { + auto str = adaptString(src->asRaw()); + auto dup = storeString(pool, str, StringStoragePolicy::Copy()); + if (!dup) + return false; + dst->setOwnedRaw(dup.c_str(), str.size()); + return true; + } + default: + *dst = *src; + return true; + } } -inline void variantSetNull(VariantData* var) { +inline void variantSetNull(VariantData* var, MemoryPool* pool) { if (!var) return; + variantRelease(var, pool); var->setNull(); } +inline void variantSetBoolean(VariantData* var, bool value, MemoryPool* pool) { + if (!var) + return; + variantRelease(var, pool); + var->setBoolean(value); +} + +inline void variantSetFloat(VariantData* var, JsonFloat value, + MemoryPool* pool) { + if (!var) + return; + variantRelease(var, pool); + var->setFloat(value); +} + +template +inline void variantSetInteger(VariantData* var, T value, MemoryPool* pool) { + if (!var) + return; + variantRelease(var, pool); + var->setInteger(value); +} + template inline void variantSetString(VariantData* var, TAdaptedString value, MemoryPool* pool) { if (!var) return; + variantRelease(var, pool); JsonString str = storeString(pool, value); if (str) var->setString(str); @@ -49,19 +118,46 @@ inline void variantSetString(VariantData* var, TAdaptedString value, var->setNull(); } +template +inline void variantSetOwnedRaw(VariantData* var, SerializedValue value, + MemoryPool* pool) { + if (!var) + return; + variantRelease(var, pool); + const char* dup = pool->saveString(adaptString(value.data(), value.size())); + if (dup) + var->setOwnedRaw(dup, value.size()); + else + var->setNull(); +} + +inline void variantSetLinkedRaw(VariantData* var, + SerializedValue value, + MemoryPool* pool) { + if (!var) + return; + variantRelease(var, pool); + if (value.data()) + var->setLinkedRaw(value.data(), value.size()); + else + var->setNull(); +} + inline size_t variantSize(const VariantData* var) { return var != 0 ? var->size() : 0; } -inline CollectionData* variantToArray(VariantData* var) { +inline CollectionData* variantToArray(VariantData* var, MemoryPool* pool) { if (!var) return 0; + variantRelease(var, pool); return &var->toArray(); } -inline CollectionData* variantToObject(VariantData* var) { +inline CollectionData* variantToObject(VariantData* var, MemoryPool* pool) { if (!var) return 0; + variantRelease(var, pool); return &var->toObject(); } @@ -69,15 +165,43 @@ inline VariantData* variantGetElement(const VariantData* var, size_t index) { return var != 0 ? var->getElement(index) : 0; } -inline NO_INLINE VariantData* variantAddElement(VariantData* var, - MemoryPool* pool) { - return var != 0 ? var->addElement(pool) : 0; +inline VariantData* variantAddElement(VariantData* var, MemoryPool* pool) { + if (!var) + return nullptr; + auto array = var->isNull() ? &var->toArray() : var->asArray(); + return collectionAddElement(array, pool); } inline NO_INLINE VariantData* variantGetOrAddElement(VariantData* var, size_t index, MemoryPool* pool) { - return var != 0 ? var->getOrAddElement(index, pool) : 0; + if (!var) + return nullptr; + auto array = var->isNull() ? &var->toArray() : var->asArray(); + if (!array) + return nullptr; + VariantSlot* slot = array->head(); + while (slot && index > 0) { + slot = slot->next(); + index--; + } + if (!slot) + index++; + while (index > 0) { + slot = pool->allocVariant(); + if (!slot) + return nullptr; + array->addSlot(slot); + index--; + } + return slot->data(); +} + +inline void variantRemoveElement(VariantData* var, size_t index, + MemoryPool* pool) { + if (!var) + return; + collectionRemoveElement(var->asArray(), index, pool); } template @@ -90,9 +214,23 @@ VariantData* variantGetMember(const VariantData* var, TAdaptedString key) { template VariantData* variantGetOrAddMember(VariantData* var, TAdaptedString key, MemoryPool* pool) { + if (!var || key.isNull()) + return nullptr; + auto obj = var->isNull() ? &var->toObject() : var->asObject(); + if (!obj) + return nullptr; + auto slot = obj->getSlot(key); + if (slot) + return slot->data(); + return collectionAddMember(obj, key, pool); +} + +template +void variantRemoveMember(VariantData* var, TAdaptedString key, + MemoryPool* pool) { if (!var) - return 0; - return var->getOrAddMember(key, pool); + return; + collectionRemoveMember(var->asObject(), key, pool); } inline bool variantIsNull(const VariantData* var) { diff --git a/src/ArduinoJson/Variant/VariantImpl.hpp b/src/ArduinoJson/Variant/VariantImpl.hpp index fdf5585c..ef5c4ab8 100644 --- a/src/ArduinoJson/Variant/VariantImpl.hpp +++ b/src/ArduinoJson/Variant/VariantImpl.hpp @@ -83,26 +83,16 @@ inline JsonString VariantData::asString() const { } } -inline bool VariantData::copyFrom(const VariantData& src, MemoryPool* pool) { - switch (src.type()) { - case VALUE_IS_ARRAY: - return toArray().copyFrom(src._content.asCollection, pool); - case VALUE_IS_OBJECT: - return toObject().copyFrom(src._content.asCollection, pool); - case VALUE_IS_OWNED_STRING: { - auto str = storeString(pool, adaptString(src.asString()), - StringStoragePolicy::Copy()); - setString(str); - return !str.isNull(); - } +inline JsonString VariantData::asRaw() const { + switch (type()) { + case VALUE_IS_LINKED_RAW: + return JsonString(_content.asString.data, _content.asString.size, + JsonString::Linked); case VALUE_IS_OWNED_RAW: - return storeOwnedRaw( - serialized(src._content.asString.data, src._content.asString.size), - pool); + return JsonString(_content.asString.data, _content.asString.size, + JsonString::Copied); default: - setType(src.type()); - _content = src._content; - return true; + return JsonString(); } } @@ -126,21 +116,21 @@ template template inline typename enable_if::value, JsonArray>::type VariantRefBase::to() const { - return JsonArray(getPool(), variantToArray(getOrCreateData())); + return JsonArray(getPool(), variantToArray(getOrCreateData(), getPool())); } template template typename enable_if::value, JsonObject>::type VariantRefBase::to() const { - return JsonObject(getPool(), variantToObject(getOrCreateData())); + return JsonObject(getPool(), variantToObject(getOrCreateData(), getPool())); } template template typename enable_if::value, JsonVariant>::type VariantRefBase::to() const { - variantSetNull(getOrCreateData()); + variantSetNull(getOrCreateData(), getPool()); return *this; } diff --git a/src/ArduinoJson/Variant/VariantRefBase.hpp b/src/ArduinoJson/Variant/VariantRefBase.hpp index 2afdda6a..55ed788d 100644 --- a/src/ArduinoJson/Variant/VariantRefBase.hpp +++ b/src/ArduinoJson/Variant/VariantRefBase.hpp @@ -30,7 +30,7 @@ class VariantRefBase : public VariantTag { // ⚠️ Doesn't release the memory associated with the previous value. // https://arduinojson.org/v6/api/jsonvariant/clear/ FORCE_INLINE void clear() const { - variantSetNull(getData()); + variantSetNull(getOrCreateData(), getPool()); } // Returns true if the value is null or the reference is unbound. @@ -112,11 +112,10 @@ class VariantRefBase : public VariantTag { VariantData* data = getOrCreateData(); if (!data) return; + variantSetNull(data, getPool()); const VariantData* targetData = VariantAttorney::getData(target); if (targetData) *data = *targetData; - else - data->setNull(); } // Copies the specified value. @@ -179,9 +178,7 @@ class VariantRefBase : public VariantTag { // ⚠️ Doesn't release the memory associated with the removed element. // https://arduinojson.org/v6/api/jsonvariant/remove/ FORCE_INLINE void remove(size_t index) const { - VariantData* data = getData(); - if (data) - data->remove(index); + variantRemoveElement(getData(), index, getPool()); } // Removes a member of the object. @@ -190,9 +187,7 @@ class VariantRefBase : public VariantTag { template FORCE_INLINE typename enable_if::value>::type remove( TChar* key) const { - VariantData* data = getData(); - if (data) - data->remove(adaptString(key)); + variantRemoveMember(getData(), adaptString(key), getPool()); } // Removes a member of the object. @@ -201,9 +196,7 @@ class VariantRefBase : public VariantTag { template FORCE_INLINE typename enable_if::value>::type remove( const TString& key) const { - VariantData* data = getData(); - if (data) - data->remove(adaptString(key)); + variantRemoveMember(getData(), adaptString(key), getPool()); } // Creates an array and appends it to the array.