JsonArray::remove() and JsonObject::remove() now release the memory of strings

This commit is contained in:
Benoit Blanchon
2018-11-09 17:27:32 +01:00
parent e842838a23
commit f375459d53
68 changed files with 1504 additions and 740 deletions

View File

@ -8,7 +8,8 @@ HEAD
* Removed `JsonObject::is<T>(k)` and `JsonObject::set(k,v)` * Removed `JsonObject::is<T>(k)` and `JsonObject::set(k,v)`
* Replaced `T JsonArray::get<T>(i)` with `JsonVariant JsonArray::get(i)` * Replaced `T JsonArray::get<T>(i)` with `JsonVariant JsonArray::get(i)`
* Replaced `T JsonObject::get<T>(k)` with `JsonVariant JsonObject::get(k)` * Replaced `T JsonObject::get<T>(k)` with `JsonVariant JsonObject::get(k)`
* `JsonArray::remove()` and `JsonObject::remove()` now release the memory of the variant * `JsonArray::remove()` and `JsonObject::remove()` now release the memory
* Added `JSON_STRING_SIZE()`
v6.5.0-beta (2018-10-13) v6.5.0-beta (2018-10-13)
----------- -----------

View File

@ -11,6 +11,8 @@ if(${COVERAGE})
set(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") set(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
endif() endif()
add_definitions(-DARDUINOJSON_DEBUG)
include_directories(${CMAKE_CURRENT_LIST_DIR}/src) include_directories(${CMAKE_CURRENT_LIST_DIR}/src)
add_subdirectory(third-party/catch) add_subdirectory(third-party/catch)
add_subdirectory(test) add_subdirectory(test)

View File

@ -1,6 +1,6 @@
# CAUTION: this file is invoked by https://github.com/google/oss-fuzz # CAUTION: this file is invoked by https://github.com/google/oss-fuzz
CXXFLAGS += -I../src CXXFLAGS += -I../src -DARDUINOJSON_DEBUG
all: \ all: \
$(OUT)/json_fuzzer \ $(OUT)/json_fuzzer \

View File

@ -5,17 +5,34 @@
// This file is NOT use by Google's OSS fuzz // This file is NOT use by Google's OSS fuzz
// I only use it to reproduce the bugs found // I only use it to reproduce the bugs found
#include <stdint.h> #include <stdint.h> // size_t
#include <fstream> #include <stdio.h> // fopen et al.
#include <stdlib.h> // exit
#include <iostream> #include <iostream>
#include <string> #include <vector>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
std::string read(const char* path) { std::vector<uint8_t> read(const char* path) {
std::ifstream file(path); FILE* f = fopen(path, "rb");
return std::string(std::istreambuf_iterator<char>(file), if (!f) {
std::istreambuf_iterator<char>()); std::cerr << "Failed to open " << path << std::endl;
exit(1);
}
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
fseek(f, 0, SEEK_SET);
std::vector<uint8_t> buffer(size);
if (fread(buffer.data(), 1, size, f) != size) {
fclose(f);
std::cerr << "Failed to read " << path << std::endl;
exit(1);
}
fclose(f);
return buffer;
} }
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {
@ -26,9 +43,8 @@ int main(int argc, const char* argv[]) {
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
std::cout << "Loading " << argv[i] << std::endl; std::cout << "Loading " << argv[i] << std::endl;
std::string buffer = read(argv[i]); std::vector<uint8_t> buffer = read(argv[i]);
LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t*>(buffer.data()), LLVMFuzzerTestOneInput(buffer.data(), buffer.size());
buffer.size());
} }
return 0; return 0;
} }

View File

@ -130,16 +130,6 @@
#endif #endif
#endif #endif
#ifndef ARDUINOJSON_ENABLE_ALIGNMENT
#ifdef ARDUINO_ARCH_AVR
// alignment isn't needed for 8-bit AVR
#define ARDUINOJSON_ENABLE_ALIGNMENT 0
#else
// but most processors need pointers to be align on word size
#define ARDUINOJSON_ENABLE_ALIGNMENT 1
#endif
#endif
// Control the exponentiation threshold for big numbers // Control the exponentiation threshold for big numbers
// CAUTION: cannot be more that 1e9 !!!! // CAUTION: cannot be more that 1e9 !!!!
#ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD #ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD

View File

@ -5,7 +5,6 @@
#pragma once #pragma once
#include "JsonVariantData.hpp" #include "JsonVariantData.hpp"
#include "Slot.hpp"
#include "SlotFunctions.hpp" #include "SlotFunctions.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
@ -13,7 +12,7 @@ namespace ARDUINOJSON_NAMESPACE {
inline JsonVariantData* arrayAdd(JsonArrayData* arr, MemoryPool* pool) { inline JsonVariantData* arrayAdd(JsonArrayData* arr, MemoryPool* pool) {
if (!arr) return 0; if (!arr) return 0;
Slot* slot = pool->allocSlot(); VariantSlot* slot = pool->allocVariant();
if (!slot) return 0; if (!slot) return 0;
slot->next = 0; slot->next = 0;
@ -29,20 +28,22 @@ inline JsonVariantData* arrayAdd(JsonArrayData* arr, MemoryPool* pool) {
arr->tail = slot; arr->tail = slot;
} }
slot->value.keyIsOwned = false;
return &slot->value; return &slot->value;
} }
inline Slot* arrayGetSlot(const JsonArrayData* arr, size_t index) { inline VariantSlot* arrayGetSlot(const JsonArrayData* arr, size_t index) {
if (!arr) return 0; if (!arr) return 0;
return slotAdvance(arr->head, index); return slotAdvance(arr->head, index);
} }
inline JsonVariantData* arrayGet(const JsonArrayData* arr, size_t index) { inline JsonVariantData* arrayGet(const JsonArrayData* arr, size_t index) {
Slot* slot = arrayGetSlot(arr, index); VariantSlot* slot = arrayGetSlot(arr, index);
return slot ? &slot->value : 0; return slot ? &slot->value : 0;
} }
inline void arrayRemove(JsonArrayData* arr, Slot* slot, MemoryPool* pool) { inline void arrayRemove(JsonArrayData* arr, VariantSlot* slot,
MemoryPool* pool) {
if (!arr || !slot) return; if (!arr || !slot) return;
if (slot->prev) if (slot->prev)
@ -73,7 +74,7 @@ inline bool arrayCopy(JsonArrayData* dst, const JsonArrayData* src,
MemoryPool* pool) { MemoryPool* pool) {
if (!dst || !src) return false; if (!dst || !src) return false;
arrayClear(dst); arrayClear(dst);
for (Slot* s = src->head; s; s = s->next) { for (VariantSlot* s = src->head; s; s = s->next) {
if (!variantCopy(arrayAdd(dst, pool), &s->value, pool)) return false; if (!variantCopy(arrayAdd(dst, pool), &s->value, pool)) return false;
} }
return true; return true;
@ -84,8 +85,8 @@ bool variantEquals(const JsonVariantData*, const JsonVariantData*);
inline bool arrayEquals(const JsonArrayData* a1, const JsonArrayData* a2) { inline bool arrayEquals(const JsonArrayData* a1, const JsonArrayData* a2) {
if (a1 == a2) return true; if (a1 == a2) return true;
if (!a1 || !a2) return false; if (!a1 || !a2) return false;
Slot* s1 = a1->head; VariantSlot* s1 = a1->head;
Slot* s2 = a2->head; VariantSlot* s2 = a2->head;
for (;;) { for (;;) {
if (s1 == s2) return true; if (s1 == s2) return true;
if (!s1 || !s2) return false; if (!s1 || !s2) return false;

View File

@ -26,13 +26,13 @@ enum JsonVariantType {
}; };
struct JsonObjectData { struct JsonObjectData {
struct Slot *head; struct VariantSlot *head;
struct Slot *tail; struct VariantSlot *tail;
}; };
struct JsonArrayData { struct JsonArrayData {
struct Slot *head; struct VariantSlot *head;
struct Slot *tail; struct VariantSlot *tail;
}; };
struct RawData { struct RawData {
@ -48,6 +48,8 @@ union JsonVariantContent {
JsonArrayData asArray; JsonArrayData asArray;
JsonObjectData asObject; JsonObjectData asObject;
const char *asString; const char *asString;
struct StringSlot *asOwnedString;
struct StringSlot *asOwnedRaw;
struct { struct {
const char *data; const char *data;
size_t size; size_t size;
@ -56,7 +58,7 @@ union JsonVariantContent {
// this struct must be a POD type to prevent error calling offsetof on clang // this struct must be a POD type to prevent error calling offsetof on clang
struct JsonVariantData { struct JsonVariantData {
bool keyIsStatic : 1; bool keyIsOwned : 1;
JsonVariantType type : 7; JsonVariantType type : 7;
JsonVariantContent content; JsonVariantContent content;
}; };

View File

@ -11,11 +11,11 @@
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
template <typename TKey> template <typename TKey>
inline Slot* objectFindSlot(const JsonObjectData* obj, TKey key) { inline VariantSlot* objectFindSlot(const JsonObjectData* obj, TKey key) {
if (!obj) return 0; if (!obj) return 0;
Slot* slot = obj->head; VariantSlot* slot = obj->head;
while (slot) { while (slot) {
if (key.equals(slot->key)) break; if (key.equals(slotGetKey(slot))) break;
slot = slot->next; slot = slot->next;
} }
return slot; return slot;
@ -29,7 +29,7 @@ inline bool objectContainsKey(const JsonObjectData* obj, const TKey& key) {
template <typename TKey> template <typename TKey>
inline JsonVariantData* objectAdd(JsonObjectData* obj, TKey key, inline JsonVariantData* objectAdd(JsonObjectData* obj, TKey key,
MemoryPool* pool) { MemoryPool* pool) {
Slot* slot = pool->allocSlot(); VariantSlot* slot = pool->allocVariant();
if (!slot) return 0; if (!slot) return 0;
slot->next = 0; slot->next = 0;
@ -58,7 +58,7 @@ inline JsonVariantData* objectSet(JsonObjectData* obj, TKey key,
if (key.isNull()) return 0; if (key.isNull()) return 0;
// search a matching key // search a matching key
Slot* slot = objectFindSlot(obj, key); VariantSlot* slot = objectFindSlot(obj, key);
if (slot) return &slot->value; if (slot) return &slot->value;
return objectAdd(obj, key, pool); return objectAdd(obj, key, pool);
@ -66,7 +66,7 @@ inline JsonVariantData* objectSet(JsonObjectData* obj, TKey key,
template <typename TKey> template <typename TKey>
inline JsonVariantData* objectGet(const JsonObjectData* obj, TKey key) { inline JsonVariantData* objectGet(const JsonObjectData* obj, TKey key) {
Slot* slot = objectFindSlot(obj, key); VariantSlot* slot = objectFindSlot(obj, key);
return slot ? &slot->value : 0; return slot ? &slot->value : 0;
} }
@ -76,7 +76,8 @@ inline void objectClear(JsonObjectData* obj) {
obj->tail = 0; obj->tail = 0;
} }
inline void objectRemove(JsonObjectData* obj, Slot* slot, MemoryPool* pool) { inline void objectRemove(JsonObjectData* obj, VariantSlot* slot,
MemoryPool* pool) {
if (!obj) return; if (!obj) return;
if (!slot) return; if (!slot) return;
if (slot->prev) if (slot->prev)
@ -102,12 +103,12 @@ inline bool objectCopy(JsonObjectData* dst, const JsonObjectData* src,
MemoryPool* pool) { MemoryPool* pool) {
if (!dst || !src) return false; if (!dst || !src) return false;
objectClear(dst); objectClear(dst);
for (Slot* s = src->head; s; s = s->next) { for (VariantSlot* s = src->head; s; s = s->next) {
JsonVariantData* var; JsonVariantData* var;
if (s->value.keyIsStatic) if (s->value.keyIsOwned)
var = objectAdd(dst, ZeroTerminatedRamStringConst(s->key), pool); var = objectAdd(dst, ZeroTerminatedRamString(s->ownedKey->value), pool);
else else
var = objectAdd(dst, ZeroTerminatedRamString(s->key), pool); var = objectAdd(dst, ZeroTerminatedRamStringConst(s->linkedKey), pool);
if (!variantCopy(var, &s->value, pool)) return false; if (!variantCopy(var, &s->value, pool)) return false;
} }
return true; return true;
@ -117,9 +118,9 @@ inline bool objectEquals(const JsonObjectData* o1, const JsonObjectData* o2) {
if (o1 == o2) return true; if (o1 == o2) return true;
if (!o1 || !o2) return false; if (!o1 || !o2) return false;
for (Slot* s = o1->head; s; s = s->next) { for (VariantSlot* s = o1->head; s; s = s->next) {
JsonVariantData* v1 = &s->value; JsonVariantData* v1 = &s->value;
JsonVariantData* v2 = objectGet(o2, makeString(s->key)); JsonVariantData* v2 = objectGet(o2, makeString(slotGetKey(s)));
if (!variantEquals(v1, v2)) return false; if (!variantEquals(v1, v2)) return false;
} }
return true; return true;

View File

@ -7,65 +7,80 @@
#include "../Memory/MemoryPool.hpp" #include "../Memory/MemoryPool.hpp"
#include "../Strings/StringTypes.hpp" #include "../Strings/StringTypes.hpp"
#include "JsonVariantData.hpp" #include "JsonVariantData.hpp"
#include "Slot.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
template <typename TKey> template <typename TKey>
inline bool slotSetKey(Slot* slot, TKey key, MemoryPool* pool) { inline bool slotSetKey(VariantSlot* var, TKey key, MemoryPool* pool) {
const char* dup = key.save(pool); StringSlot* slot = key.save(pool);
if (!dup) return false; if (!slot) return false;
slot->key = dup; var->ownedKey = slot;
slot->value.keyIsStatic = false; var->value.keyIsOwned = true;
return true; return true;
} }
inline bool slotSetKey(Slot* slot, ZeroTerminatedRamStringConst key, inline bool slotSetKey(VariantSlot* var, ZeroTerminatedRamStringConst key,
MemoryPool* pool) { MemoryPool*) {
slot->key = key.save(pool); var->linkedKey = key.c_str();
slot->value.keyIsStatic = true; var->value.keyIsOwned = false;
return true; return true;
} }
inline bool slotSetKey(Slot* slot, StringInMemoryPool key, MemoryPool* pool) { inline bool slotSetKey(VariantSlot* var, StringInMemoryPool key, MemoryPool*) {
slot->key = key.save(pool); var->ownedKey = key.slot();
slot->value.keyIsStatic = false; var->value.keyIsOwned = true;
return true; return true;
} }
inline const Slot* slotAdvance(const Slot* slot, size_t distance) { inline const char* slotGetKey(const VariantSlot* var) {
while (distance && slot) { return var->value.keyIsOwned ? var->ownedKey->value : var->linkedKey;
slot = slot->next; }
inline const VariantSlot* slotAdvance(const VariantSlot* var, size_t distance) {
while (distance && var) {
var = var->next;
distance--; distance--;
} }
return slot; return var;
} }
inline Slot* slotAdvance(Slot* slot, size_t distance) { inline VariantSlot* slotAdvance(VariantSlot* var, size_t distance) {
while (distance && slot) { while (distance && var) {
slot = slot->next; var = var->next;
distance--; distance--;
} }
return slot; return var;
} }
inline size_t slotSize(const Slot* slot) { inline size_t slotSize(const VariantSlot* var) {
size_t n = 0; size_t n = 0;
while (slot) { while (var) {
n++; n++;
slot = slot->next; var = var->next;
} }
return n; return n;
} }
inline void slotFree(Slot* slot, MemoryPool* pool) { inline void slotFree(VariantSlot* var, MemoryPool* pool) {
const JsonVariantData& v = slot->value; const JsonVariantData& v = var->value;
if (v.type == JSON_ARRAY || v.type == JSON_OBJECT) {
for (Slot* s = v.content.asObject.head; s; s = s->next) { switch (v.type) {
slotFree(s, pool); case JSON_ARRAY:
} case JSON_OBJECT:
for (VariantSlot* s = v.content.asObject.head; s; s = s->next) {
slotFree(s, pool);
}
break;
case JSON_OWNED_STRING:
case JSON_OWNED_RAW:
pool->freeString(v.content.asOwnedString);
break;
default:
break;
} }
pool->freeSlot(slot); if (v.keyIsOwned) pool->freeString(var->ownedKey);
pool->freeVariant(var);
} }
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -10,7 +10,6 @@
#include "ArrayFunctions.hpp" #include "ArrayFunctions.hpp"
#include "JsonVariantData.hpp" #include "JsonVariantData.hpp"
#include "ObjectFunctions.hpp" #include "ObjectFunctions.hpp"
#include "Slot.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
@ -24,8 +23,9 @@ inline T variantAsIntegral(const JsonVariantData* var) {
case JSON_NEGATIVE_INTEGER: case JSON_NEGATIVE_INTEGER:
return T(~var->content.asInteger + 1); return T(~var->content.asInteger + 1);
case JSON_LINKED_STRING: case JSON_LINKED_STRING:
case JSON_OWNED_STRING:
return parseInteger<T>(var->content.asString); return parseInteger<T>(var->content.asString);
case JSON_OWNED_STRING:
return parseInteger<T>(var->content.asOwnedString->value);
case JSON_FLOAT: case JSON_FLOAT:
return T(var->content.asFloat); return T(var->content.asFloat);
default: default:
@ -48,8 +48,9 @@ inline T variantAsFloat(const JsonVariantData* var) {
case JSON_NEGATIVE_INTEGER: case JSON_NEGATIVE_INTEGER:
return -static_cast<T>(var->content.asInteger); return -static_cast<T>(var->content.asInteger);
case JSON_LINKED_STRING: case JSON_LINKED_STRING:
case JSON_OWNED_STRING:
return parseFloat<T>(var->content.asString); return parseFloat<T>(var->content.asString);
case JSON_OWNED_STRING:
return parseFloat<T>(var->content.asOwnedString->value);
case JSON_FLOAT: case JSON_FLOAT:
return static_cast<T>(var->content.asFloat); return static_cast<T>(var->content.asFloat);
default: default:
@ -59,11 +60,14 @@ inline T variantAsFloat(const JsonVariantData* var) {
inline const char* variantAsString(const JsonVariantData* var) { inline const char* variantAsString(const JsonVariantData* var) {
if (!var) return 0; if (!var) return 0;
if (var && switch (var->type) {
(var->type == JSON_LINKED_STRING || var->type == JSON_OWNED_STRING)) case JSON_LINKED_STRING:
return var->content.asString; return var->content.asString;
else case JSON_OWNED_STRING:
return 0; return var->content.asOwnedString->value;
default:
return 0;
}
} }
inline JsonArrayData* variantAsArray(JsonVariantData* var) { inline JsonArrayData* variantAsArray(JsonVariantData* var) {
@ -141,11 +145,10 @@ template <typename T>
inline bool variantSetOwnedRaw(JsonVariantData* var, SerializedValue<T> value, inline bool variantSetOwnedRaw(JsonVariantData* var, SerializedValue<T> value,
MemoryPool* pool) { MemoryPool* pool) {
if (!var) return false; if (!var) return false;
const char* dup = makeString(value.data(), value.size()).save(pool); StringSlot* slot = makeString(value.data(), value.size()).save(pool);
if (dup) { if (slot) {
var->type = JSON_OWNED_RAW; var->type = JSON_OWNED_RAW;
var->content.asRaw.data = dup; var->content.asOwnedRaw = slot;
var->content.asRaw.size = value.size();
return true; return true;
} else { } else {
var->type = JSON_NULL; var->type = JSON_NULL;
@ -156,10 +159,10 @@ inline bool variantSetOwnedRaw(JsonVariantData* var, SerializedValue<T> value,
template <typename T> template <typename T>
inline bool variantSetString(JsonVariantData* var, T value, MemoryPool* pool) { inline bool variantSetString(JsonVariantData* var, T value, MemoryPool* pool) {
if (!var) return false; if (!var) return false;
const char* dup = value.save(pool); StringSlot* slot = value.save(pool);
if (dup) { if (slot) {
var->type = JSON_OWNED_STRING; var->type = JSON_OWNED_STRING;
var->content.asString = dup; var->content.asOwnedString = slot;
return true; return true;
} else { } else {
var->type = JSON_NULL; var->type = JSON_NULL;
@ -167,6 +170,13 @@ inline bool variantSetString(JsonVariantData* var, T value, MemoryPool* pool) {
} }
} }
inline bool variantSetOwnedString(JsonVariantData* var, StringSlot* slot) {
if (!var) return false;
var->type = JSON_OWNED_STRING;
var->content.asOwnedString = slot;
return true;
}
inline bool variantSetString(JsonVariantData* var, const char* value) { inline bool variantSetString(JsonVariantData* var, const char* value) {
if (!var) return false; if (!var) return false;
var->type = JSON_LINKED_STRING; var->type = JSON_LINKED_STRING;
@ -174,11 +184,6 @@ inline bool variantSetString(JsonVariantData* var, const char* value) {
return true; return true;
} }
inline bool variantSetString(JsonVariantData* var, const char* value,
MemoryPool* pool) {
return variantSetString(var, makeString(const_cast<char*>(value)), pool);
}
inline void variantSetNull(JsonVariantData* var) { inline void variantSetNull(JsonVariantData* var) {
if (var) var->type = JSON_NULL; if (var) var->type = JSON_NULL;
} }
@ -212,15 +217,17 @@ inline bool variantCopy(JsonVariantData* dst, const JsonVariantData* src,
case JSON_OBJECT: case JSON_OBJECT:
return objectCopy(variantToObject(dst), &src->content.asObject, pool); return objectCopy(variantToObject(dst), &src->content.asObject, pool);
case JSON_OWNED_STRING: case JSON_OWNED_STRING:
return variantSetString(dst, src->content.asString, pool); return variantSetString(
dst, makeString(src->content.asOwnedString->value), pool);
case JSON_OWNED_RAW: case JSON_OWNED_RAW:
return variantSetOwnedRaw( return variantSetOwnedRaw(dst,
dst, serialized(src->content.asOwnedRaw->value,
serialized(const_cast<char*>(src->content.asRaw.data), src->content.asOwnedRaw->size),
src->content.asRaw.size), pool);
pool);
default: default:
*dst = *src; // caution: don't override keyIsOwned
dst->type = src->type;
dst->content = src->content;
return true; return true;
} }
} }
@ -260,11 +267,16 @@ inline bool variantEquals(const JsonVariantData* a, const JsonVariantData* b) {
switch (a->type) { switch (a->type) {
case JSON_LINKED_RAW: case JSON_LINKED_RAW:
case JSON_OWNED_RAW:
case JSON_LINKED_STRING: case JSON_LINKED_STRING:
case JSON_OWNED_STRING:
return !strcmp(a->content.asString, b->content.asString); return !strcmp(a->content.asString, b->content.asString);
case JSON_OWNED_RAW:
case JSON_OWNED_STRING:
return a->content.asOwnedString->size == b->content.asOwnedString->size &&
!memcmp(a->content.asOwnedString->value,
b->content.asOwnedString->value,
a->content.asOwnedString->size);
case JSON_BOOLEAN: case JSON_BOOLEAN:
case JSON_POSITIVE_INTEGER: case JSON_POSITIVE_INTEGER:
case JSON_NEGATIVE_INTEGER: case JSON_NEGATIVE_INTEGER:

View File

@ -18,6 +18,7 @@ template <typename TReader, typename TStringStorage>
class JsonDeserializer { class JsonDeserializer {
typedef typename remove_reference<TStringStorage>::type::StringBuilder typedef typename remove_reference<TStringStorage>::type::StringBuilder
StringBuilder; StringBuilder;
typedef typename StringBuilder::StringType StringType;
public: public:
JsonDeserializer(MemoryPool &memoryPool, TReader reader, JsonDeserializer(MemoryPool &memoryPool, TReader reader,
@ -124,7 +125,7 @@ class JsonDeserializer {
// Read each key value pair // Read each key value pair
for (;;) { for (;;) {
// Parse key // Parse key
StringInMemoryPool key; StringType key;
err = parseKey(key); err = parseKey(key);
if (err) return err; if (err) return err;
@ -165,7 +166,7 @@ class JsonDeserializer {
} }
} }
DeserializationError parseKey(StringInMemoryPool &key) { DeserializationError parseKey(StringType &key) {
if (isQuote(current())) { if (isQuote(current())) {
return parseQuotedString(key); return parseQuotedString(key);
} else { } else {
@ -174,15 +175,15 @@ class JsonDeserializer {
} }
DeserializationError parseStringValue(JsonVariant variant) { DeserializationError parseStringValue(JsonVariant variant) {
StringInMemoryPool value; StringType value;
DeserializationError err = parseQuotedString(value); DeserializationError err = parseQuotedString(value);
if (err) return err; if (err) return err;
variant.set(value); variant.set(value);
return DeserializationError::Ok; return DeserializationError::Ok;
} }
DeserializationError parseQuotedString(StringInMemoryPool &result) { DeserializationError parseQuotedString(StringType &result) {
StringBuilder str = _stringStorage.startString(); StringBuilder builder = _stringStorage.startString();
const char stopChar = current(); const char stopChar = current();
move(); move();
@ -203,16 +204,16 @@ class JsonDeserializer {
move(); move();
} }
str.append(c); builder.append(c);
} }
result = str.complete(); result = builder.complete();
if (result.isNull()) return DeserializationError::NoMemory; if (result.isNull()) return DeserializationError::NoMemory;
return DeserializationError::Ok; return DeserializationError::Ok;
} }
DeserializationError parseNonQuotedString(StringInMemoryPool &result) { DeserializationError parseNonQuotedString(StringType &result) {
StringBuilder str = _stringStorage.startString(); StringBuilder builder = _stringStorage.startString();
char c = current(); char c = current();
if (c == '\0') return DeserializationError::IncompleteInput; if (c == '\0') return DeserializationError::IncompleteInput;
@ -220,14 +221,14 @@ class JsonDeserializer {
if (canBeInNonQuotedString(c)) { // no quotes if (canBeInNonQuotedString(c)) { // no quotes
do { do {
move(); move();
str.append(c); builder.append(c);
c = current(); c = current();
} while (canBeInNonQuotedString(c)); } while (canBeInNonQuotedString(c));
} else { } else {
return DeserializationError::InvalidInput; return DeserializationError::InvalidInput;
} }
result = str.complete(); result = builder.complete();
if (result.isNull()) return DeserializationError::NoMemory; if (result.isNull()) return DeserializationError::NoMemory;
return DeserializationError::Ok; return DeserializationError::Ok;
} }

View File

@ -11,7 +11,7 @@
// Returns the size (in bytes) of an array with n elements. // Returns the size (in bytes) of an array with n elements.
// Can be very handy to determine the size of a StaticMemoryPool. // Can be very handy to determine the size of a StaticMemoryPool.
#define JSON_ARRAY_SIZE(NUMBER_OF_ELEMENTS) \ #define JSON_ARRAY_SIZE(NUMBER_OF_ELEMENTS) \
((NUMBER_OF_ELEMENTS) * sizeof(ARDUINOJSON_NAMESPACE::Slot)) ((NUMBER_OF_ELEMENTS) * sizeof(ARDUINOJSON_NAMESPACE::VariantSlot))
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {

View File

@ -4,7 +4,6 @@
#pragma once #pragma once
#include "Data/Slot.hpp"
#include "Data/SlotFunctions.hpp" #include "Data/SlotFunctions.hpp"
#include "JsonVariant.hpp" #include "JsonVariant.hpp"
@ -30,7 +29,7 @@ class JsonVariantPtr {
class JsonArrayIterator { class JsonArrayIterator {
public: public:
JsonArrayIterator() : _slot(0) {} JsonArrayIterator() : _slot(0) {}
explicit JsonArrayIterator(MemoryPool *memoryPool, Slot *slot) explicit JsonArrayIterator(MemoryPool *memoryPool, VariantSlot *slot)
: _memoryPool(memoryPool), _slot(slot) {} : _memoryPool(memoryPool), _slot(slot) {}
JsonVariant operator*() const { JsonVariant operator*() const {
@ -58,13 +57,13 @@ class JsonArrayIterator {
return *this; return *this;
} }
Slot *internal() { VariantSlot *internal() {
return _slot; return _slot;
} }
private: private:
MemoryPool *_memoryPool; MemoryPool *_memoryPool;
Slot *_slot; VariantSlot *_slot;
}; };
class JsonVariantConstPtr { class JsonVariantConstPtr {
@ -86,7 +85,7 @@ class JsonVariantConstPtr {
class JsonArrayConstIterator { class JsonArrayConstIterator {
public: public:
JsonArrayConstIterator() : _slot(0) {} JsonArrayConstIterator() : _slot(0) {}
explicit JsonArrayConstIterator(const Slot *slot) : _slot(slot) {} explicit JsonArrayConstIterator(const VariantSlot *slot) : _slot(slot) {}
JsonVariantConst operator*() const { JsonVariantConst operator*() const {
return JsonVariantConst(&_slot->value); return JsonVariantConst(&_slot->value);
@ -113,11 +112,11 @@ class JsonArrayConstIterator {
return *this; return *this;
} }
const Slot *internal() { const VariantSlot *internal() {
return _slot; return _slot;
} }
private: private:
const Slot *_slot; const VariantSlot *_slot;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -8,22 +8,18 @@ namespace ARDUINOJSON_NAMESPACE {
class JsonKey { class JsonKey {
public: public:
JsonKey(const Slot* slot) : _slot(slot) {} JsonKey(const VariantSlot* slot) : _slot(slot) {}
operator const char*() const { operator const char*() const {
return c_str(); return c_str();
} }
const char* c_str() const { const char* c_str() const {
return _slot ? _slot->key : 0; return _slot ? slotGetKey(_slot) : 0;
} }
bool isNull() const { bool isNull() const {
return _slot == 0 || _slot->key == 0; return _slot == 0 || _slot->linkedKey == 0;
}
bool isStatic() const {
return _slot ? _slot->value.keyIsStatic : true;
} }
friend bool operator==(JsonKey lhs, const char* rhs) { friend bool operator==(JsonKey lhs, const char* rhs) {
@ -32,6 +28,6 @@ class JsonKey {
} }
private: private:
const Slot* _slot; const VariantSlot* _slot;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -10,7 +10,7 @@
// Returns the size (in bytes) of an object with n elements. // Returns the size (in bytes) of an object with n elements.
// Can be very handy to determine the size of a StaticMemoryPool. // Can be very handy to determine the size of a StaticMemoryPool.
#define JSON_OBJECT_SIZE(NUMBER_OF_ELEMENTS) \ #define JSON_OBJECT_SIZE(NUMBER_OF_ELEMENTS) \
((NUMBER_OF_ELEMENTS) * sizeof(ARDUINOJSON_NAMESPACE::Slot)) ((NUMBER_OF_ELEMENTS) * sizeof(ARDUINOJSON_NAMESPACE::VariantSlot))
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
@ -256,7 +256,11 @@ class JsonObject : public JsonObjectProxy<JsonObjectData>, public Visitable {
return set_impl(makeString(key)); return set_impl(makeString(key));
} }
FORCE_INLINE JsonVariant set(const StringInMemoryPool& key) const { FORCE_INLINE JsonVariant set(StringInMemoryPool key) const {
return set_impl(key);
}
FORCE_INLINE JsonVariant set(ZeroTerminatedRamStringConst key) const {
return set_impl(key); return set_impl(key);
} }

View File

@ -11,7 +11,8 @@ namespace ARDUINOJSON_NAMESPACE {
class JsonPairPtr { class JsonPairPtr {
public: public:
JsonPairPtr(MemoryPool *memoryPool, Slot *slot) : _pair(memoryPool, slot) {} JsonPairPtr(MemoryPool *memoryPool, VariantSlot *slot)
: _pair(memoryPool, slot) {}
const JsonPair *operator->() const { const JsonPair *operator->() const {
return &_pair; return &_pair;
@ -29,7 +30,7 @@ class JsonObjectIterator {
public: public:
JsonObjectIterator() : _slot(0) {} JsonObjectIterator() : _slot(0) {}
explicit JsonObjectIterator(MemoryPool *memoryPool, Slot *slot) explicit JsonObjectIterator(MemoryPool *memoryPool, VariantSlot *slot)
: _memoryPool(memoryPool), _slot(slot) {} : _memoryPool(memoryPool), _slot(slot) {}
JsonPair operator*() const { JsonPair operator*() const {
@ -57,18 +58,18 @@ class JsonObjectIterator {
return *this; return *this;
} }
Slot *internal() { VariantSlot *internal() {
return _slot; return _slot;
} }
private: private:
MemoryPool *_memoryPool; MemoryPool *_memoryPool;
Slot *_slot; VariantSlot *_slot;
}; };
class JsonPairConstPtr { class JsonPairConstPtr {
public: public:
JsonPairConstPtr(const Slot *slot) : _pair(slot) {} JsonPairConstPtr(const VariantSlot *slot) : _pair(slot) {}
const JsonPairConst *operator->() const { const JsonPairConst *operator->() const {
return &_pair; return &_pair;
@ -86,7 +87,7 @@ class JsonObjectConstIterator {
public: public:
JsonObjectConstIterator() : _slot(0) {} JsonObjectConstIterator() : _slot(0) {}
explicit JsonObjectConstIterator(const Slot *slot) : _slot(slot) {} explicit JsonObjectConstIterator(const VariantSlot *slot) : _slot(slot) {}
JsonPairConst operator*() const { JsonPairConst operator*() const {
return JsonPairConst(_slot); return JsonPairConst(_slot);
@ -113,11 +114,11 @@ class JsonObjectConstIterator {
return *this; return *this;
} }
const Slot *internal() { const VariantSlot *internal() {
return _slot; return _slot;
} }
private: private:
const Slot *_slot; const VariantSlot *_slot;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -11,7 +11,7 @@ namespace ARDUINOJSON_NAMESPACE {
// A key value pair for JsonObjectData. // A key value pair for JsonObjectData.
class JsonPair { class JsonPair {
public: public:
JsonPair(MemoryPool* memoryPool, Slot* slot) : _key(slot) { JsonPair(MemoryPool* memoryPool, VariantSlot* slot) : _key(slot) {
if (slot) { if (slot) {
_value = JsonVariant(memoryPool, &slot->value); _value = JsonVariant(memoryPool, &slot->value);
} }
@ -32,7 +32,7 @@ class JsonPair {
class JsonPairConst { class JsonPairConst {
public: public:
JsonPairConst(const Slot* slot) : _key(slot) { JsonPairConst(const VariantSlot* slot) : _key(slot) {
if (slot) { if (slot) {
_value = JsonVariantConst(&slot->value); _value = JsonVariantConst(&slot->value);
} }

View File

@ -204,9 +204,12 @@ class JsonVariant : public JsonVariantProxy<JsonVariantData>,
return variantSetString(_data, value); return variantSetString(_data, value);
} }
// set(const char*); // for internal use only
FORCE_INLINE bool set(StringInMemoryPool value) const { FORCE_INLINE bool set(StringInMemoryPool value) const {
return variantSetString(_data, value, _memoryPool); return variantSetOwnedString(_data, value.slot());
}
FORCE_INLINE bool set(ZeroTerminatedRamStringConst value) const {
return variantSetString(_data, value.c_str());
} }
bool set(JsonVariantConst value) const; bool set(JsonVariantConst value) const;
@ -323,15 +326,4 @@ class JsonVariantConst : public JsonVariantProxy<const JsonVariantData>,
return JsonVariantConst(objectGet(variantAsObject(_data), makeString(key))); return JsonVariantConst(objectGet(variantAsObject(_data), makeString(key)));
} }
}; };
class JsonVariantLocal : public JsonVariant {
public:
explicit JsonVariantLocal(MemoryPool *memoryPool)
: JsonVariant(memoryPool, &_localData) {
_localData.type = JSON_NULL;
}
private:
JsonVariantData _localData;
};
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -89,11 +89,16 @@ inline void JsonVariantConst::accept(Visitor& visitor) const {
return visitor.visitObject(JsonObjectConst(&_data->content.asObject)); return visitor.visitObject(JsonObjectConst(&_data->content.asObject));
case JSON_LINKED_STRING: case JSON_LINKED_STRING:
case JSON_OWNED_STRING:
return visitor.visitString(_data->content.asString); return visitor.visitString(_data->content.asString);
case JSON_LINKED_RAW: case JSON_OWNED_STRING:
return visitor.visitString(_data->content.asOwnedString->value);
case JSON_OWNED_RAW: case JSON_OWNED_RAW:
return visitor.visitRawJson(_data->content.asOwnedRaw->value,
_data->content.asOwnedRaw->size);
case JSON_LINKED_RAW:
return visitor.visitRawJson(_data->content.asRaw.data, return visitor.visitRawJson(_data->content.asRaw.data,
_data->content.asRaw.size); _data->content.asRaw.size);

View File

@ -0,0 +1,28 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#include <stddef.h> // size_t
namespace ARDUINOJSON_NAMESPACE {
inline bool isAligned(void *ptr) {
const size_t mask = sizeof(void *) - 1;
size_t addr = reinterpret_cast<size_t>(ptr);
return (addr & mask) == 0;
}
inline size_t addPadding(size_t bytes) {
const size_t mask = sizeof(void *) - 1;
return (bytes + mask) & ~mask;
}
template <size_t bytes>
struct AddPadding {
static const size_t mask = sizeof(void *) - 1;
static const size_t value = (bytes + mask) & ~mask;
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -5,8 +5,9 @@
#pragma once #pragma once
#include "../Strings/StringInMemoryPool.hpp" #include "../Strings/StringInMemoryPool.hpp"
#include "Alignment.hpp"
#include "MemoryPool.hpp" #include "MemoryPool.hpp"
#include "StringBuilder.hpp" #include "StaticMemoryPool.hpp"
#include <stdlib.h> // malloc, free #include <stdlib.h> // malloc, free
@ -33,18 +34,15 @@ class DefaultAllocator {
template <typename TAllocator> template <typename TAllocator>
class DynamicMemoryPoolBase : public MemoryPool { class DynamicMemoryPoolBase : public MemoryPool {
struct Block; class Block : public StaticMemoryPoolBase {
struct EmptyBlock { public:
Block(char* buf, size_t sz, Block* nxt)
: StaticMemoryPoolBase(buf, sz), next(nxt) {}
Block* next; Block* next;
size_t capacity;
size_t size;
};
struct Block : EmptyBlock {
char data[1];
}; };
public: public:
enum { EmptyBlockSize = sizeof(EmptyBlock) }; enum { EmptyBlockSize = sizeof(Block) };
DynamicMemoryPoolBase(size_t initialSize = ARDUINOJSON_DEFAULT_POOL_SIZE) DynamicMemoryPoolBase(size_t initialSize = ARDUINOJSON_DEFAULT_POOL_SIZE)
: _head(NULL), _nextBlockCapacity(initialSize) {} : _head(NULL), _nextBlockCapacity(initialSize) {}
@ -57,28 +55,82 @@ class DynamicMemoryPoolBase : public MemoryPool {
_nextBlockCapacity = capacity; _nextBlockCapacity = capacity;
} }
// Gets the number of bytes occupied in the memoryPool virtual size_t size() const {
virtual size_t allocated_bytes() const { size_t sum = 0;
size_t total = 0; for (Block* b = _head; b; b = b->next) {
for (const Block* b = _head; b; b = b->next) total += b->size; sum += b->size();
return total; }
return sum;
} }
// Allocates the specified amount of bytes in the memoryPool virtual VariantSlot* allocVariant() {
virtual char* alloc(size_t bytes) { for (Block* b = _head; b; b = b->next) {
alignNextAlloc(); VariantSlot* s = b->allocVariant();
return canAllocInHead(bytes) ? allocInHead(bytes) : allocInNewBlock(bytes); if (s) return s;
}
if (!addNewBlock(sizeof(VariantSlot))) return 0;
return _head->allocVariant();
} }
virtual char* realloc(char* oldPtr, size_t oldSize, size_t newSize) { virtual void freeVariant(VariantSlot* slot) {
size_t n = newSize - oldSize; for (Block* b = _head; b; b = b->next) {
if (canAllocInHead(n)) { if (b->owns(slot)) {
allocInHead(n); b->freeVariant(slot);
return oldPtr; break;
} else { }
char* newPtr = allocInNewBlock(newSize); }
if (oldPtr && newPtr) memcpy(newPtr, oldPtr, oldSize); }
return newPtr;
virtual void freeString(StringSlot* slot) {
for (Block* b = _head; b; b = b->next) {
if (b->owns(slot)) {
b->freeString(slot);
break;
}
}
}
virtual StringSlot* allocFrozenString(size_t n) {
for (Block* b = _head; b; b = b->next) {
StringSlot* s = b->allocFrozenString(n);
if (s) return s;
}
if (!addNewBlock(sizeof(StringSlot) + n)) return 0;
return _head->allocFrozenString(n);
}
virtual StringSlot* allocExpandableString() {
for (Block* b = _head; b; b = b->next) {
StringSlot* s = b->allocExpandableString();
if (s) return s;
}
if (!addNewBlock(sizeof(StringSlot))) return 0;
return _head->allocExpandableString();
}
virtual StringSlot* expandString(StringSlot* oldSlot) {
if (!addNewBlock(sizeof(StringSlot) + oldSlot->size)) return 0;
StringSlot* newSlot = _head->allocExpandableString();
ARDUINOJSON_ASSERT(newSlot->size > oldSlot->size);
memcpy(newSlot->value, oldSlot->value, oldSlot->size);
freeString(oldSlot);
return newSlot;
}
virtual void freezeString(StringSlot* slot, size_t newSize) {
for (Block* b = _head; b; b = b->next) {
if (b->owns(slot)) {
b->freezeString(slot, newSize);
}
} }
} }
@ -87,7 +139,7 @@ class DynamicMemoryPoolBase : public MemoryPool {
void clear() { void clear() {
Block* currentBlock = _head; Block* currentBlock = _head;
while (currentBlock != NULL) { while (currentBlock != NULL) {
_nextBlockCapacity = currentBlock->capacity; _nextBlockCapacity = currentBlock->capacity();
Block* nextBlock = currentBlock->next; Block* nextBlock = currentBlock->next;
_allocator.deallocate(currentBlock); _allocator.deallocate(currentBlock);
currentBlock = nextBlock; currentBlock = nextBlock;
@ -95,40 +147,22 @@ class DynamicMemoryPoolBase : public MemoryPool {
_head = 0; _head = 0;
} }
StringBuilder startString() { size_t blockCount() const {
return StringBuilder(this); size_t sum = 0;
for (Block* b = _head; b; b = b->next) sum++;
return sum;
} }
private: private:
void alignNextAlloc() { bool addNewBlock(size_t minCapacity) {
if (_head) _head->size = this->round_size_up(_head->size);
}
bool canAllocInHead(size_t bytes) const {
return _head != NULL && _head->size + bytes <= _head->capacity;
}
char* allocInHead(size_t bytes) {
char* p = _head->data + _head->size;
_head->size += bytes;
return p;
}
char* allocInNewBlock(size_t bytes) {
size_t capacity = _nextBlockCapacity; size_t capacity = _nextBlockCapacity;
if (bytes > capacity) capacity = bytes; if (minCapacity > capacity) capacity = minCapacity;
if (!addNewBlock(capacity)) return NULL; capacity = addPadding(capacity);
_nextBlockCapacity *= 2; size_t bytes = sizeof(Block) + capacity;
return allocInHead(bytes); char* p = reinterpret_cast<char*>(_allocator.allocate(bytes));
} if (!p) return false;
Block* block = new (p) Block(p + sizeof(Block), capacity, _head);
bool addNewBlock(size_t capacity) { _nextBlockCapacity = capacity * 2;
size_t bytes = EmptyBlockSize + capacity;
Block* block = static_cast<Block*>(_allocator.allocate(bytes));
if (block == NULL) return false;
block->capacity = capacity;
block->size = 0;
block->next = _head;
_head = block; _head = block;
return true; return true;
} }

View File

@ -5,66 +5,30 @@
#pragma once #pragma once
#include <stddef.h> // for size_t #include <stddef.h> // for size_t
#include <stdint.h> // for uint8_t
#include <string.h>
#include "../Configuration.hpp"
#include "../Data/Slot.hpp"
#include "../Polyfills/attributes.hpp" #include "../Polyfills/attributes.hpp"
#include "StringSlot.hpp"
#include "VariantSlot.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
// Handle the memory management (done in derived classes) and calls the parser.
// This abstract class is implemented by StaticMemoryPool which implements a
// fixed memory allocation.
class MemoryPool { class MemoryPool {
public: public:
// Allocates n bytes in the MemoryPool. virtual StringSlot *allocExpandableString() = 0;
// Return a pointer to the allocated memory or NULL if allocation fails. virtual StringSlot *allocFrozenString(size_t) = 0;
virtual char *alloc(size_t size) = 0; virtual StringSlot *expandString(StringSlot *) = 0;
virtual void freezeString(StringSlot *, size_t) = 0;
virtual void freeString(StringSlot *) = 0;
virtual char *realloc(char *oldPtr, size_t oldSize, size_t newSize) = 0; virtual VariantSlot *allocVariant() = 0;
virtual void freeVariant(VariantSlot *) = 0;
Slot *allocSlot() { virtual size_t size() const = 0;
if (_freeSlots) {
Slot *s = _freeSlots;
_freeSlots = s->next;
return s;
}
return reinterpret_cast<Slot *>(alloc(sizeof(Slot)));
}
void freeSlot(Slot *slot) {
slot->next = _freeSlots;
_freeSlots = slot;
}
size_t size() const {
size_t result = allocated_bytes();
for (Slot *s = _freeSlots; s; s = s->next) result -= sizeof(Slot);
return result;
}
protected: protected:
MemoryPool() : _freeSlots(0) {}
// CAUTION: NO VIRTUAL DESTRUCTOR! // CAUTION: NO VIRTUAL DESTRUCTOR!
// If we add a virtual constructor the Arduino compiler will add malloc() // If we add a virtual constructor the Arduino compiler will add malloc()
// and free() to the binary, adding 706 useless bytes. // and free() to the binary, adding 706 useless bytes.
~MemoryPool() {} ~MemoryPool() {}
virtual size_t allocated_bytes() const = 0;
// Preserve aligment if necessary
static FORCE_INLINE size_t round_size_up(size_t bytes) {
#if ARDUINOJSON_ENABLE_ALIGNMENT
const size_t x = sizeof(void *) - 1;
return (bytes + x) & ~x;
#else
return bytes;
#endif
}
private:
Slot *_freeSlots;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -0,0 +1,67 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#include <stddef.h> // for size_t
namespace ARDUINOJSON_NAMESPACE {
template <typename TSlot>
class SlotList {
public:
SlotList() : _head(0) {}
TSlot *pop() {
TSlot *slot = _head;
if (slot) _head = slot->next;
return slot;
}
void push(TSlot *slot) {
slot->next = _head;
_head = slot;
}
bool remove(const TSlot *slot) {
if (_head == slot) {
_head = slot->next;
return true;
}
for (TSlot *s = _head; s; s = s->next) {
if (s->next == slot) {
s->next = slot->next;
return true;
}
}
return false;
}
bool remove(const void *slot) {
return remove(reinterpret_cast<const TSlot *>(slot));
}
template <typename Functor>
void forEach(const Functor &f) {
for (TSlot *s = _head; s; s = s->next) {
f(s);
}
}
size_t size() const {
size_t sum = 0;
for (TSlot *s = _head; s; s = s->next) sum += sizeof(TSlot);
return sum;
}
void clear() {
_head = 0;
}
private:
TSlot *_head;
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -4,74 +4,201 @@
#pragma once #pragma once
#include "../Polyfills/assert.hpp"
#include "../Polyfills/mpl/max.hpp" #include "../Polyfills/mpl/max.hpp"
#include "../Strings/StringInMemoryPool.hpp" #include "../Strings/StringInMemoryPool.hpp"
#include "Alignment.hpp"
#include "MemoryPool.hpp" #include "MemoryPool.hpp"
#include "StringBuilder.hpp" #include "SlotList.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
// _begin _end
// v v
// +-------------+--------------+-----------+
// | strings... | (free) | ...slots |
// +-------------+--------------+-----------+
// ^ ^
// _left _right
class StaticMemoryPoolBase : public MemoryPool { class StaticMemoryPoolBase : public MemoryPool {
class UpdateStringSlotAddress {
public:
UpdateStringSlotAddress(const char* address, size_t offset)
: _address(address), _offset(offset) {}
void operator()(StringSlot* slot) const {
ARDUINOJSON_ASSERT(slot != NULL);
if (slot->value > _address) slot->value -= _offset;
}
private:
const char* _address;
size_t _offset;
};
public: public:
// Gets the capacity of the memoryPool in bytes // Gets the capacity of the memoryPool in bytes
size_t capacity() const { size_t capacity() const {
return _capacity; return size_t(_end - _begin);
} }
// Allocates the specified amount of bytes in the memoryPool virtual size_t size() const {
virtual char* alloc(size_t bytes) { return allocated_bytes() - _freeVariants.size() - _freeStrings.size();
alignNextAlloc();
if (!canAlloc(bytes)) return NULL;
return doAlloc(bytes);
} }
virtual char* realloc(char* oldPtr, size_t oldSize, size_t newSize) { virtual VariantSlot* allocVariant() {
size_t n = newSize - oldSize; VariantSlot* s = _freeVariants.pop();
if (!canAlloc(n)) return NULL; if (s) return s;
doAlloc(n); return s ? s : allocRight<VariantSlot>();
return oldPtr; }
virtual void freeVariant(VariantSlot* slot) {
freeVariantSlot(slot);
compactRightSide();
}
virtual void freeString(StringSlot* slot) {
freeStringSlot(slot);
compactLeftSide(slot->value, slot->size);
compactRightSide();
}
virtual StringSlot* allocFrozenString(size_t n) {
StringSlot* s = allocStringSlot();
if (!s) return 0;
if (!canAlloc(n)) return 0;
s->value = _left;
s->size = n;
_left += n;
_usedString.push(s);
checkInvariants();
return s;
}
virtual StringSlot* allocExpandableString() {
StringSlot* s = allocStringSlot();
if (!s) return 0;
s->value = _left;
s->size = size_t(_right - _left);
_usedString.push(s);
_left = _right;
checkInvariants();
return s;
}
virtual StringSlot* expandString(StringSlot*) {
return 0;
}
virtual void freezeString(StringSlot* slot, size_t newSize) {
_left -= (slot->size - newSize);
slot->size = newSize;
checkInvariants();
} }
// Resets the memoryPool.
// USE WITH CAUTION: this invalidates all previously allocated data
void clear() { void clear() {
_size = 0; _left = _begin;
_right = _end;
_freeVariants.clear();
_freeStrings.clear();
_usedString.clear();
} }
StringBuilder startString() { bool canAlloc(size_t bytes) const {
return StringBuilder(this); return _left + bytes <= _right;
}
bool owns(void* p) const {
return _begin <= p && p < _end;
}
template <typename T>
T* allocRight() {
return reinterpret_cast<T*>(allocRight(sizeof(T)));
}
char* allocRight(size_t bytes) {
if (!canAlloc(bytes)) return 0;
_right -= bytes;
return _right;
}
// Workaround for missing placement new
void* operator new(size_t, void* p) {
return p;
} }
protected: protected:
StaticMemoryPoolBase(char* memoryPool, size_t capa) StaticMemoryPoolBase(char* buffer, size_t capa)
: _buffer(memoryPool), _capacity(capa), _size(0) {} : _begin(buffer),
_left(buffer),
_right(buffer + capa),
_end(buffer + capa) {}
~StaticMemoryPoolBase() {} ~StaticMemoryPoolBase() {}
// Gets the current usage of the memoryPool in bytes // Gets the current usage of the memoryPool in bytes
virtual size_t allocated_bytes() const { size_t allocated_bytes() const {
return _size; return size_t(_left - _begin + _end - _right);
} }
private: private:
void alignNextAlloc() { StringSlot* allocStringSlot() {
_size = round_size_up(_size); StringSlot* s = _freeStrings.pop();
if (s) return s;
return allocRight<StringSlot>();
} }
bool canAlloc(size_t bytes) const { void freeVariantSlot(VariantSlot* slot) {
return _size + bytes <= _capacity; _freeVariants.push(slot);
} }
char* doAlloc(size_t bytes) { void freeStringSlot(StringSlot* slot) {
char* p = &_buffer[_size]; _usedString.remove(slot);
_size += bytes; _freeStrings.push(slot);
return p;
} }
char* _buffer; void compactLeftSide(char* holeAddress, size_t holeSize) {
size_t _capacity; ARDUINOJSON_ASSERT(holeAddress >= _begin);
size_t _size; ARDUINOJSON_ASSERT(holeAddress + holeSize <= _left);
}; char* holeEnd = holeAddress + holeSize;
memmove(holeAddress, // where the hole begun
holeEnd, // where the hole ended
size_t(_left - holeEnd)); // everything after the hole
_left -= holeSize;
_usedString.forEach(UpdateStringSlotAddress(holeAddress, holeSize));
checkInvariants();
}
void compactRightSide() {
loop:
if (_freeStrings.remove(_right)) {
_right += sizeof(StringSlot);
goto loop;
}
if (_freeVariants.remove(_right)) {
_right += sizeof(VariantSlot);
goto loop;
}
checkInvariants();
}
void checkInvariants() {
ARDUINOJSON_ASSERT(_begin <= _left);
ARDUINOJSON_ASSERT(_left <= _right);
ARDUINOJSON_ASSERT(_right <= _end);
}
char *_begin, *_left, *_right, *_end;
SlotList<VariantSlot> _freeVariants;
SlotList<StringSlot> _freeStrings;
SlotList<StringSlot> _usedString;
}; // namespace ARDUINOJSON_NAMESPACE
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
@ -88,7 +215,8 @@ class StaticMemoryPoolBase : public MemoryPool {
// bytes. // bytes.
template <size_t CAPACITY> template <size_t CAPACITY>
class StaticMemoryPool : public StaticMemoryPoolBase { class StaticMemoryPool : public StaticMemoryPoolBase {
static const size_t ACTUAL_CAPACITY = Max<1, CAPACITY>::value; static const size_t ACTUAL_CAPACITY =
AddPadding<Max<1, CAPACITY>::value>::value;
public: public:
explicit StaticMemoryPool() explicit StaticMemoryPool()

View File

@ -11,25 +11,43 @@ namespace ARDUINOJSON_NAMESPACE {
class StringBuilder { class StringBuilder {
public: public:
explicit StringBuilder(MemoryPool* parent) typedef StringInMemoryPool StringType;
: _parent(parent), _start(0), _size(0) {
_start = _parent->alloc(1); explicit StringBuilder(MemoryPool* parent) : _parent(parent), _size(0) {
_slot = _parent->allocExpandableString();
}
void append(const char* s) {
while (*s) append(*s++);
}
void append(const char* s, size_t n) {
while (n-- > 0) append(*s++);
} }
void append(char c) { void append(char c) {
_start = _parent->realloc(_start, _size + 1, _size + 2); if (!_slot) return;
if (_start) _start[_size++] = c;
if (_size >= _slot->size) {
_slot = _parent->expandString(_slot);
if (!_slot) return;
}
_slot->value[_size++] = c;
} }
StringInMemoryPool complete() { StringType complete() {
if (_start) _start[_size] = 0; append('\0');
return _start; if (_slot) {
_parent->freezeString(_slot, _size);
}
return _slot;
} }
private: private:
MemoryPool* _parent; MemoryPool* _parent;
char* _start;
size_t _size; size_t _size;
StringSlot* _slot;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -0,0 +1,20 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#include <stddef.h> // for size_t
#include "../Configuration.hpp"
#define JSON_STRING_SIZE(SIZE) \
(sizeof(ARDUINOJSON_NAMESPACE::StringSlot) + (SIZE))
namespace ARDUINOJSON_NAMESPACE {
struct StringSlot {
char *value;
size_t size;
struct StringSlot *next;
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -4,15 +4,18 @@
#pragma once #pragma once
#include "JsonVariantData.hpp" #include "../Data/JsonVariantData.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
struct Slot { struct VariantSlot {
JsonVariantData value; JsonVariantData value;
struct Slot* next; struct VariantSlot* next;
struct Slot* prev; struct VariantSlot* prev;
const char* key; union {
const char* linkedKey;
StringSlot* ownedKey;
};
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -17,6 +17,7 @@ template <typename TReader, typename TStringStorage>
class MsgPackDeserializer { class MsgPackDeserializer {
typedef typename remove_reference<TStringStorage>::type::StringBuilder typedef typename remove_reference<TStringStorage>::type::StringBuilder
StringBuilder; StringBuilder;
typedef typename StringBuilder::StringType StringType;
public: public:
MsgPackDeserializer(MemoryPool &memoryPool, TReader reader, MsgPackDeserializer(MemoryPool &memoryPool, TReader reader,
@ -220,16 +221,29 @@ class MsgPackDeserializer {
return readString(variant, size); return readString(variant, size);
} }
template <typename T>
DeserializationError readString(StringType &str) {
T size;
if (!readInteger(size)) return DeserializationError::IncompleteInput;
return readString(str, size);
}
DeserializationError readString(JsonVariant variant, size_t n) { DeserializationError readString(JsonVariant variant, size_t n) {
StringBuilder str = _stringStorage.startString(); StringType s;
DeserializationError err = readString(s, n);
if (!err) variant.set(s);
return err;
}
DeserializationError readString(StringType &s, size_t n) {
StringBuilder builder = _stringStorage.startString();
for (; n; --n) { for (; n; --n) {
uint8_t c; uint8_t c;
if (!readBytes(c)) return DeserializationError::IncompleteInput; if (!readBytes(c)) return DeserializationError::IncompleteInput;
str.append(static_cast<char>(c)); builder.append(static_cast<char>(c));
} }
StringInMemoryPool s = str.complete(); s = builder.complete();
if (s.isNull()) return DeserializationError::NoMemory; if (s.isNull()) return DeserializationError::NoMemory;
variant.set(s);
return DeserializationError::Ok; return DeserializationError::Ok;
} }
@ -278,12 +292,11 @@ class MsgPackDeserializer {
if (_nestingLimit == 0) return DeserializationError::TooDeep; if (_nestingLimit == 0) return DeserializationError::TooDeep;
--_nestingLimit; --_nestingLimit;
for (; n; --n) { for (; n; --n) {
JsonVariantLocal key(_memoryPool); StringType key;
DeserializationError err = parse(key); DeserializationError err = parseKey(key);
if (err) return err; if (err) return err;
if (!key.is<char *>()) return DeserializationError::NotSupported;
JsonVariant value = object.set(StringInMemoryPool(key.as<char *>())); JsonVariant value = object.set(key);
if (value.isInvalid()) return DeserializationError::NoMemory; if (value.isInvalid()) return DeserializationError::NoMemory;
err = parse(value); err = parse(value);
@ -293,6 +306,27 @@ class MsgPackDeserializer {
return DeserializationError::Ok; return DeserializationError::Ok;
} }
DeserializationError parseKey(StringType &key) {
uint8_t code;
if (!readByte(code)) return DeserializationError::IncompleteInput;
if ((code & 0xe0) == 0xa0) return readString(key, code & 0x1f);
switch (code) {
case 0xd9:
return readString<uint8_t>(key);
case 0xda:
return readString<uint16_t>(key);
case 0xdb:
return readString<uint32_t>(key);
default:
return DeserializationError::NotSupported;
}
}
MemoryPool *_memoryPool; MemoryPool *_memoryPool;
TReader _reader; TReader _reader;
TStringStorage _stringStorage; TStringStorage _stringStorage;

View File

@ -0,0 +1,12 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#ifdef ARDUINOJSON_DEBUG
#include <assert.h>
#define ARDUINOJSON_ASSERT(X) assert(X)
#else
#define ARDUINOJSON_ASSERT(X) ((void)0)
#endif

View File

@ -11,13 +11,15 @@ class StringMover {
public: public:
class StringBuilder { class StringBuilder {
public: public:
typedef ZeroTerminatedRamStringConst StringType;
StringBuilder(TChar** ptr) : _writePtr(ptr), _startPtr(*ptr) {} StringBuilder(TChar** ptr) : _writePtr(ptr), _startPtr(*ptr) {}
void append(char c) { void append(char c) {
*(*_writePtr)++ = TChar(c); *(*_writePtr)++ = TChar(c);
} }
StringInMemoryPool complete() const { StringType complete() const {
*(*_writePtr)++ = 0; *(*_writePtr)++ = 0;
return reinterpret_cast<const char*>(_startPtr); return reinterpret_cast<const char*>(_startPtr);
} }

View File

@ -13,12 +13,12 @@ class ArduinoString {
ArduinoString(const ::String& str) : _str(&str) {} ArduinoString(const ::String& str) : _str(&str) {}
template <typename TMemoryPool> template <typename TMemoryPool>
const char* save(TMemoryPool* memoryPool) const { StringSlot* save(TMemoryPool* memoryPool) const {
if (isNull()) return NULL; if (isNull()) return NULL;
size_t n = _str->length() + 1; size_t n = _str->length() + 1;
void* dup = memoryPool->alloc(n); StringSlot* slot = memoryPool->allocFrozenString(n);
if (dup != NULL) memcpy(dup, _str->c_str(), n); if (slot) memcpy(slot->value, _str->c_str(), n);
return static_cast<const char*>(dup); return slot;
} }
bool isNull() const { bool isNull() const {

View File

@ -22,11 +22,11 @@ class FixedSizeFlashString {
} }
template <typename TMemoryPool> template <typename TMemoryPool>
const char* save(TMemoryPool* memoryPool) const { StringSlot* save(TMemoryPool* memoryPool) const {
if (!_str) return NULL; if (!_str) return NULL;
void* dup = memoryPool->alloc(_size); StringSlot* slot = memoryPool->allocFrozenString(_size);
if (dup != NULL) memcpy_P(dup, (const char*)_str, _size); if (!slot) memcpy_P(slot->value, (const char*)_str, _size);
return static_cast<const char*>(dup); return slot;
} }
size_t size() const { size_t size() const {

View File

@ -23,12 +23,11 @@ class FixedSizeRamString {
} }
template <typename TMemoryPool> template <typename TMemoryPool>
const char* save(TMemoryPool* memoryPool) const { StringSlot* save(TMemoryPool* memoryPool) const {
if (!_str) return NULL; if (!_str) return NULL;
void* dup = memoryPool->alloc(_size); StringSlot* slot = memoryPool->allocFrozenString(_size);
if (!dup) return NULL; if (slot) memcpy(slot->value, _str, _size);
memcpy(dup, _str, _size); return slot;
return static_cast<const char*>(dup);
} }
size_t size() const { size_t size() const {

View File

@ -13,11 +13,11 @@ class StlString {
StlString(const std::string& str) : _str(&str) {} StlString(const std::string& str) : _str(&str) {}
template <typename TMemoryPool> template <typename TMemoryPool>
const char* save(TMemoryPool* memoryPool) const { StringSlot* save(TMemoryPool* memoryPool) const {
size_t n = _str->length() + 1; size_t n = _str->length() + 1;
void* dup = memoryPool->alloc(n); StringSlot* slot = memoryPool->allocFrozenString(n);
if (dup != NULL) memcpy(dup, _str->c_str(), n); if (slot) memcpy(slot->value, _str->c_str(), n);
return static_cast<const char*>(dup); return slot;
} }
bool isNull() const { bool isNull() const {

View File

@ -4,13 +4,45 @@
#pragma once #pragma once
#include "ZeroTerminatedRamStringConst.hpp" #include <string.h>
#include "../Memory/StringSlot.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
class StringInMemoryPool : public ZeroTerminatedRamStringConst { class StringInMemoryPool {
public: public:
StringInMemoryPool(const char* str = 0) : ZeroTerminatedRamStringConst(str) {} StringInMemoryPool(StringSlot* s = 0) : _slot(s) {}
bool equals(const char* expected) const {
if (!_slot) return expected == 0;
const char* actual = _slot->value;
if (actual == expected) return true;
return strcmp(actual, expected) == 0;
}
bool isNull() const {
return !_slot;
}
template <typename TMemoryPool>
StringSlot* save(TMemoryPool*) const {
return _slot;
}
size_t size() const {
return _slot->size;
}
StringSlot* slot() const {
return _slot;
}
const char* c_str() const {
return _slot->value;
}
protected:
StringSlot* _slot;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -21,12 +21,12 @@ class ZeroTerminatedFlashString {
} }
template <typename TMemoryPool> template <typename TMemoryPool>
const char* save(TMemoryPool* memoryPool) const { StringSlot* save(TMemoryPool* memoryPool) const {
if (!_str) return NULL; if (!_str) return NULL;
size_t n = size() + 1; // copy the terminator size_t n = size() + 1; // copy the terminator
void* dup = memoryPool->alloc(n); StringSlot* slot = memoryPool->allocFrozenString(n);
if (dup != NULL) memcpy_P(dup, (const char*)_str, n); if (slot) memcpy_P(slot->value, reinterpret_cast<const char*>(_str), n);
return static_cast<const char*>(dup); return slot;
} }
size_t size() const { size_t size() const {

View File

@ -14,13 +14,12 @@ class ZeroTerminatedRamString : public ZeroTerminatedRamStringConst {
: ZeroTerminatedRamStringConst(str) {} : ZeroTerminatedRamStringConst(str) {}
template <typename TMemoryPool> template <typename TMemoryPool>
const char* save(TMemoryPool* memoryPool) const { StringSlot* save(TMemoryPool* memoryPool) const {
if (!_str) return NULL; if (!_str) return NULL;
size_t n = size() + 1; size_t n = size() + 1;
void* dup = memoryPool->alloc(n); StringSlot* slot = memoryPool->allocFrozenString(n);
if (!dup) return NULL; if (slot) memcpy(slot->value, _str, n);
memcpy(dup, _str, n); return slot;
return static_cast<const char*>(dup);
} }
}; };

View File

@ -11,7 +11,7 @@ namespace ARDUINOJSON_NAMESPACE {
class ZeroTerminatedRamStringConst { class ZeroTerminatedRamStringConst {
public: public:
ZeroTerminatedRamStringConst(const char* str) : _str(str) {} ZeroTerminatedRamStringConst(const char* str = 0) : _str(str) {}
bool equals(const char* expected) const { bool equals(const char* expected) const {
const char* actual = _str; const char* actual = _str;
@ -23,15 +23,19 @@ class ZeroTerminatedRamStringConst {
return !_str; return !_str;
} }
template <typename TMemoryPool> // template <typename TMemoryPool>
const char* save(TMemoryPool*) const { // const char* save(TMemoryPool*) const {
return _str; // return _str;
} // }
size_t size() const { size_t size() const {
return strlen(_str); return strlen(_str);
} }
const char* c_str() const {
return _str;
}
protected: protected:
const char* _str; const char* _str;
}; };

View File

@ -2,12 +2,14 @@
# Copyright Benoit Blanchon 2014-2018 # Copyright Benoit Blanchon 2014-2018
# MIT License # MIT License
add_executable(DynamicMemoryPoolTests add_executable(DynamicMemoryPoolTests
alloc.cpp allocString.cpp
allocSlot.cpp allocVariant.cpp
blocks.cpp
clear.cpp
no_memory.cpp no_memory.cpp
size.cpp size.cpp
startString.cpp StringBuilder.cpp
) )
target_link_libraries(DynamicMemoryPoolTests catch) target_link_libraries(DynamicMemoryPoolTests catch)

View File

@ -0,0 +1,41 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <ArduinoJson/Memory/StringBuilder.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("DynamicMemoryPool::startString()") {
SECTION("WorksWhenBufferIsBigEnough") {
DynamicMemoryPool memoryPool(JSON_STRING_SIZE(8));
StringBuilder str(&memoryPool);
str.append("abcdefg");
REQUIRE(memoryPool.blockCount() == 1);
REQUIRE(str.complete().equals("abcdefg"));
}
SECTION("GrowsWhenBufferIsTooSmall") {
DynamicMemoryPool memoryPool(JSON_STRING_SIZE(8));
StringBuilder str(&memoryPool);
str.append("abcdefghABC");
REQUIRE(memoryPool.blockCount() == 2);
REQUIRE(str.complete().equals("abcdefghABC"));
}
SECTION("SizeIncreases") {
DynamicMemoryPool memoryPool(JSON_STRING_SIZE(5));
StringBuilder str(&memoryPool);
str.append('h');
str.complete();
REQUIRE(JSON_STRING_SIZE(2) == memoryPool.size());
}
}

View File

@ -1,77 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson.h>
#include <catch.hpp>
#include <sstream>
using namespace ARDUINOJSON_NAMESPACE;
static bool isAligned(void* ptr) {
const size_t mask = sizeof(void*) - 1;
size_t addr = reinterpret_cast<size_t>(ptr);
return (addr & mask) == 0;
}
std::stringstream allocatorLog;
struct SpyingAllocator : DefaultAllocator {
void* allocate(size_t n) {
allocatorLog << "A" << (n - DynamicMemoryPool::EmptyBlockSize);
return DefaultAllocator::allocate(n);
}
void deallocate(void* p) {
allocatorLog << "F";
return DefaultAllocator::deallocate(p);
}
};
TEST_CASE("DynamicMemoryPool::alloc()") {
SECTION("Returns different pointers") {
DynamicMemoryPool memoryPool;
void* p1 = memoryPool.alloc(1);
void* p2 = memoryPool.alloc(2);
REQUIRE(p1 != p2);
}
SECTION("Doubles allocation size when full") {
allocatorLog.str("");
{
DynamicMemoryPoolBase<SpyingAllocator> memoryPool(1);
memoryPool.alloc(1);
memoryPool.alloc(1);
}
REQUIRE(allocatorLog.str() == "A1A2FF");
}
SECTION("Resets allocation size after clear()") {
allocatorLog.str("");
{
DynamicMemoryPoolBase<SpyingAllocator> memoryPool(1);
memoryPool.alloc(1);
memoryPool.alloc(1);
memoryPool.clear();
memoryPool.alloc(1);
}
REQUIRE(allocatorLog.str() == "A1A2FFA1F");
}
SECTION("Makes a big allocation when needed") {
allocatorLog.str("");
{
DynamicMemoryPoolBase<SpyingAllocator> memoryPool(1);
memoryPool.alloc(42);
}
REQUIRE(allocatorLog.str() == "A42F");
}
SECTION("Alignment") {
// make room for two but not three
DynamicMemoryPool tinyBuf(2 * sizeof(void*) + 1);
REQUIRE(isAligned(tinyBuf.alloc(1))); // this on is aligned by design
REQUIRE(isAligned(tinyBuf.alloc(1))); // this one fits in the first block
REQUIRE(isAligned(tinyBuf.alloc(1))); // this one requires a new block
}
}

View File

@ -1,27 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("DynamicMemoryPool::allocSlot()") {
DynamicMemoryPool memoryPool;
SECTION("Returns different pointer") {
Slot* s1 = memoryPool.allocSlot();
Slot* s2 = memoryPool.allocSlot();
REQUIRE(s1 != s2);
}
SECTION("Returns same pointer after freeSlot()") {
Slot* s1 = memoryPool.allocSlot();
memoryPool.freeSlot(s1);
Slot* s2 = memoryPool.allocSlot();
REQUIRE(s1 == s2);
}
}

View File

@ -0,0 +1,28 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <catch.hpp>
#include <sstream>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("DynamicMemoryPool::allocFrozenString()") {
DynamicMemoryPool pool;
SECTION("Returns different pointers") {
StringSlot* a = pool.allocFrozenString(1);
StringSlot* b = pool.allocFrozenString(2);
REQUIRE(a != b);
REQUIRE(a->value != b->value);
}
SECTION("Returns same slot after freeString") {
StringSlot* a = pool.allocFrozenString(1);
pool.freeString(a);
StringSlot* b = pool.allocFrozenString(2);
REQUIRE(a == b);
REQUIRE(a->value == b->value);
}
}

View File

@ -0,0 +1,41 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("DynamicMemoryPool::allocVariant()") {
DynamicMemoryPool memoryPool;
SECTION("Returns different pointer") {
VariantSlot* s1 = memoryPool.allocVariant();
VariantSlot* s2 = memoryPool.allocVariant();
REQUIRE(s1 != s2);
}
SECTION("Returns same pointer after freeSlot()") {
VariantSlot* s1 = memoryPool.allocVariant();
memoryPool.freeVariant(s1);
VariantSlot* s2 = memoryPool.allocVariant();
REQUIRE(s1 == s2);
}
SECTION("Returns aligned pointers") {
// make room for two but not three
// pass an uneven capacity
DynamicMemoryPool pool(2 * sizeof(VariantSlot) + 1);
REQUIRE(isAligned(pool.allocVariant()));
REQUIRE(isAligned(pool.allocVariant()));
REQUIRE(pool.blockCount() == 1);
REQUIRE(isAligned(pool.allocVariant()));
REQUIRE(isAligned(pool.allocVariant()));
REQUIRE(pool.blockCount() == 2);
}
}

View File

@ -0,0 +1,69 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <catch.hpp>
#include <sstream>
using namespace ARDUINOJSON_NAMESPACE;
std::stringstream allocatorLog;
struct SpyingAllocator : DefaultAllocator {
void* allocate(size_t n) {
allocatorLog << "A" << (n - DynamicMemoryPool::EmptyBlockSize);
return DefaultAllocator::allocate(n);
}
void deallocate(void* p) {
allocatorLog << "F";
return DefaultAllocator::deallocate(p);
}
};
TEST_CASE("DynamicMemoryPool blocks") {
SECTION("Doubles allocation size when full") {
allocatorLog.str("");
{
DynamicMemoryPoolBase<SpyingAllocator> memoryPool(sizeof(VariantSlot));
memoryPool.allocVariant();
memoryPool.allocVariant();
}
std::stringstream expected;
expected << "A" << sizeof(VariantSlot) // block 1
<< "A" << 2 * sizeof(VariantSlot) // block 2, twice bigger
<< "FF";
REQUIRE(allocatorLog.str() == expected.str());
}
SECTION("Resets allocation size after clear()") {
allocatorLog.str("");
{
DynamicMemoryPoolBase<SpyingAllocator> memoryPool(sizeof(VariantSlot));
memoryPool.allocVariant();
memoryPool.allocVariant();
memoryPool.clear();
memoryPool.allocVariant();
}
std::stringstream expected;
expected << "A" << sizeof(VariantSlot) // block 1
<< "A" << 2 * sizeof(VariantSlot) // block 2, twice bigger
<< "FF" // clear
<< "A" << sizeof(VariantSlot) // block 1
<< "F";
REQUIRE(allocatorLog.str() == expected.str());
}
/* SECTION("Alloc big block for large string") {
allocatorLog.str("");
{
DynamicMemoryPoolBase<SpyingAllocator> memoryPool(1);
memoryPool.allocString(42);
}
std::stringstream expected;
expected << "A" << JSON_STRING_SIZE(42) // block 1
<< "F";
REQUIRE(allocatorLog.str() == expected.str());
}*/
}

View File

@ -0,0 +1,47 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("StaticMemoryPool::clear()") {
DynamicMemoryPool memoryPool;
SECTION("Discards allocated variants") {
memoryPool.allocVariant();
REQUIRE(memoryPool.size() > 0);
memoryPool.clear();
CHECK(memoryPool.size() == 0);
}
SECTION("Discards allocated strings") {
memoryPool.allocFrozenString(10);
REQUIRE(memoryPool.size() > 0);
memoryPool.clear();
CHECK(memoryPool.size() == 0);
}
SECTION("Purges variant cache") {
memoryPool.freeVariant(memoryPool.allocVariant());
REQUIRE(memoryPool.size() == 0);
memoryPool.clear();
CHECK(memoryPool.size() == 0);
}
SECTION("Purges string cache") {
memoryPool.freeString(memoryPool.allocFrozenString(10));
// REQUIRE(memoryPool.size() == 0);
memoryPool.clear();
CHECK(memoryPool.size() == 0);
}
}

View File

@ -2,7 +2,8 @@
// Copyright Benoit Blanchon 2014-2018 // Copyright Benoit Blanchon 2014-2018
// MIT License // MIT License
#include <ArduinoJson.h> #include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <ArduinoJson/Memory/StringBuilder.hpp>
#include <catch.hpp> #include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE; using namespace ARDUINOJSON_NAMESPACE;
@ -32,8 +33,8 @@ TEST_CASE("DynamicMemoryPool no memory") {
// REQUIRE(err != DeserializationError::Ok); // REQUIRE(err != DeserializationError::Ok);
// } // }
SECTION("startString()") { SECTION("StringBuilder returns null") {
StringBuilder str = _memoryPool.startString(); StringBuilder str(&_memoryPool);
str.append('!'); str.append('!');
REQUIRE(str.complete().isNull()); REQUIRE(str.complete().isNull());
} }

View File

@ -14,35 +14,43 @@ TEST_CASE("DynamicMemoryPool::size()") {
REQUIRE(0 == memoryPool.size()); REQUIRE(0 == memoryPool.size());
} }
SECTION("Increases after alloc()") { SECTION("Increases after allocExpandableString()") {
memoryPool.alloc(1); StringSlot* a = memoryPool.allocExpandableString();
REQUIRE(1U <= memoryPool.size()); memoryPool.freezeString(a, 1);
memoryPool.alloc(1); REQUIRE(memoryPool.size() == JSON_STRING_SIZE(1));
REQUIRE(2U <= memoryPool.size());
StringSlot* b = memoryPool.allocExpandableString();
memoryPool.freezeString(b, 1);
REQUIRE(memoryPool.size() == 2 * JSON_STRING_SIZE(1));
} }
SECTION("Goes back to 0 after clear()") { SECTION("Increases after allocVariant()") {
memoryPool.alloc(1); memoryPool.allocVariant();
memoryPool.clear(); REQUIRE(sizeof(VariantSlot) == memoryPool.size());
memoryPool.allocVariant();
REQUIRE(2 * sizeof(VariantSlot) == memoryPool.size());
}
SECTION("Decreases after freeVariant()") {
VariantSlot* a = memoryPool.allocVariant();
VariantSlot* b = memoryPool.allocVariant();
memoryPool.freeVariant(b);
REQUIRE(sizeof(VariantSlot) == memoryPool.size());
memoryPool.freeVariant(a);
REQUIRE(0 == memoryPool.size()); REQUIRE(0 == memoryPool.size());
} }
SECTION("Increases after allocSlot()") { SECTION("Decreases after freeString()") {
memoryPool.allocSlot(); StringSlot* a = memoryPool.allocFrozenString(5);
REQUIRE(sizeof(Slot) == memoryPool.size()); StringSlot* b = memoryPool.allocFrozenString(6);
memoryPool.allocSlot(); memoryPool.freeString(b);
REQUIRE(2 * sizeof(Slot) == memoryPool.size()); REQUIRE(memoryPool.size() == JSON_STRING_SIZE(5));
}
SECTION("Decreases after freeSlot()") { memoryPool.freeString(a);
Slot* s1 = memoryPool.allocSlot(); REQUIRE(memoryPool.size() == 0);
Slot* s2 = memoryPool.allocSlot();
memoryPool.freeSlot(s1);
REQUIRE(sizeof(Slot) == memoryPool.size());
memoryPool.freeSlot(s2);
REQUIRE(0 == memoryPool.size());
} }
} }

View File

@ -1,46 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("DynamicMemoryPool::startString()") {
SECTION("WorksWhenBufferIsBigEnough") {
DynamicMemoryPool memoryPool(6);
StringBuilder str = memoryPool.startString();
str.append('h');
str.append('e');
str.append('l');
str.append('l');
str.append('o');
REQUIRE(str.complete().equals("hello"));
}
SECTION("GrowsWhenBufferIsTooSmall") {
DynamicMemoryPool memoryPool(5);
StringBuilder str = memoryPool.startString();
str.append('h');
str.append('e');
str.append('l');
str.append('l');
str.append('o');
REQUIRE(str.complete().equals("hello"));
}
SECTION("SizeIncreases") {
DynamicMemoryPool memoryPool(5);
StringBuilder str = memoryPool.startString();
str.append('h');
str.complete();
REQUIRE(2 == memoryPool.size());
}
}

View File

@ -102,13 +102,13 @@ TEST_CASE("JsonArray::add()") {
SECTION("should duplicate char*") { SECTION("should duplicate char*") {
array.add(const_cast<char*>("world")); array.add(const_cast<char*>("world"));
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 6; const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(6);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
SECTION("should duplicate std::string") { SECTION("should duplicate std::string") {
array.add(std::string("world")); array.add(std::string("world"));
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 6; const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(6);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
@ -120,19 +120,19 @@ TEST_CASE("JsonArray::add()") {
SECTION("should duplicate serialized(char*)") { SECTION("should duplicate serialized(char*)") {
array.add(serialized(const_cast<char*>("{}"))); array.add(serialized(const_cast<char*>("{}")));
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 2; const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(2);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
SECTION("should duplicate serialized(std::string)") { SECTION("should duplicate serialized(std::string)") {
array.add(serialized(std::string("{}"))); array.add(serialized(std::string("{}")));
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 2; const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(2);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
SECTION("should duplicate serialized(std::string)") { SECTION("should duplicate serialized(std::string)") {
array.add(serialized(std::string("\0XX", 3))); array.add(serialized(std::string("\0XX", 3)));
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 3; const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(3);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
} }

View File

@ -105,13 +105,13 @@ TEST_CASE("JsonArray::operator[]") {
SECTION("should duplicate char*") { SECTION("should duplicate char*") {
array[0] = const_cast<char*>("world"); array[0] = const_cast<char*>("world");
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 6; const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(6);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
SECTION("should duplicate std::string") { SECTION("should duplicate std::string") {
array[0] = std::string("world"); array[0] = std::string("world");
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 6; const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(6);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }

View File

@ -16,7 +16,7 @@ TEST_CASE("deserialize JSON array with a StaticJsonDocument") {
} }
SECTION("TooSmallBufferForArrayWithOneValue") { SECTION("TooSmallBufferForArrayWithOneValue") {
StaticJsonDocument<JSON_ARRAY_SIZE(1) - 1> doc; StaticJsonDocument<JSON_ARRAY_SIZE(0)> doc;
char input[] = "[1]"; char input[] = "[1]";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
@ -34,7 +34,7 @@ TEST_CASE("deserialize JSON array with a StaticJsonDocument") {
} }
SECTION("TooSmallBufferForArrayWithNestedObject") { SECTION("TooSmallBufferForArrayWithNestedObject") {
StaticJsonDocument<JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(0) - 1> doc; StaticJsonDocument<JSON_ARRAY_SIZE(0) + JSON_OBJECT_SIZE(0)> doc;
char input[] = "[{}]"; char input[] = "[{}]";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
@ -56,7 +56,7 @@ TEST_CASE("deserialize JSON array with a StaticJsonDocument") {
deserializeJson(doc, " [ \"1234567\" ] "); deserializeJson(doc, " [ \"1234567\" ] ");
REQUIRE(JSON_ARRAY_SIZE(1) + sizeof("1234567") == doc.memoryUsage()); REQUIRE(JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(8) == doc.memoryUsage());
// note: we use a string of 8 bytes to be sure that the StaticMemoryPool // note: we use a string of 8 bytes to be sure that the StaticMemoryPool
// will not insert bytes to enforce alignement // will not insert bytes to enforce alignement
} }

View File

@ -16,7 +16,7 @@ TEST_CASE("deserialize JSON object with StaticJsonDocument") {
} }
SECTION("TooSmallBufferForObjectWithOneValue") { SECTION("TooSmallBufferForObjectWithOneValue") {
StaticJsonDocument<JSON_OBJECT_SIZE(1) - 1> doc; StaticJsonDocument<JSON_OBJECT_SIZE(0)> doc;
char input[] = "{\"a\":1}"; char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
@ -34,7 +34,7 @@ TEST_CASE("deserialize JSON object with StaticJsonDocument") {
} }
SECTION("TooSmallBufferForObjectWithNestedObject") { SECTION("TooSmallBufferForObjectWithNestedObject") {
StaticJsonDocument<JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(0) - 1> doc; StaticJsonDocument<JSON_OBJECT_SIZE(0) + JSON_ARRAY_SIZE(0)> doc;
char input[] = "{\"a\":[]}"; char input[] = "{\"a\":[]}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);

View File

@ -94,35 +94,36 @@ TEST_CASE("DynamicJsonDocument") {
} }
SECTION("memoryUsage()") { SECTION("memoryUsage()") {
typedef ARDUINOJSON_NAMESPACE::Slot Slot;
SECTION("Increases after adding value to array") { SECTION("Increases after adding value to array") {
JsonArray arr = doc.to<JsonArray>(); JsonArray arr = doc.to<JsonArray>();
REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0));
arr.add(42); arr.add(42);
REQUIRE(sizeof(Slot) == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1));
arr.add(43); arr.add(43);
REQUIRE(2 * sizeof(Slot) == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(2));
} }
SECTION("Decreases after remove value from array") { SECTION("Decreases after removing value from array") {
JsonArray arr = doc.to<JsonArray>(); JsonArray arr = doc.to<JsonArray>();
arr.add(42); arr.add(42);
arr.add(43); arr.add(43);
REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(2));
arr.remove(1); arr.remove(1);
REQUIRE(sizeof(Slot) == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1));
arr.remove(0); arr.remove(0);
REQUIRE(0 == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0));
} }
SECTION("Increases after adding value to object") { SECTION("Increases after adding value to object") {
JsonObject obj = doc.to<JsonObject>(); JsonObject obj = doc.to<JsonObject>();
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
obj["a"] = 1; obj["a"] = 1;
REQUIRE(sizeof(Slot) == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1));
obj["b"] = 2; obj["b"] = 2;
REQUIRE(2 * sizeof(Slot) == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2));
} }
SECTION("Decreases after removing value from object") { SECTION("Decreases after removing value from object") {
@ -130,10 +131,11 @@ TEST_CASE("DynamicJsonDocument") {
obj["a"] = 1; obj["a"] = 1;
obj["b"] = 2; obj["b"] = 2;
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2));
obj.remove("a"); obj.remove("a");
REQUIRE(sizeof(Slot) == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1));
obj.remove("b"); obj.remove("b");
REQUIRE(0 == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
} }
SECTION("Decreases after removing nested object from array") { SECTION("Decreases after removing nested object from array") {
@ -141,8 +143,9 @@ TEST_CASE("DynamicJsonDocument") {
JsonObject obj = arr.createNestedObject(); JsonObject obj = arr.createNestedObject();
obj["hello"] = "world"; obj["hello"] = "world";
REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1));
arr.remove(0); arr.remove(0);
REQUIRE(0 == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0));
} }
SECTION("Decreases after removing nested array from object") { SECTION("Decreases after removing nested array from object") {
@ -150,8 +153,9 @@ TEST_CASE("DynamicJsonDocument") {
JsonArray arr = obj.createNestedArray("hello"); JsonArray arr = obj.createNestedArray("hello");
arr.add("world"); arr.add("world");
REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1));
obj.remove("hello"); obj.remove("hello");
REQUIRE(0 == doc.memoryUsage()); REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
} }
} }
} }

View File

@ -19,18 +19,47 @@ TEST_CASE("JsonObject::remove()") {
obj.remove("a"); obj.remove("a");
serializeJson(obj, result); serializeJson(obj, result);
REQUIRE("{\"b\":1,\"c\":2}" == result); REQUIRE("{\"b\":1,\"c\":2}" == result);
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2));
} }
SECTION("Remove middle") { SECTION("Remove middle") {
obj.remove("b"); obj.remove("b");
serializeJson(obj, result); serializeJson(obj, result);
REQUIRE("{\"a\":0,\"c\":2}" == result); REQUIRE("{\"a\":0,\"c\":2}" == result);
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2));
} }
SECTION("Remove last") { SECTION("Remove last") {
obj.remove("c"); obj.remove("c");
serializeJson(obj, result); serializeJson(obj, result);
REQUIRE("{\"a\":0,\"b\":1}" == result); REQUIRE("{\"a\":0,\"b\":1}" == result);
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2));
}
SECTION("Release value string memory") {
obj["c"] = std::string("Copy me!");
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(3) + JSON_STRING_SIZE(9));
obj.remove("c");
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2));
}
SECTION("Release key string memory") {
obj[std::string("Copy me!")] = 42;
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(4) + JSON_STRING_SIZE(9));
obj.remove("Copy me!");
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(3));
}
SECTION("Release raw string memory") {
obj["c"] = serialized(std::string("Copy me!"));
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(3) + JSON_STRING_SIZE(8));
obj.remove("c");
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2));
} }
} }

View File

@ -107,37 +107,37 @@ TEST_CASE("JsonObject::operator[]") {
SECTION("should duplicate char* value") { SECTION("should duplicate char* value") {
obj["hello"] = const_cast<char*>("world"); obj["hello"] = const_cast<char*>("world");
const size_t expectedSize = JSON_OBJECT_SIZE(1) + 6; const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(6);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
SECTION("should duplicate char* key") { SECTION("should duplicate char* key") {
obj[const_cast<char*>("hello")] = "world"; obj[const_cast<char*>("hello")] = "world";
const size_t expectedSize = JSON_OBJECT_SIZE(1) + 6; const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(6);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
SECTION("should duplicate char* key&value") { SECTION("should duplicate char* key&value") {
obj[const_cast<char*>("hello")] = const_cast<char*>("world"); obj[const_cast<char*>("hello")] = const_cast<char*>("world");
const size_t expectedSize = JSON_OBJECT_SIZE(1) + 12; const size_t expectedSize = JSON_OBJECT_SIZE(1) + 2 * JSON_STRING_SIZE(6);
REQUIRE(expectedSize <= doc.memoryUsage()); REQUIRE(expectedSize <= doc.memoryUsage());
} }
SECTION("should duplicate std::string value") { SECTION("should duplicate std::string value") {
obj["hello"] = std::string("world"); obj["hello"] = std::string("world");
const size_t expectedSize = JSON_OBJECT_SIZE(1) + 6; const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(6);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
SECTION("should duplicate std::string key") { SECTION("should duplicate std::string key") {
obj[std::string("hello")] = "world"; obj[std::string("hello")] = "world";
const size_t expectedSize = JSON_OBJECT_SIZE(1) + 6; const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(6);
REQUIRE(expectedSize == doc.memoryUsage()); REQUIRE(expectedSize == doc.memoryUsage());
} }
SECTION("should duplicate std::string key&value") { SECTION("should duplicate std::string key&value") {
obj[std::string("hello")] = std::string("world"); obj[std::string("hello")] = std::string("world");
const size_t expectedSize = JSON_OBJECT_SIZE(1) + 12; const size_t expectedSize = JSON_OBJECT_SIZE(1) + 2 * JSON_STRING_SIZE(6);
REQUIRE(expectedSize <= doc.memoryUsage()); REQUIRE(expectedSize <= doc.memoryUsage());
} }

View File

@ -108,6 +108,16 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<std::string>() == std::string("hello")); REQUIRE(variant.as<std::string>() == std::string("hello"));
} }
SECTION("set(std::string(\"4.2\"))") {
variant.set(std::string("4.2"));
REQUIRE(variant.as<bool>() == true);
REQUIRE(variant.as<long>() == 4L);
REQUIRE(variant.as<double>() == 4.2);
REQUIRE(variant.as<char*>() == std::string("4.2"));
REQUIRE(variant.as<std::string>() == std::string("4.2"));
}
SECTION("set(\"true\")") { SECTION("set(\"true\")") {
variant.set("true"); variant.set("true");

View File

@ -216,7 +216,7 @@ TEST_CASE("JsonVariant comparisons") {
JsonVariant variant2 = doc2.to<JsonVariant>(); JsonVariant variant2 = doc2.to<JsonVariant>();
JsonVariant variant3 = doc3.to<JsonVariant>(); JsonVariant variant3 = doc3.to<JsonVariant>();
SECTION("IntegerInVariant") { SECTION("Variants containing integers") {
variant1.set(42); variant1.set(42);
variant2.set(42); variant2.set(42);
variant3.set(666); variant3.set(666);
@ -228,7 +228,7 @@ TEST_CASE("JsonVariant comparisons") {
REQUIRE_FALSE(variant1 == variant3); REQUIRE_FALSE(variant1 == variant3);
} }
SECTION("StringInVariant") { SECTION("Variants containing linked strings") {
variant1.set("0hello" + 1); // make sure they have variant1.set("0hello" + 1); // make sure they have
variant2.set("1hello" + 1); // different addresses variant2.set("1hello" + 1); // different addresses
variant3.set("world"); variant3.set("world");
@ -240,7 +240,19 @@ TEST_CASE("JsonVariant comparisons") {
REQUIRE_FALSE(variant1 == variant3); REQUIRE_FALSE(variant1 == variant3);
} }
SECTION("DoubleInVariant") { SECTION("Variants containing owned strings") {
variant1.set(std::string("hello"));
variant2.set(std::string("hello"));
variant3.set(std::string("world"));
REQUIRE(variant1 == variant2);
REQUIRE_FALSE(variant1 != variant2);
REQUIRE(variant1 != variant3);
REQUIRE_FALSE(variant1 == variant3);
}
SECTION("Variants containing double") {
variant1.set(42.0); variant1.set(42.0);
variant2.set(42.0); variant2.set(42.0);
variant3.set(666.0); variant3.set(666.0);

View File

@ -13,22 +13,24 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
SECTION("stores JsonArray by copy") { SECTION("stores JsonArray by copy") {
JsonArray arr = doc2.to<JsonArray>(); JsonArray arr = doc2.to<JsonArray>();
arr.add(42); JsonObject obj = arr.createNestedObject();
obj["hello"] = "world";
var1.set(arr); var1.set(arr);
arr[0] = 666; arr[0] = 666;
REQUIRE(var1.as<std::string>() == "[42]"); REQUIRE(var1.as<std::string>() == "[{\"hello\":\"world\"}]");
} }
SECTION("stores JsonObject by copy") { SECTION("stores JsonObject by copy") {
JsonObject obj = doc2.to<JsonObject>(); JsonObject obj = doc2.to<JsonObject>();
obj["value"] = 42; JsonArray arr = obj.createNestedArray("value");
arr.add(42);
var1.set(obj); var1.set(obj);
obj["value"] = 666; obj["value"] = 666;
REQUIRE(var1.as<std::string>() == "{\"value\":42}"); REQUIRE(var1.as<std::string>() == "{\"value\":[42]}");
} }
SECTION("stores const char* by reference") { SECTION("stores const char* by reference") {
@ -45,20 +47,20 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
var1.set(str); var1.set(str);
var2.set(var1); var2.set(var1);
REQUIRE(doc1.memoryUsage() == 8); REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(8));
REQUIRE(doc2.memoryUsage() == 8); REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(8));
} }
SECTION("stores std::string by copy") { SECTION("stores std::string by copy") {
var1.set(std::string("hello!!")); var1.set(std::string("hello!!"));
var2.set(var1); var2.set(var1);
REQUIRE(doc1.memoryUsage() == 8); REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(8));
REQUIRE(doc2.memoryUsage() == 8); REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(8));
} }
SECTION("stores Serialized<const char*> by reference") { SECTION("stores Serialized<const char*> by reference") {
var1.set(serialized("hello!!", 8)); var1.set(serialized("hello!!", JSON_STRING_SIZE(8)));
var2.set(var1); var2.set(var1);
REQUIRE(doc1.memoryUsage() == 0); REQUIRE(doc1.memoryUsage() == 0);
@ -70,15 +72,15 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
var1.set(serialized(str, 8)); var1.set(serialized(str, 8));
var2.set(var1); var2.set(var1);
REQUIRE(doc1.memoryUsage() == 8); REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(8));
REQUIRE(doc2.memoryUsage() == 8); REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(8));
} }
SECTION("stores Serialized<std::string> by copy") { SECTION("stores Serialized<std::string> by copy") {
var1.set(serialized(std::string("hello!!!"))); var1.set(serialized(std::string("hello!!!")));
var2.set(var1); var2.set(var1);
REQUIRE(doc1.memoryUsage() == 8); REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(8));
REQUIRE(doc2.memoryUsage() == 8); REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(8));
} }
} }

View File

@ -5,17 +5,21 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
static const size_t epsilon = sizeof(void*);
template <size_t Capacity> template <size_t Capacity>
static void check(const char* input, DeserializationError expected) { static void check(const char* input, DeserializationError expected) {
StaticJsonDocument<Capacity> variant; StaticJsonDocument<Capacity> variant;
DeserializationError error = deserializeMsgPack(variant, input); DeserializationError error = deserializeMsgPack(variant, input);
CAPTURE(input);
REQUIRE(error == expected); REQUIRE(error == expected);
} }
template <size_t Size>
static void checkString(const char* input, DeserializationError expected) {
check<JSON_STRING_SIZE(Size)>(input, expected);
}
TEST_CASE("deserializeMsgPack(StaticJsonDocument&)") { TEST_CASE("deserializeMsgPack(StaticJsonDocument&)") {
SECTION("single values always fit") { SECTION("single values always fit") {
check<0>("\xc0", DeserializationError::Ok); // nil check<0>("\xc0", DeserializationError::Ok); // nil
@ -27,31 +31,39 @@ TEST_CASE("deserializeMsgPack(StaticJsonDocument&)") {
} }
SECTION("fixstr") { SECTION("fixstr") {
check<0>("\xA0", DeserializationError::Ok); checkString<8>("\xA0", DeserializationError::Ok);
check<0>("\xA1H", DeserializationError::NoMemory); checkString<8>("\xA7ZZZZZZZ", DeserializationError::Ok);
check<4>("\xA1H", DeserializationError::Ok); checkString<8>("\xA8ZZZZZZZZ", DeserializationError::NoMemory);
check<4>("\xA5Hello", DeserializationError::NoMemory); checkString<16>("\xAFZZZZZZZZZZZZZZZ", DeserializationError::Ok);
checkString<16>("\xB0ZZZZZZZZZZZZZZZZ", DeserializationError::NoMemory);
} }
SECTION("str 8") { SECTION("str 8") {
check<0>("\xD9\x00", DeserializationError::Ok); checkString<8>("\xD9\x00", DeserializationError::Ok);
check<0>("\xD9\x01H", DeserializationError::NoMemory); checkString<8>("\xD9\x07ZZZZZZZ", DeserializationError::Ok);
check<4>("\xD9\x01H", DeserializationError::Ok); checkString<8>("\xD9\x08ZZZZZZZZ", DeserializationError::NoMemory);
check<4>("\xD9\x05Hello", DeserializationError::NoMemory); checkString<16>("\xD9\x0FZZZZZZZZZZZZZZZ", DeserializationError::Ok);
checkString<16>("\xD9\x10ZZZZZZZZZZZZZZZZ", DeserializationError::NoMemory);
} }
SECTION("str 16") { SECTION("str 16") {
check<0>("\xDA\x00\x00", DeserializationError::Ok); checkString<8>("\xDA\x00\x00", DeserializationError::Ok);
check<0>("\xDA\x00\x01H", DeserializationError::NoMemory); checkString<8>("\xDA\x00\x07ZZZZZZZ", DeserializationError::Ok);
check<4>("\xDA\x00\x01H", DeserializationError::Ok); checkString<8>("\xDA\x00\x08ZZZZZZZZ", DeserializationError::NoMemory);
check<4>("\xDA\x00\x05Hello", DeserializationError::NoMemory); checkString<16>("\xDA\x00\x0FZZZZZZZZZZZZZZZ", DeserializationError::Ok);
checkString<16>("\xDA\x00\x10ZZZZZZZZZZZZZZZZ",
DeserializationError::NoMemory);
} }
SECTION("str 32") { SECTION("str 32") {
check<0>("\xDB\x00\x00\x00\x00", DeserializationError::Ok); checkString<8>("\xDB\x00\x00\x00\x00", DeserializationError::Ok);
check<0>("\xDB\x00\x00\x00\x01H", DeserializationError::NoMemory); checkString<8>("\xDB\x00\x00\x00\x07ZZZZZZZ", DeserializationError::Ok);
check<4>("\xDB\x00\x00\x00\x01H", DeserializationError::Ok); checkString<8>("\xDB\x00\x00\x00\x08ZZZZZZZZ",
check<4>("\xDB\x00\x00\x00\x05Hello", DeserializationError::NoMemory); DeserializationError::NoMemory);
checkString<16>("\xDB\x00\x00\x00\x0FZZZZZZZZZZZZZZZ",
DeserializationError::Ok);
checkString<16>("\xDB\x00\x00\x00\x10ZZZZZZZZZZZZZZZZ",
DeserializationError::NoMemory);
} }
SECTION("fixarray") { SECTION("fixarray") {
@ -89,14 +101,14 @@ TEST_CASE("deserializeMsgPack(StaticJsonDocument&)") {
SECTION("{H:1}") { SECTION("{H:1}") {
check<JSON_OBJECT_SIZE(0)>("\x81\xA1H\x01", check<JSON_OBJECT_SIZE(0)>("\x81\xA1H\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
check<JSON_OBJECT_SIZE(1) + epsilon>("\x81\xA1H\x01", check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
DeserializationError::Ok); "\x81\xA1H\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
check<JSON_OBJECT_SIZE(1) + epsilon>("\x82\xA1H\x01\xA1W\x02", check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
DeserializationError::NoMemory); "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory);
check<JSON_OBJECT_SIZE(2) + 2 * epsilon>("\x82\xA1H\x01\xA1W\x02", check<JSON_OBJECT_SIZE(2) + 2 * JSON_STRING_SIZE(2)>(
DeserializationError::Ok); "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok);
} }
} }
@ -107,14 +119,14 @@ TEST_CASE("deserializeMsgPack(StaticJsonDocument&)") {
SECTION("{H:1}") { SECTION("{H:1}") {
check<JSON_OBJECT_SIZE(0)>("\xDE\x00\x01\xA1H\x01", check<JSON_OBJECT_SIZE(0)>("\xDE\x00\x01\xA1H\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
check<JSON_OBJECT_SIZE(1) + epsilon>("\xDE\x00\x01\xA1H\x01", check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
DeserializationError::Ok); "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
check<JSON_OBJECT_SIZE(1) + epsilon>("\xDE\x00\x02\xA1H\x01\xA1W\x02", check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
DeserializationError::NoMemory); "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::NoMemory);
check<JSON_OBJECT_SIZE(2) + 2 * epsilon>("\xDE\x00\x02\xA1H\x01\xA1W\x02", check<JSON_OBJECT_SIZE(2) + 2 * JSON_OBJECT_SIZE(1)>(
DeserializationError::Ok); "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
} }
} }
@ -126,14 +138,14 @@ TEST_CASE("deserializeMsgPack(StaticJsonDocument&)") {
SECTION("{H:1}") { SECTION("{H:1}") {
check<JSON_OBJECT_SIZE(0)>("\xDF\x00\x00\x00\x01\xA1H\x01", check<JSON_OBJECT_SIZE(0)>("\xDF\x00\x00\x00\x01\xA1H\x01",
DeserializationError::NoMemory); DeserializationError::NoMemory);
check<JSON_OBJECT_SIZE(1) + epsilon>("\xDF\x00\x00\x00\x01\xA1H\x01", check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
DeserializationError::Ok); "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{H:1,W:2}") {
check<JSON_OBJECT_SIZE(1) + epsilon>( check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
"\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
check<JSON_OBJECT_SIZE(2) + 2 * epsilon>( check<JSON_OBJECT_SIZE(2) + 2 * JSON_OBJECT_SIZE(1)>(
"\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
} }
} }

View File

@ -3,9 +3,11 @@
# MIT License # MIT License
add_executable(StaticMemoryPoolTests add_executable(StaticMemoryPoolTests
alloc.cpp allocVariant.cpp
allocString.cpp
clear.cpp
size.cpp size.cpp
startString.cpp StringBuilder.cpp
) )
target_link_libraries(StaticMemoryPoolTests catch) target_link_libraries(StaticMemoryPoolTests catch)

View File

@ -0,0 +1,39 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/StaticMemoryPool.hpp>
#include <ArduinoJson/Memory/StringBuilder.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("StringBuilder") {
SECTION("WorksWhenBufferIsBigEnough") {
StaticMemoryPool<JSON_STRING_SIZE(6)> memoryPool;
StringBuilder str(&memoryPool);
str.append("hello");
REQUIRE(str.complete().equals("hello"));
}
SECTION("ReturnsNullWhenTooSmall") {
StaticMemoryPool<1> memoryPool;
StringBuilder str(&memoryPool);
str.append("hello!!!");
REQUIRE(str.complete().isNull());
}
SECTION("Increases size of memory pool") {
StaticMemoryPool<JSON_STRING_SIZE(6)> memoryPool;
StringBuilder str(&memoryPool);
str.append('h');
str.complete();
REQUIRE(JSON_STRING_SIZE(2) == memoryPool.size());
}
}

View File

@ -1,54 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/StaticMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
static bool isAligned(void *ptr) {
const size_t mask = sizeof(void *) - 1;
size_t addr = reinterpret_cast<size_t>(ptr);
return (addr & mask) == 0;
}
TEST_CASE("StaticMemoryPool::alloc()") {
StaticMemoryPool<64> memoryPool;
SECTION("Returns different addresses") {
void *p1 = memoryPool.alloc(1);
void *p2 = memoryPool.alloc(1);
REQUIRE(p1 != p2);
}
SECTION("Returns non-NULL when using full capacity") {
void *p = memoryPool.alloc(64);
REQUIRE(0 != p);
}
SECTION("Returns NULL when full") {
memoryPool.alloc(64);
void *p = memoryPool.alloc(1);
REQUIRE(0 == p);
}
SECTION("Returns NULL when memoryPool is too small") {
void *p = memoryPool.alloc(65);
REQUIRE(0 == p);
}
SECTION("Returns aligned pointers") {
for (size_t size = 1; size <= sizeof(void *); size++) {
void *p = memoryPool.alloc(1);
REQUIRE(isAligned(p));
}
}
SECTION("Returns same address after clear()") {
void *p1 = memoryPool.alloc(1);
memoryPool.clear();
void *p2 = memoryPool.alloc(1);
REQUIRE(p1 == p2);
}
}

View File

@ -0,0 +1,131 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/StaticMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("StaticMemoryPool::allocFrozenString()") {
const size_t poolCapacity = 64;
const size_t longestString = poolCapacity - sizeof(StringSlot);
StaticMemoryPool<poolCapacity> pool;
SECTION("Returns different addresses") {
StringSlot *a = pool.allocFrozenString(1);
StringSlot *b = pool.allocFrozenString(1);
REQUIRE(a != b);
REQUIRE(a->value != b->value);
}
SECTION("Returns a StringSlot of the right size") {
StringSlot *s = pool.allocFrozenString(12);
REQUIRE(s->size == 12);
}
SECTION("Returns NULL when full") {
pool.allocFrozenString(longestString);
void *p = pool.allocFrozenString(1);
REQUIRE(0 == p);
}
SECTION("Returns NULL when pool is too small") {
void *p = pool.allocFrozenString(longestString + 1);
REQUIRE(0 == p);
}
SECTION("Returns aligned pointers") {
REQUIRE(isAligned(pool.allocFrozenString(1)));
REQUIRE(isAligned(pool.allocFrozenString(1)));
}
SECTION("Returns same address after clear()") {
StringSlot *a = pool.allocFrozenString(1);
pool.clear();
StringSlot *b = pool.allocFrozenString(1);
REQUIRE(a == b);
REQUIRE(a->value == b->value);
}
SECTION("Returns same address after freeString()") {
StringSlot *a = pool.allocFrozenString(1);
pool.freeString(a);
StringSlot *b = pool.allocFrozenString(1);
REQUIRE(a == b);
REQUIRE(a->value == b->value);
}
SECTION("Can use full capacity when fresh") {
StringSlot *a = pool.allocFrozenString(longestString);
REQUIRE(a != 0);
}
SECTION("Can use full capacity after clear") {
pool.allocFrozenString(longestString);
pool.clear();
StringSlot *a = pool.allocFrozenString(longestString);
REQUIRE(a != 0);
}
}
TEST_CASE("StaticMemoryPool::freeString()") {
const size_t poolCapacity = 512;
const size_t longestString = poolCapacity - sizeof(StringSlot);
StaticMemoryPool<poolCapacity> pool;
static const size_t testStringSize =
(poolCapacity - sizeof(StringSlot) * 4 - sizeof(VariantSlot) * 4) / 4;
SECTION("Restores full capacity") {
StringSlot *strings[4];
VariantSlot *variants[4];
for (int i = 0; i < 4; i++) {
strings[i] = pool.allocFrozenString(testStringSize);
REQUIRE(strings[i] != 0);
variants[i] = pool.allocVariant();
REQUIRE(variants[i] != 0);
}
// In random order
pool.freeString(strings[2]);
pool.freeVariant(variants[3]);
pool.freeVariant(variants[0]);
pool.freeString(strings[0]);
pool.freeVariant(variants[1]);
pool.freeString(strings[1]);
pool.freeVariant(variants[2]);
pool.freeString(strings[3]);
StringSlot *b = pool.allocFrozenString(longestString);
REQUIRE(b != 0);
REQUIRE(b->size == longestString);
}
SECTION("Move strings") {
StringSlot *a = pool.allocFrozenString(6);
strcpy(a->value, "hello");
StringSlot *b = pool.allocFrozenString(7);
strcpy(b->value, "world!");
pool.freeString(a);
REQUIRE(b->size == 7);
REQUIRE(b->value == std::string("world!"));
REQUIRE(a->value == b->value);
}
SECTION("Accepts non-frozen string") {
StringSlot *a = pool.allocExpandableString();
pool.freeString(a);
REQUIRE(pool.size() == 0);
}
}

View File

@ -0,0 +1,38 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/StaticMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("StaticMemoryPool::allocVariant()") {
StaticMemoryPool<128> memoryPool;
SECTION("Returns different pointer") {
VariantSlot* s1 = memoryPool.allocVariant();
REQUIRE(s1 != 0);
VariantSlot* s2 = memoryPool.allocVariant();
REQUIRE(s2 != 0);
REQUIRE(s1 != s2);
}
SECTION("Returns same pointer after freeSlot()") {
VariantSlot* s1 = memoryPool.allocVariant();
memoryPool.freeVariant(s1);
VariantSlot* s2 = memoryPool.allocVariant();
REQUIRE(s1 == s2);
}
SECTION("Returns aligned pointers") {
// make room for two
// pass an uneven capacity
StaticMemoryPool<2 * sizeof(VariantSlot) + 1> pool;
REQUIRE(isAligned(pool.allocVariant()));
REQUIRE(isAligned(pool.allocVariant()));
}
}

View File

@ -0,0 +1,81 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/StaticMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
static const size_t poolCapacity = 512;
TEST_CASE("StaticMemoryPool::clear()") {
StaticMemoryPool<poolCapacity> memoryPool;
SECTION("Discards allocated variants") {
memoryPool.allocVariant();
memoryPool.clear();
REQUIRE(memoryPool.size() == 0);
}
SECTION("Discards allocated strings") {
memoryPool.allocFrozenString(10);
memoryPool.clear();
REQUIRE(memoryPool.size() == 0);
}
SECTION("Purges variant cache") {
VariantSlot* a = memoryPool.allocVariant();
REQUIRE(a != 0);
VariantSlot* b = memoryPool.allocVariant();
REQUIRE(b != 0);
// place slot a in the pool of free slots
memoryPool.freeVariant(a);
memoryPool.clear();
REQUIRE(memoryPool.size() == 0);
}
SECTION("Purges string cache") {
StringSlot* a = memoryPool.allocFrozenString(10);
REQUIRE(a != 0);
StringSlot* b = memoryPool.allocFrozenString(10);
REQUIRE(b != 0);
// place slot a in the pool of free slots
memoryPool.freeString(a);
memoryPool.clear();
REQUIRE(memoryPool.size() == 0);
}
SECTION("Purges list of string") {
StringSlot* a = memoryPool.allocFrozenString(6);
REQUIRE(a != 0);
strcpy(a->value, "hello");
StringSlot* b = memoryPool.allocFrozenString(6);
REQUIRE(b != 0);
strcpy(b->value, "world");
memoryPool.clear(); // ACT!
StringSlot* c = memoryPool.allocFrozenString(2);
REQUIRE(c != 0);
strcpy(c->value, "H");
StringSlot* d = memoryPool.allocFrozenString(2);
REQUIRE(d != 0);
strcpy(d->value, "W");
// if the memory pool keeps pointer to the old strings
// it will try to compact the strings
memoryPool.freeString(c);
REQUIRE(d->value == std::string("W"));
}
}

View File

@ -8,37 +8,72 @@
using namespace ARDUINOJSON_NAMESPACE; using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("StaticMemoryPool::size()") { TEST_CASE("StaticMemoryPool::size()") {
StaticMemoryPool<64> memoryPool;
SECTION("Capacity equals template parameter") { SECTION("Capacity equals template parameter") {
REQUIRE(64 == memoryPool.capacity()); const size_t capacity = 64;
StaticMemoryPool<capacity> memoryPool;
REQUIRE(capacity == memoryPool.capacity());
} }
SECTION("Initial size is 0") { SECTION("Initial size is 0") {
StaticMemoryPool<32> memoryPool;
REQUIRE(0 == memoryPool.size()); REQUIRE(0 == memoryPool.size());
} }
SECTION("Increases after alloc()") { SECTION("Increases after allocFrozenString()") {
memoryPool.alloc(1); StaticMemoryPool<128> memoryPool;
REQUIRE(1U <= memoryPool.size()); memoryPool.allocFrozenString(0);
memoryPool.alloc(1); REQUIRE(memoryPool.size() == JSON_STRING_SIZE(0));
REQUIRE(2U <= memoryPool.size()); memoryPool.allocFrozenString(0);
REQUIRE(memoryPool.size() == 2 * JSON_STRING_SIZE(0));
} }
SECTION("Doesn't grow when memoryPool is full") { SECTION("Decreases after freeVariant()") {
memoryPool.alloc(64); StaticMemoryPool<128> memoryPool;
memoryPool.alloc(1); VariantSlot* a = memoryPool.allocVariant();
REQUIRE(64 == memoryPool.size()); VariantSlot* b = memoryPool.allocVariant();
memoryPool.freeVariant(b);
REQUIRE(memoryPool.size() == sizeof(VariantSlot));
memoryPool.freeVariant(a);
REQUIRE(memoryPool.size() == 0);
} }
SECTION("Does't grow when memoryPool is too small for alloc") { SECTION("Decreases after calling freeString() in order") {
memoryPool.alloc(65); StaticMemoryPool<128> memoryPool;
REQUIRE(0 == memoryPool.size()); StringSlot* a = memoryPool.allocFrozenString(5);
REQUIRE(a != 0);
StringSlot* b = memoryPool.allocFrozenString(6);
REQUIRE(b != 0);
memoryPool.freeString(b);
REQUIRE(memoryPool.size() == JSON_STRING_SIZE(5));
memoryPool.freeString(a);
REQUIRE(memoryPool.size() == 0);
} }
SECTION("Goes back to zero after clear()") { SECTION("Decreases after calling freeString() in reverse order") {
memoryPool.alloc(1); StaticMemoryPool<128> memoryPool;
memoryPool.clear(); StringSlot* a = memoryPool.allocFrozenString(5);
REQUIRE(0 == memoryPool.size()); REQUIRE(a != 0);
StringSlot* b = memoryPool.allocFrozenString(6);
REQUIRE(b != 0);
memoryPool.freeString(a);
REQUIRE(memoryPool.size() == JSON_STRING_SIZE(6));
memoryPool.freeString(b);
REQUIRE(memoryPool.size() == 0);
}
SECTION("Doesn't grow when memory pool is full") {
const size_t variantCount = 4;
const size_t capacity = variantCount * sizeof(VariantSlot);
StaticMemoryPool<capacity> memoryPool;
for (size_t i = 0; i < variantCount; i++) memoryPool.allocVariant();
REQUIRE(capacity == memoryPool.size());
memoryPool.allocVariant();
REQUIRE(capacity == memoryPool.size());
} }
} }

View File

@ -1,46 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson.h>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("StaticMemoryPool::startString()") {
SECTION("WorksWhenBufferIsBigEnough") {
StaticMemoryPool<6> memoryPool;
StringBuilder str = memoryPool.startString();
str.append('h');
str.append('e');
str.append('l');
str.append('l');
str.append('o');
REQUIRE(str.complete().equals("hello"));
}
SECTION("ReturnsNullWhenTooSmall") {
StaticMemoryPool<5> memoryPool;
StringBuilder str = memoryPool.startString();
str.append('h');
str.append('e');
str.append('l');
str.append('l');
str.append('o');
REQUIRE(str.complete().isNull());
}
SECTION("SizeIncreases") {
StaticMemoryPool<5> memoryPool;
StringBuilder str = memoryPool.startString();
str.append('h');
str.complete();
REQUIRE(2 == memoryPool.size());
}
}