diff --git a/ArduinoJson.cpp b/ArduinoJson.cpp index 3040c986..bcdba190 100644 --- a/ArduinoJson.cpp +++ b/ArduinoJson.cpp @@ -9,6 +9,7 @@ // This file is here to help the Arduino IDE find the other files. #include "src/Arduino/Print.cpp" +#include "src/DynamicJsonBuffer.cpp" #include "src/Internals/IndentedPrint.cpp" #include "src/Internals/JsonParser.cpp" #include "src/Internals/List.cpp" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c43095a..782315d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Arduino JSON: change log ======================== +HEAD +---- + +* Fixed stack-overflow in `DynamicJsonBuffer` when parsing huge JSON files (issue #65) + v4.2 ---- diff --git a/include/ArduinoJson/DynamicJsonBuffer.hpp b/include/ArduinoJson/DynamicJsonBuffer.hpp index ef0f7c27..667c52d9 100644 --- a/include/ArduinoJson/DynamicJsonBuffer.hpp +++ b/include/ArduinoJson/DynamicJsonBuffer.hpp @@ -8,60 +8,39 @@ #include "JsonBuffer.hpp" +#include + namespace ArduinoJson { +// Forward declaration +namespace Internals { +struct DynamicJsonBufferBlock; +} + // Implements a JsonBuffer with dynamic memory allocation. // You are strongly encouraged to consider using StaticJsonBuffer which is much // more suitable for embedded systems. class DynamicJsonBuffer : public JsonBuffer { public: - DynamicJsonBuffer() : _next(NULL), _size(0) {} + DynamicJsonBuffer(); + ~DynamicJsonBuffer(); - ~DynamicJsonBuffer() { delete _next; } - - size_t size() const { return _size + (_next ? _next->size() : 0); } - - size_t blockCount() const { return 1 + (_next ? _next->blockCount() : 0); } - - static const size_t BLOCK_CAPACITY = 32; + size_t size() const; protected: - virtual void* alloc(size_t bytes) { - if (canAllocInThisBlock(bytes)) - return allocInThisBlock(bytes); - else if (canAllocInOtherBlocks(bytes)) - return allocInOtherBlocks(bytes); - else - return NULL; - } + virtual void* alloc(size_t bytes); private: - bool canAllocInThisBlock(size_t bytes) const { - return _size + bytes <= BLOCK_CAPACITY; - } + typedef Internals::DynamicJsonBufferBlock Block; - void* allocInThisBlock(size_t bytes) { - void* p = _buffer + _size; - _size += bytes; - return p; - } + static const size_t FIRST_BLOCK_CAPACITY = 32; - bool canAllocInOtherBlocks(size_t bytes) const { - // by design a DynamicJsonBuffer can't alloc a block bigger than - // BLOCK_CAPACITY - return bytes <= BLOCK_CAPACITY; - } + static Block* createBlock(size_t capacity); - void* allocInOtherBlocks(size_t bytes) { - if (!_next) { - _next = new DynamicJsonBuffer(); - if (!_next) return NULL; - } - return _next->alloc(bytes); - } + inline bool canAllocInHead(size_t bytes) const; + inline void* allocInHead(size_t bytes); + inline void addNewBlock(); - DynamicJsonBuffer* _next; - size_t _size; - uint8_t _buffer[BLOCK_CAPACITY]; + Block* _head; }; } diff --git a/src/DynamicJsonBuffer.cpp b/src/DynamicJsonBuffer.cpp new file mode 100644 index 00000000..f0fb9393 --- /dev/null +++ b/src/DynamicJsonBuffer.cpp @@ -0,0 +1,79 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#include "../include/ArduinoJson/DynamicJsonBuffer.hpp" + +namespace ArduinoJson { +namespace Internals { +struct DynamicJsonBufferBlockWithoutData { + DynamicJsonBufferBlock* next; + size_t capacity; + size_t size; +}; + + +struct DynamicJsonBufferBlock : DynamicJsonBufferBlockWithoutData { + uint8_t data[1]; +}; +} +} + +using namespace ArduinoJson; +using namespace ArduinoJson::Internals; + +DynamicJsonBuffer::DynamicJsonBuffer() { + _head = createBlock(FIRST_BLOCK_CAPACITY); +} + +DynamicJsonBuffer::~DynamicJsonBuffer() { + Block* currentBlock = _head; + + while (currentBlock != NULL) { + Block* nextBlock = currentBlock->next; + free(currentBlock); + currentBlock = nextBlock; + } +} + +size_t DynamicJsonBuffer::size() const { + size_t total = 0; + + for (const Block* b = _head; b != NULL; b = b->next) { + total += b->size; + } + + return total; +} + +void* DynamicJsonBuffer::alloc(size_t bytes) { + if (!canAllocInHead(bytes)) addNewBlock(); + return allocInHead(bytes); +} + +bool DynamicJsonBuffer::canAllocInHead(size_t bytes) const { + return _head->size + bytes <= _head->capacity; +} + +void* DynamicJsonBuffer::allocInHead(size_t bytes) { + void* p = _head->data + _head->size; + _head->size += bytes; + return p; +} + +void DynamicJsonBuffer::addNewBlock() { + Block* block = createBlock(_head->capacity * 2); + block->next = _head; + _head = block; +} + +DynamicJsonBuffer::Block* DynamicJsonBuffer::createBlock(size_t capacity) { + size_t blkSize = sizeof(DynamicJsonBufferBlockWithoutData) + capacity; + Block* block = static_cast(malloc(blkSize)); + block->capacity = capacity; + block->size = 0; + block->next = NULL; + return block; +} diff --git a/test/DynamicJsonBuffer_Basic_Tests.cpp b/test/DynamicJsonBuffer_Basic_Tests.cpp index 7e3b7919..bd8fa53b 100644 --- a/test/DynamicJsonBuffer_Basic_Tests.cpp +++ b/test/DynamicJsonBuffer_Basic_Tests.cpp @@ -20,40 +20,11 @@ TEST_F(DynamicJsonBuffer_Basic_Tests, InitialSizeIsZero) { ASSERT_EQ(0, buffer.size()); } -TEST_F(DynamicJsonBuffer_Basic_Tests, InitialBlockCountIsOne) { - ASSERT_EQ(1, buffer.blockCount()); -} - TEST_F(DynamicJsonBuffer_Basic_Tests, SizeIncreasesAfterAlloc) { buffer.alloc(1); ASSERT_EQ(1, buffer.size()); buffer.alloc(1); ASSERT_EQ(2, buffer.size()); - buffer.alloc(DynamicJsonBuffer::BLOCK_CAPACITY); - ASSERT_EQ(2 + DynamicJsonBuffer::BLOCK_CAPACITY, buffer.size()); -} - -TEST_F(DynamicJsonBuffer_Basic_Tests, BlockCountDoesntChangeWhenNotFull) { - buffer.alloc(DynamicJsonBuffer::BLOCK_CAPACITY); - ASSERT_EQ(1, buffer.blockCount()); -} - -TEST_F(DynamicJsonBuffer_Basic_Tests, BlockCountChangesWhenFull) { - buffer.alloc(DynamicJsonBuffer::BLOCK_CAPACITY); - buffer.alloc(1); - ASSERT_EQ(2, buffer.blockCount()); -} - -TEST_F(DynamicJsonBuffer_Basic_Tests, CanAllocLessThanBlockCapacity) { - void* p1 = buffer.alloc(DynamicJsonBuffer::BLOCK_CAPACITY); - ASSERT_FALSE(p1 == NULL); - void* p2 = buffer.alloc(DynamicJsonBuffer::BLOCK_CAPACITY); - ASSERT_FALSE(p2 == NULL); -} - -TEST_F(DynamicJsonBuffer_Basic_Tests, CantAllocMoreThanBlockCapacity) { - void* p = buffer.alloc(DynamicJsonBuffer::BLOCK_CAPACITY + 1); - ASSERT_TRUE(p == NULL); } TEST_F(DynamicJsonBuffer_Basic_Tests, ReturnDifferentPointer) {