Benoit Blanchon 11 tahun lalu
induk
melakukan
bfe60243a4

+ 1 - 0
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"

+ 5 - 0
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
 ----
 

+ 18 - 39
include/ArduinoJson/DynamicJsonBuffer.hpp

@@ -8,60 +8,39 @@
 
 #include "JsonBuffer.hpp"
 
+#include <stdlib.h>
+
 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() { delete _next; }
-
-  size_t size() const { return _size + (_next ? _next->size() : 0); }
-
-  size_t blockCount() const { return 1 + (_next ? _next->blockCount() : 0); }
+  DynamicJsonBuffer();
+  ~DynamicJsonBuffer();
 
-  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;
 };
 }

+ 79 - 0
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<Block*>(malloc(blkSize));
+  block->capacity = capacity;
+  block->size = 0;
+  block->next = NULL;
+  return block;
+}

+ 0 - 29
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) {