Skip to content

Commit fb85f50

Browse files
TimothyGuitaloacasas
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 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 beda326 commit fb85f50

File tree

2 files changed

+168
-19
lines changed

2 files changed

+168
-19
lines changed

src/util.h

+44-19
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
#include <type_traits> // std::remove_reference
1516

@@ -304,54 +305,76 @@ class MaybeStackBuffer {
304305
return length_;
305306
}
306307

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

317-
// Remember how much was allocated to check against that in SetLength().
318330
length_ = storage;
319331
}
320332

321333
void SetLength(size_t length) {
322-
// length_ stores how much memory was allocated.
323-
CHECK_LE(length, length_);
334+
// capacity() returns how much memory is actually available.
335+
CHECK_LE(length, capacity());
324336
length_ = length;
325337
}
326338

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

332344
// T() is 0 for integer types, nullptr for pointers, etc.
333345
buf_[length] = T();
334346
}
335347

336348
// Make derefencing this object return nullptr.
337-
// Calling this is mutually exclusive with calling
338-
// AllocateSufficientStorage.
349+
// This method can be called multiple times throughout the lifetime of the
350+
// buffer, but once this has been called AllocateSufficientStorage() cannot
351+
// be used.
339352
void Invalidate() {
340-
CHECK_EQ(buf_, buf_st_);
353+
CHECK(!IsAllocated());
341354
length_ = 0;
342355
buf_ = nullptr;
343356
}
344357

345-
bool IsAllocated() {
346-
return buf_ != buf_st_;
358+
// If the buffer is stored in the heap rather than on the stack.
359+
bool IsAllocated() const {
360+
return !IsInvalidated() && buf_ != buf_st_;
361+
}
362+
363+
// If Invalidate() has been called.
364+
bool IsInvalidated() const {
365+
return buf_ == nullptr;
347366
}
348367

368+
// Release ownership of the malloc'd buffer.
369+
// Note: This does not free the buffer.
349370
void Release() {
371+
CHECK(IsAllocated());
350372
buf_ = buf_st_;
351373
length_ = 0;
374+
capacity_ = 0;
352375
}
353376

354-
MaybeStackBuffer() : length_(0), buf_(buf_st_) {
377+
MaybeStackBuffer() : length_(0), capacity_(0), buf_(buf_st_) {
355378
// Default to a zero-length, null-terminated buffer.
356379
buf_[0] = T();
357380
}
@@ -361,12 +384,14 @@ class MaybeStackBuffer {
361384
}
362385

363386
~MaybeStackBuffer() {
364-
if (buf_ != buf_st_)
387+
if (IsAllocated())
365388
free(buf_);
366389
}
367390

368391
private:
369392
size_t length_;
393+
// capacity of the malloc'ed buf_
394+
size_t capacity_;
370395
T* buf_;
371396
T buf_st_[kStackStorageSize];
372397
};

test/cctest/util.cc

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

0 commit comments

Comments
 (0)