From 62539cad368987726e41e611450b27b947838e8d Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sun, 19 Feb 2017 22:01:31 -0800 Subject: [PATCH 1/5] src: don't assume v8::Local is using-declared --- src/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.h b/src/util.h index b32f2b8a71ca0f..20b2ffcdba454b 100644 --- a/src/util.h +++ b/src/util.h @@ -394,7 +394,7 @@ class BufferValue : public MaybeStackBuffer { #define SPREAD_BUFFER_ARG(val, name) \ CHECK((val)->IsUint8Array()); \ - Local name = (val).As(); \ + v8::Local name = (val).As(); \ v8::ArrayBuffer::Contents name##_c = name->Buffer()->GetContents(); \ const size_t name##_offset = name->ByteOffset(); \ const size_t name##_length = name->ByteLength(); \ From 41786c47aa95f3197219d35e5e8717f71d195306 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sun, 19 Feb 2017 20:03:42 -0800 Subject: [PATCH 2/5] 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 --- src/util.h | 61 +++++++++++++++------- test/cctest/util.cc | 124 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 18 deletions(-) diff --git a/src/util.h b/src/util.h index 20b2ffcdba454b..ee2cfb45d45870 100644 --- a/src/util.h +++ b/src/util.h @@ -10,6 +10,7 @@ #include #include #include +#include #include // std::remove_reference @@ -304,29 +305,40 @@ class MaybeStackBuffer { return length_; } - // Call to make sure enough space for `storage` entries is available. - // There can only be 1 call to AllocateSufficientStorage or Invalidate - // per instance. + // Current maximum capacity of the buffer with which SetLength() can be used + // without first calling AllocateSufficientStorage(). + size_t capacity() const { + return IsAllocated() ? capacity_ : + IsInvalidated() ? 0 : kStackStorageSize; + } + + // Make sure enough space for `storage` entries is available. + // This method can be called multiple times throughout the lifetime of the + // buffer, but once this has been called Invalidate() cannot be used. + // Content of the buffer in the range [0, length()) is preserved. void AllocateSufficientStorage(size_t storage) { - if (storage <= kStackStorageSize) { - buf_ = buf_st_; - } else { - buf_ = Malloc(storage); + CHECK(!IsInvalidated()); + if (storage > capacity()) { + bool was_allocated = IsAllocated(); + T* allocated_ptr = was_allocated ? buf_ : nullptr; + buf_ = Realloc(allocated_ptr, storage); + capacity_ = storage; + if (!was_allocated && length_ > 0) + memcpy(buf_, buf_st_, length_ * sizeof(buf_[0])); } - // Remember how much was allocated to check against that in SetLength(). length_ = storage; } void SetLength(size_t length) { - // length_ stores how much memory was allocated. - CHECK_LE(length, length_); + // capacity_ stores how much memory was allocated. + CHECK_LE(length, capacity()); length_ = length; } void SetLengthAndZeroTerminate(size_t length) { // length_ stores how much memory was allocated. - CHECK_LE(length + 1, length_); + CHECK_LE(length + 1, capacity()); SetLength(length); // T() is 0 for integer types, nullptr for pointers, etc. @@ -334,24 +346,35 @@ class MaybeStackBuffer { } // Make derefencing this object return nullptr. - // Calling this is mutually exclusive with calling - // AllocateSufficientStorage. + // This method can be called multiple times throughout the lifetime of the + // buffer, but once this has been called AllocateSufficientStorage() cannot + // be used. void Invalidate() { - CHECK_EQ(buf_, buf_st_); + CHECK(!IsAllocated()); length_ = 0; buf_ = nullptr; } - bool IsAllocated() { - return buf_ != buf_st_; + // If the buffer is stored in the heap rather than on the stack. + bool IsAllocated() const { + return !IsInvalidated() && buf_ != buf_st_; + } + + // If Invalidate() has been called. + bool IsInvalidated() const { + return buf_ == nullptr; } + // Release ownership of the malloc'd buffer. + // Note: This does not free the buffer. void Release() { + CHECK(IsAllocated()); buf_ = buf_st_; length_ = 0; + capacity_ = 0; } - MaybeStackBuffer() : length_(0), buf_(buf_st_) { + MaybeStackBuffer() : length_(0), capacity_(0), buf_(buf_st_) { // Default to a zero-length, null-terminated buffer. buf_[0] = T(); } @@ -361,12 +384,14 @@ class MaybeStackBuffer { } ~MaybeStackBuffer() { - if (buf_ != buf_st_) + if (IsAllocated()) free(buf_); } private: size_t length_; + // capacity of the malloc'ed buf_ + size_t capacity_; T* buf_; T buf_st_[kStackStorageSize]; }; diff --git a/test/cctest/util.cc b/test/cctest/util.cc index f99a46cdf5353b..a6ece3c6f4d377 100644 --- a/test/cctest/util.cc +++ b/test/cctest/util.cc @@ -132,3 +132,127 @@ TEST(UtilTest, UncheckedCalloc) { TEST_AND_FREE(UncheckedCalloc(0)); TEST_AND_FREE(UncheckedCalloc(1)); } + +template +static void MaybeStackBufferBasic() { + using node::MaybeStackBuffer; + + MaybeStackBuffer buf; + size_t old_length; + size_t old_capacity; + + /* Default constructor */ + EXPECT_EQ(0U, buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + EXPECT_GT(buf.capacity(), buf.length()); + + /* SetLength() expansion */ + buf.SetLength(buf.capacity()); + EXPECT_EQ(buf.capacity(), buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + + /* Means of accessing raw buffer */ + EXPECT_EQ(buf.out(), *buf); + EXPECT_EQ(&buf[0], *buf); + + /* Basic I/O */ + for (size_t i = 0; i < buf.length(); i++) + buf[i] = static_cast(i); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + + /* SetLengthAndZeroTerminate() */ + buf.SetLengthAndZeroTerminate(buf.capacity() - 1); + EXPECT_EQ(buf.capacity() - 1, buf.length()); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + buf.SetLength(buf.capacity()); + EXPECT_EQ(0, buf[buf.length() - 1]); + + /* Initial Realloc */ + old_length = buf.length() - 1; + old_capacity = buf.capacity(); + buf.AllocateSufficientStorage(buf.capacity() * 2); + EXPECT_EQ(buf.capacity(), buf.length()); + EXPECT_TRUE(buf.IsAllocated()); + for (size_t i = 0; i < old_length; i++) + EXPECT_EQ(static_cast(i), buf[i]); + EXPECT_EQ(0, buf[old_length]); + + /* SetLength() reduction and expansion */ + for (size_t i = 0; i < buf.length(); i++) + buf[i] = static_cast(i); + buf.SetLength(10); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + buf.SetLength(buf.capacity()); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + + /* Subsequent Realloc */ + old_length = buf.length(); + old_capacity = buf.capacity(); + buf.AllocateSufficientStorage(old_capacity * 1.5); + EXPECT_EQ(buf.capacity(), buf.length()); + EXPECT_EQ(static_cast(old_capacity * 1.5), buf.length()); + EXPECT_TRUE(buf.IsAllocated()); + for (size_t i = 0; i < old_length; i++) + EXPECT_EQ(static_cast(i), buf[i]); + + /* Basic I/O on Realloc'd buffer */ + for (size_t i = 0; i < buf.length(); i++) + buf[i] = static_cast(i); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + + /* Release() */ + T* rawbuf = buf.out(); + buf.Release(); + EXPECT_EQ(0U, buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + EXPECT_GT(buf.capacity(), buf.length()); + free(rawbuf); +} + +TEST(UtilTest, MaybeStackBuffer) { + using node::MaybeStackBuffer; + + MaybeStackBufferBasic(); + MaybeStackBufferBasic(); + + // Constructor with size parameter + { + MaybeStackBuffer buf(100); + EXPECT_EQ(100U, buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + EXPECT_GT(buf.capacity(), buf.length()); + buf.SetLength(buf.capacity()); + EXPECT_EQ(buf.capacity(), buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + for (size_t i = 0; i < buf.length(); i++) + buf[i] = static_cast(i); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + + MaybeStackBuffer bigbuf(10000); + EXPECT_EQ(10000U, bigbuf.length()); + EXPECT_TRUE(bigbuf.IsAllocated()); + EXPECT_EQ(bigbuf.length(), bigbuf.capacity()); + for (size_t i = 0; i < bigbuf.length(); i++) + bigbuf[i] = static_cast(i); + for (size_t i = 0; i < bigbuf.length(); i++) + EXPECT_EQ(static_cast(i), bigbuf[i]); + } + + // Invalidated buffer + { + MaybeStackBuffer buf; + buf.Invalidate(); + EXPECT_TRUE(buf.IsInvalidated()); + EXPECT_FALSE(buf.IsAllocated()); + EXPECT_EQ(0U, buf.length()); + EXPECT_EQ(0U, buf.capacity()); + buf.Invalidate(); + EXPECT_TRUE(buf.IsInvalidated()); + } +} From 199d115f39071a73fa81305ccde712390a2b4c2b Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sun, 19 Feb 2017 20:04:10 -0800 Subject: [PATCH 3/5] src, i18n: cleanup usage of MaybeStackBuffer - Templatize AsBuffer() and create a generic version for inclusion in the Buffer class - Use MaybeStackBuffer::storage() - If possible, avoid double conversion in ToASCII()/ToUnicode() - More descriptive assertion error in tests --- src/node_i18n.cc | 110 ++++++++++++++-------------- src/node_internals.h | 29 ++++++++ test/parallel/test-icu-transcode.js | 4 +- 3 files changed, 85 insertions(+), 58 deletions(-) diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 648012a50601e3..ae14aed7c6b4c2 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -72,37 +72,19 @@ using v8::Value; namespace i18n { -const size_t kStorageSize = 1024; - -// TODO(jasnell): This could potentially become a member of MaybeStackBuffer -// at some point in the future. Care would need to be taken with the -// MaybeStackBuffer variant below. -MaybeLocal AsBuffer(Isolate* isolate, - MaybeStackBuffer* buf, - size_t len) { - if (buf->IsAllocated()) { - MaybeLocal ret = Buffer::New(isolate, buf->out(), len); - if (!ret.IsEmpty()) buf->Release(); +template +MaybeLocal ToBufferEndian(Environment* env, MaybeStackBuffer* buf) { + MaybeLocal ret = Buffer::New(env, buf); + if (ret.IsEmpty()) return ret; - } - return Buffer::Copy(isolate, buf->out(), len); -} -MaybeLocal AsBuffer(Isolate* isolate, - MaybeStackBuffer* buf, - size_t len) { - char* dst = reinterpret_cast(**buf); - MaybeLocal ret; - if (buf->IsAllocated()) { - ret = Buffer::New(isolate, dst, len); - if (!ret.IsEmpty()) buf->Release(); - } else { - ret = Buffer::Copy(isolate, dst, len); - } - if (!ret.IsEmpty() && IsBigEndian()) { - SPREAD_BUFFER_ARG(ret.ToLocalChecked(), buf); - SwapBytes16(buf_data, buf_length); + static_assert(sizeof(T) == 1 || sizeof(T) == 2, + "Currently only one- or two-byte buffers are supported"); + if (sizeof(T) > 1 && IsBigEndian()) { + SPREAD_BUFFER_ARG(ret.ToLocalChecked(), retbuf); + SwapBytes16(retbuf_data, retbuf_length); } + return ret; } @@ -138,14 +120,14 @@ void CopySourceBuffer(MaybeStackBuffer* dest, } } -typedef MaybeLocal (*TranscodeFunc)(Isolate* isolate, +typedef MaybeLocal (*TranscodeFunc)(Environment* env, const char* fromEncoding, const char* toEncoding, const char* source, const size_t source_length, UErrorCode* status); -MaybeLocal Transcode(Isolate* isolate, +MaybeLocal Transcode(Environment* env, const char* fromEncoding, const char* toEncoding, const char* source, @@ -162,12 +144,14 @@ MaybeLocal Transcode(Isolate* isolate, ucnv_convertEx(to.conv, from.conv, &target, target + limit, &source, source + source_length, nullptr, nullptr, nullptr, nullptr, true, true, status); - if (U_SUCCESS(*status)) - ret = AsBuffer(isolate, &result, target - &result[0]); + if (U_SUCCESS(*status)) { + result.SetLength(target - &result[0]); + ret = ToBufferEndian(env, &result); + } return ret; } -MaybeLocal TranscodeToUcs2(Isolate* isolate, +MaybeLocal TranscodeToUcs2(Environment* env, const char* fromEncoding, const char* toEncoding, const char* source, @@ -181,11 +165,11 @@ MaybeLocal TranscodeToUcs2(Isolate* isolate, ucnv_toUChars(from.conv, *destbuf, length_in_chars, source, source_length, status); if (U_SUCCESS(*status)) - ret = AsBuffer(isolate, &destbuf, length_in_chars); + ret = ToBufferEndian(env, &destbuf); return ret; } -MaybeLocal TranscodeFromUcs2(Isolate* isolate, +MaybeLocal TranscodeFromUcs2(Environment* env, const char* fromEncoding, const char* toEncoding, const char* source, @@ -200,37 +184,42 @@ MaybeLocal TranscodeFromUcs2(Isolate* isolate, MaybeStackBuffer destbuf(length_in_chars); const uint32_t len = ucnv_fromUChars(to.conv, *destbuf, length_in_chars, *sourcebuf, length_in_chars, status); - if (U_SUCCESS(*status)) - ret = AsBuffer(isolate, &destbuf, len); + if (U_SUCCESS(*status)) { + destbuf.SetLength(len); + ret = ToBufferEndian(env, &destbuf); + } return ret; } -MaybeLocal TranscodeUcs2FromUtf8(Isolate* isolate, +MaybeLocal TranscodeUcs2FromUtf8(Environment* env, const char* fromEncoding, const char* toEncoding, const char* source, const size_t source_length, UErrorCode* status) { *status = U_ZERO_ERROR; - MaybeStackBuffer destbuf; + MaybeStackBuffer destbuf; int32_t result_length; - u_strFromUTF8(*destbuf, kStorageSize, &result_length, + u_strFromUTF8(*destbuf, destbuf.capacity(), &result_length, source, source_length, status); MaybeLocal ret; if (U_SUCCESS(*status)) { - ret = AsBuffer(isolate, &destbuf, result_length * sizeof(**destbuf)); + destbuf.SetLength(result_length); + ret = ToBufferEndian(env, &destbuf); } else if (*status == U_BUFFER_OVERFLOW_ERROR) { *status = U_ZERO_ERROR; destbuf.AllocateSufficientStorage(result_length); u_strFromUTF8(*destbuf, result_length, &result_length, source, source_length, status); - if (U_SUCCESS(*status)) - ret = AsBuffer(isolate, &destbuf, result_length * sizeof(**destbuf)); + if (U_SUCCESS(*status)) { + destbuf.SetLength(result_length); + ret = ToBufferEndian(env, &destbuf); + } } return ret; } -MaybeLocal TranscodeUtf8FromUcs2(Isolate* isolate, +MaybeLocal TranscodeUtf8FromUcs2(Environment* env, const char* fromEncoding, const char* toEncoding, const char* source, @@ -241,20 +230,21 @@ MaybeLocal TranscodeUtf8FromUcs2(Isolate* isolate, const size_t length_in_chars = source_length / sizeof(UChar); int32_t result_length; MaybeStackBuffer sourcebuf; - MaybeStackBuffer destbuf; + MaybeStackBuffer destbuf; CopySourceBuffer(&sourcebuf, source, source_length, length_in_chars); - u_strToUTF8(*destbuf, kStorageSize, &result_length, + u_strToUTF8(*destbuf, destbuf.capacity(), &result_length, *sourcebuf, length_in_chars, status); if (U_SUCCESS(*status)) { - ret = AsBuffer(isolate, &destbuf, result_length); + destbuf.SetLength(result_length); + ret = ToBufferEndian(env, &destbuf); } else if (*status == U_BUFFER_OVERFLOW_ERROR) { *status = U_ZERO_ERROR; destbuf.AllocateSufficientStorage(result_length); u_strToUTF8(*destbuf, result_length, &result_length, *sourcebuf, length_in_chars, status); if (U_SUCCESS(*status)) { - ret = Buffer::New(isolate, *destbuf, result_length); - destbuf.Release(); + destbuf.SetLength(result_length); + ret = ToBufferEndian(env, &destbuf); } } return ret; @@ -320,7 +310,7 @@ void Transcode(const FunctionCallbackInfo&args) { ABORT(); } - result = tfn(isolate, EncodingName(fromEncoding), EncodingName(toEncoding), + result = tfn(env, EncodingName(fromEncoding), EncodingName(toEncoding), ts_obj_data, ts_obj_length, &status); } else { status = U_ILLEGAL_ARGUMENT_ERROR; @@ -431,7 +421,7 @@ int32_t ToUnicode(MaybeStackBuffer* buf, int32_t len = uidna_nameToUnicodeUTF8(uidna, input, length, - **buf, buf->length(), + **buf, buf->capacity(), &info, &status); @@ -440,13 +430,17 @@ int32_t ToUnicode(MaybeStackBuffer* buf, buf->AllocateSufficientStorage(len); len = uidna_nameToUnicodeUTF8(uidna, input, length, - **buf, buf->length(), + **buf, buf->capacity(), &info, &status); } - if (U_FAILURE(status)) + if (U_FAILURE(status)) { len = -1; + buf->SetLength(0); + } else { + buf->SetLength(len); + } uidna_close(uidna); return len; @@ -465,7 +459,7 @@ int32_t ToASCII(MaybeStackBuffer* buf, int32_t len = uidna_nameToASCII_UTF8(uidna, input, length, - **buf, buf->length(), + **buf, buf->capacity(), &info, &status); @@ -474,13 +468,17 @@ int32_t ToASCII(MaybeStackBuffer* buf, buf->AllocateSufficientStorage(len); len = uidna_nameToASCII_UTF8(uidna, input, length, - **buf, buf->length(), + **buf, buf->capacity(), &info, &status); } - if (U_FAILURE(status)) + if (U_FAILURE(status)) { len = -1; + buf->SetLength(0); + } else { + buf->SetLength(len); + } uidna_close(uidna); return len; diff --git a/src/node_internals.h b/src/node_internals.h index 64d82deec1b050..347712dcd8feae 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -204,6 +204,35 @@ v8::MaybeLocal New(Environment* env, // because ArrayBufferAllocator::Free() deallocates it again with free(). // Mixing operator new and free() is undefined behavior so don't do that. v8::MaybeLocal New(Environment* env, char* data, size_t length); + +// Construct a Buffer from a MaybeStackBuffer (and also its subclasses like +// Utf8Value and TwoByteValue). +// If |buf| is invalidated, an empty MaybeLocal is returned, and nothing is +// changed. +// If |buf| contains actual data, this method takes ownership of |buf|'s +// underlying buffer. However, |buf| itself can be reused even after this call, +// but its capacity, if increased through AllocateSufficientStorage, is not +// guaranteed to stay the same. +template +static v8::MaybeLocal New(Environment* env, + MaybeStackBuffer* buf) { + v8::MaybeLocal ret; + char* src = reinterpret_cast(buf->out()); + const size_t len_in_bytes = buf->length() * sizeof(buf->out()[0]); + + if (buf->IsAllocated()) + ret = New(env, src, len_in_bytes); + else if (!buf->IsInvalidated()) + ret = Copy(env, src, len_in_bytes); + + if (ret.IsEmpty()) + return ret; + + if (buf->IsAllocated()) + buf->Release(); + + return ret; +} } // namespace Buffer } // namespace node diff --git a/test/parallel/test-icu-transcode.js b/test/parallel/test-icu-transcode.js index 7bc10e7c5d7827..1c77427b1545bc 100644 --- a/test/parallel/test-icu-transcode.js +++ b/test/parallel/test-icu-transcode.js @@ -22,9 +22,9 @@ const tests = { for (const test in tests) { const dest = buffer.transcode(orig, 'utf8', test); - assert.strictEqual(dest.length, tests[test].length); + assert.strictEqual(dest.length, tests[test].length, `utf8->${test} length`); for (let n = 0; n < tests[test].length; n++) - assert.strictEqual(dest[n], tests[test][n]); + assert.strictEqual(dest[n], tests[test][n], `utf8->${test} char ${n}`); } { From b5acb67972414de07a5aa14f709b7ddbf7e6de84 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sun, 19 Feb 2017 16:51:40 -0800 Subject: [PATCH 4/5] benchmark: add url.domainTo*() --- benchmark/url/whatwg-url-idna.js | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 benchmark/url/whatwg-url-idna.js diff --git a/benchmark/url/whatwg-url-idna.js b/benchmark/url/whatwg-url-idna.js new file mode 100644 index 00000000000000..41b4c639de97b6 --- /dev/null +++ b/benchmark/url/whatwg-url-idna.js @@ -0,0 +1,47 @@ +'use strict'; +const common = require('../common.js'); +const { domainToASCII, domainToUnicode } = require('url'); + +const inputs = { + empty: { + ascii: '', + unicode: '' + }, + none: { + ascii: 'passports', + unicode: 'passports' + }, + some: { + ascii: 'Paßstraße', + unicode: 'xn--Pastrae-1vae' + }, + all: { + ascii: '他们不说中文', + unicode: 'xn--ihqwczyycu19kkg2c' + }, + nonstring: { + ascii: { toString() { return ''; } }, + unicode: { toString() { return ''; } } + } +}; + +const bench = common.createBenchmark(main, { + input: Object.keys(inputs), + to: ['ascii', 'unicode'], + n: [5e6] +}); + +function main(conf) { + const n = conf.n | 0; + const to = conf.to; + const input = inputs[conf.input][to]; + const method = to === 'ascii' ? domainToASCII : domainToUnicode; + + common.v8ForceOptimization(method, input); + + bench.start(); + for (var i = 0; i < n; i++) { + method(input); + } + bench.end(n); +} From 0995c120bd54950b1754d9f0ad43620af76c7e06 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 22 Feb 2017 21:45:55 -0800 Subject: [PATCH 5/5] util: update comments --- src/util.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.h b/src/util.h index ee2cfb45d45870..cf530a4da9146c 100644 --- a/src/util.h +++ b/src/util.h @@ -331,13 +331,13 @@ class MaybeStackBuffer { } void SetLength(size_t length) { - // capacity_ stores how much memory was allocated. + // capacity() returns how much memory is actually available. CHECK_LE(length, capacity()); length_ = length; } void SetLengthAndZeroTerminate(size_t length) { - // length_ stores how much memory was allocated. + // capacity() returns how much memory is actually available. CHECK_LE(length + 1, capacity()); SetLength(length);