Skip to content

Commit a6d2384

Browse files
TimothyGuMylesBorins
authored andcommitted
src: clean up MaybeStackBuffer
- Add IsInvalidated() method - Add capacity() method for finding out the actual capacity, not the current size, of the buffer - Make IsAllocated() work for invalidated buffers - Allow multiple calls to AllocateSufficientStorage() and Invalidate() - Assert buffer is malloc'd in Release() - Assert buffer has not been invalidated in AllocateSufficientStorage() - Add more descriptive comments describing the purpose of the methods - Add cctest for MaybeStackBuffer Backport-PR-URL: #17365 PR-URL: #11464 Reviewed-By: Steven R Loomis <srloomis@us.ibm.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent ddd9d85 commit a6d2384

File tree

2 files changed

+175
-17
lines changed

2 files changed

+175
-17
lines changed

src/util.h

+51-17
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <stddef.h>
1111
#include <stdio.h>
1212
#include <stdlib.h>
13+
#include <string.h>
1314

1415
// OSX 10.9 defaults to libc++ which provides a C++11 <type_traits> header.
1516
#if defined(__APPLE__) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1090
@@ -310,45 +311,76 @@ class MaybeStackBuffer {
310311
return length_;
311312
}
312313

313-
// Call to make sure enough space for `storage` entries is available.
314-
// There can only be 1 call to AllocateSufficientStorage or Invalidate
315-
// per instance.
314+
// Current maximum capacity of the buffer with which SetLength() can be used
315+
// without first calling AllocateSufficientStorage().
316+
size_t capacity() const {
317+
return IsAllocated() ? capacity_ :
318+
IsInvalidated() ? 0 : kStackStorageSize;
319+
}
320+
321+
// Make sure enough space for `storage` entries is available.
322+
// This method can be called multiple times throughout the lifetime of the
323+
// buffer, but once this has been called Invalidate() cannot be used.
324+
// Content of the buffer in the range [0, length()) is preserved.
316325
void AllocateSufficientStorage(size_t storage) {
317-
if (storage <= kStackStorageSize) {
318-
buf_ = buf_st_;
319-
} else {
320-
buf_ = Malloc<T>(storage);
326+
CHECK(!IsInvalidated());
327+
if (storage > capacity()) {
328+
bool was_allocated = IsAllocated();
329+
T* allocated_ptr = was_allocated ? buf_ : nullptr;
330+
buf_ = Realloc(allocated_ptr, storage);
331+
capacity_ = storage;
332+
if (!was_allocated && length_ > 0)
333+
memcpy(buf_, buf_st_, length_ * sizeof(buf_[0]));
321334
}
322335

323-
// Remember how much was allocated to check against that in SetLength().
324336
length_ = storage;
325337
}
326338

327339
void SetLength(size_t length) {
328-
// length_ stores how much memory was allocated.
329-
CHECK_LE(length, length_);
340+
// capacity() returns how much memory is actually available.
341+
CHECK_LE(length, capacity());
330342
length_ = length;
331343
}
332344

333345
void SetLengthAndZeroTerminate(size_t length) {
334-
// length_ stores how much memory was allocated.
335-
CHECK_LE(length + 1, length_);
346+
// capacity() returns how much memory is actually available.
347+
CHECK_LE(length + 1, capacity());
336348
SetLength(length);
337349

338350
// T() is 0 for integer types, nullptr for pointers, etc.
339351
buf_[length] = T();
340352
}
341353

342354
// Make derefencing this object return nullptr.
343-
// Calling this is mutually exclusive with calling
344-
// AllocateSufficientStorage.
355+
// This method can be called multiple times throughout the lifetime of the
356+
// buffer, but once this has been called AllocateSufficientStorage() cannot
357+
// be used.
345358
void Invalidate() {
346-
CHECK_EQ(buf_, buf_st_);
359+
CHECK(!IsAllocated());
347360
length_ = 0;
348361
buf_ = nullptr;
349362
}
350363

351-
MaybeStackBuffer() : length_(0), buf_(buf_st_) {
364+
// If the buffer is stored in the heap rather than on the stack.
365+
bool IsAllocated() const {
366+
return !IsInvalidated() && buf_ != buf_st_;
367+
}
368+
369+
// If Invalidate() has been called.
370+
bool IsInvalidated() const {
371+
return buf_ == nullptr;
372+
}
373+
374+
// Release ownership of the malloc'd buffer.
375+
// Note: This does not free the buffer.
376+
void Release() {
377+
CHECK(IsAllocated());
378+
buf_ = buf_st_;
379+
length_ = 0;
380+
capacity_ = 0;
381+
}
382+
383+
MaybeStackBuffer() : length_(0), capacity_(0), buf_(buf_st_) {
352384
// Default to a zero-length, null-terminated buffer.
353385
buf_[0] = T();
354386
}
@@ -358,12 +390,14 @@ class MaybeStackBuffer {
358390
}
359391

360392
~MaybeStackBuffer() {
361-
if (buf_ != buf_st_)
393+
if (IsAllocated())
362394
free(buf_);
363395
}
364396

365397
private:
366398
size_t length_;
399+
// capacity of the malloc'ed buf_
400+
size_t capacity_;
367401
T* buf_;
368402
T buf_st_[kStackStorageSize];
369403
};

test/cctest/test_util.cc

+124
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,127 @@ TEST(UtilTest, UncheckedCalloc) {
121121
EXPECT_NE(nullptr, UncheckedCalloc(0));
122122
EXPECT_NE(nullptr, UncheckedCalloc(1));
123123
}
124+
125+
template <typename T>
126+
static void MaybeStackBufferBasic() {
127+
using node::MaybeStackBuffer;
128+
129+
MaybeStackBuffer<T> buf;
130+
size_t old_length;
131+
size_t old_capacity;
132+
133+
/* Default constructor */
134+
EXPECT_EQ(0U, buf.length());
135+
EXPECT_FALSE(buf.IsAllocated());
136+
EXPECT_GT(buf.capacity(), buf.length());
137+
138+
/* SetLength() expansion */
139+
buf.SetLength(buf.capacity());
140+
EXPECT_EQ(buf.capacity(), buf.length());
141+
EXPECT_FALSE(buf.IsAllocated());
142+
143+
/* Means of accessing raw buffer */
144+
EXPECT_EQ(buf.out(), *buf);
145+
EXPECT_EQ(&buf[0], *buf);
146+
147+
/* Basic I/O */
148+
for (size_t i = 0; i < buf.length(); i++)
149+
buf[i] = static_cast<T>(i);
150+
for (size_t i = 0; i < buf.length(); i++)
151+
EXPECT_EQ(static_cast<T>(i), buf[i]);
152+
153+
/* SetLengthAndZeroTerminate() */
154+
buf.SetLengthAndZeroTerminate(buf.capacity() - 1);
155+
EXPECT_EQ(buf.capacity() - 1, buf.length());
156+
for (size_t i = 0; i < buf.length(); i++)
157+
EXPECT_EQ(static_cast<T>(i), buf[i]);
158+
buf.SetLength(buf.capacity());
159+
EXPECT_EQ(0, buf[buf.length() - 1]);
160+
161+
/* Initial Realloc */
162+
old_length = buf.length() - 1;
163+
old_capacity = buf.capacity();
164+
buf.AllocateSufficientStorage(buf.capacity() * 2);
165+
EXPECT_EQ(buf.capacity(), buf.length());
166+
EXPECT_TRUE(buf.IsAllocated());
167+
for (size_t i = 0; i < old_length; i++)
168+
EXPECT_EQ(static_cast<T>(i), buf[i]);
169+
EXPECT_EQ(0, buf[old_length]);
170+
171+
/* SetLength() reduction and expansion */
172+
for (size_t i = 0; i < buf.length(); i++)
173+
buf[i] = static_cast<T>(i);
174+
buf.SetLength(10);
175+
for (size_t i = 0; i < buf.length(); i++)
176+
EXPECT_EQ(static_cast<T>(i), buf[i]);
177+
buf.SetLength(buf.capacity());
178+
for (size_t i = 0; i < buf.length(); i++)
179+
EXPECT_EQ(static_cast<T>(i), buf[i]);
180+
181+
/* Subsequent Realloc */
182+
old_length = buf.length();
183+
old_capacity = buf.capacity();
184+
buf.AllocateSufficientStorage(old_capacity * 1.5);
185+
EXPECT_EQ(buf.capacity(), buf.length());
186+
EXPECT_EQ(static_cast<size_t>(old_capacity * 1.5), buf.length());
187+
EXPECT_TRUE(buf.IsAllocated());
188+
for (size_t i = 0; i < old_length; i++)
189+
EXPECT_EQ(static_cast<T>(i), buf[i]);
190+
191+
/* Basic I/O on Realloc'd buffer */
192+
for (size_t i = 0; i < buf.length(); i++)
193+
buf[i] = static_cast<T>(i);
194+
for (size_t i = 0; i < buf.length(); i++)
195+
EXPECT_EQ(static_cast<T>(i), buf[i]);
196+
197+
/* Release() */
198+
T* rawbuf = buf.out();
199+
buf.Release();
200+
EXPECT_EQ(0U, buf.length());
201+
EXPECT_FALSE(buf.IsAllocated());
202+
EXPECT_GT(buf.capacity(), buf.length());
203+
free(rawbuf);
204+
}
205+
206+
TEST(UtilTest, MaybeStackBuffer) {
207+
using node::MaybeStackBuffer;
208+
209+
MaybeStackBufferBasic<uint8_t>();
210+
MaybeStackBufferBasic<uint16_t>();
211+
212+
// Constructor with size parameter
213+
{
214+
MaybeStackBuffer<unsigned char> buf(100);
215+
EXPECT_EQ(100U, buf.length());
216+
EXPECT_FALSE(buf.IsAllocated());
217+
EXPECT_GT(buf.capacity(), buf.length());
218+
buf.SetLength(buf.capacity());
219+
EXPECT_EQ(buf.capacity(), buf.length());
220+
EXPECT_FALSE(buf.IsAllocated());
221+
for (size_t i = 0; i < buf.length(); i++)
222+
buf[i] = static_cast<unsigned char>(i);
223+
for (size_t i = 0; i < buf.length(); i++)
224+
EXPECT_EQ(static_cast<unsigned char>(i), buf[i]);
225+
226+
MaybeStackBuffer<unsigned char> bigbuf(10000);
227+
EXPECT_EQ(10000U, bigbuf.length());
228+
EXPECT_TRUE(bigbuf.IsAllocated());
229+
EXPECT_EQ(bigbuf.length(), bigbuf.capacity());
230+
for (size_t i = 0; i < bigbuf.length(); i++)
231+
bigbuf[i] = static_cast<unsigned char>(i);
232+
for (size_t i = 0; i < bigbuf.length(); i++)
233+
EXPECT_EQ(static_cast<unsigned char>(i), bigbuf[i]);
234+
}
235+
236+
// Invalidated buffer
237+
{
238+
MaybeStackBuffer<char> buf;
239+
buf.Invalidate();
240+
EXPECT_TRUE(buf.IsInvalidated());
241+
EXPECT_FALSE(buf.IsAllocated());
242+
EXPECT_EQ(0U, buf.length());
243+
EXPECT_EQ(0U, buf.capacity());
244+
buf.Invalidate();
245+
EXPECT_TRUE(buf.IsInvalidated());
246+
}
247+
}

0 commit comments

Comments
 (0)