Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
src: remove UncheckedMalloc(0) workaround
Assuming that UncheckedMalloc(0) returns a non-nullptr is non-standard
and we use other allocators as well (e.g., OPENSSL_malloc) that do not
guarantee this behavior. It is the caller's responsibility to check that
size != 0 implies UncheckedMalloc(size) != nullptr, and that's exactly
what the checked variants (Malloc etc.) already do.

The current behavior is also inconsistent with UncheckedRealloc(), which
always returns a nullptr when the size is 0, and with the documentation
in src/README.md as well as with multiple comments in the source code.

This changes UncheckedMalloc(), UncheckedCalloc(), and
UncheckedRealloc() to always return a nullptr when the size is 0 instead
of doing fake allocations in UncheckedMalloc() and UncheckedCalloc()
while returning a nullptr from UncheckedRealloc(). This is consistent
with existing documentation and comments.

Refs: #8571
Refs: #8572
  • Loading branch information
tniessen committed Sep 8, 2022
commit 8dae03b42f2ab0c762450f0564be4cd945d8901c
14 changes: 8 additions & 6 deletions src/string_bytes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -704,20 +704,21 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
}

case UCS2: {
size_t str_len = buflen / 2;
if (IsBigEndian()) {
uint16_t* dst = node::UncheckedMalloc<uint16_t>(buflen / 2);
if (dst == nullptr) {
uint16_t* dst = node::UncheckedMalloc<uint16_t>(str_len);
if (str_len != 0 && dst == nullptr) {
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
return MaybeLocal<Value>();
}
for (size_t i = 0, k = 0; k < buflen / 2; i += 2, k += 1) {
for (size_t i = 0, k = 0; k < str_len; i += 2, k += 1) {
// The input is in *little endian*, because that's what Node.js
// expects, so the high byte comes after the low byte.
const uint8_t hi = static_cast<uint8_t>(buf[i + 1]);
const uint8_t lo = static_cast<uint8_t>(buf[i + 0]);
dst[k] = static_cast<uint16_t>(hi) << 8 | lo;
}
return ExternTwoByteString::New(isolate, dst, buflen / 2, error);
return ExternTwoByteString::New(isolate, dst, str_len, error);
}
if (reinterpret_cast<uintptr_t>(buf) % 2 != 0) {
// Unaligned data still means we can't directly pass it to V8.
Expand All @@ -728,10 +729,10 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
}
memcpy(dst, buf, buflen);
return ExternTwoByteString::New(
isolate, reinterpret_cast<uint16_t*>(dst), buflen / 2, error);
isolate, reinterpret_cast<uint16_t*>(dst), str_len, error);
}
return ExternTwoByteString::NewFromCopy(
isolate, reinterpret_cast<const uint16_t*>(buf), buflen / 2, error);
isolate, reinterpret_cast<const uint16_t*>(buf), str_len, error);
}

default:
Expand All @@ -747,6 +748,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
const uint16_t* buf,
size_t buflen,
Local<Value>* error) {
if (buflen == 0) return String::Empty(isolate);
CHECK_BUFLEN_IN_RANGE(buflen);

// Node's "ucs2" encoding expects LE character data inside a
Expand Down
4 changes: 1 addition & 3 deletions src/util-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,14 +361,12 @@ T* UncheckedRealloc(T* pointer, size_t n) {
// As per spec realloc behaves like malloc if passed nullptr.
template <typename T>
inline T* UncheckedMalloc(size_t n) {
if (n == 0) n = 1;
return UncheckedRealloc<T>(nullptr, n);
}

template <typename T>
inline T* UncheckedCalloc(size_t n) {
if (n == 0) n = 1;
MultiplyWithOverflowCheck(sizeof(T), n);
if (MultiplyWithOverflowCheck(sizeof(T), n) == 0) return nullptr;
return static_cast<T*>(calloc(n, sizeof(T)));
}

Expand Down
42 changes: 21 additions & 21 deletions test/cctest/test_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,39 +97,39 @@ TEST(UtilTest, ToLower) {
EXPECT_EQ('a', ToLower('A'));
}

#define TEST_AND_FREE(expression) \
do { \
auto pointer = expression; \
EXPECT_NE(nullptr, pointer); \
free(pointer); \
#define TEST_AND_FREE(expression, size) \
do { \
auto pointer = expression(size); \
EXPECT_EQ(pointer == nullptr, size == 0); \
free(pointer); \
} while (0)

TEST(UtilTest, Malloc) {
TEST_AND_FREE(Malloc<char>(0));
TEST_AND_FREE(Malloc<char>(1));
TEST_AND_FREE(Malloc(0));
TEST_AND_FREE(Malloc(1));
TEST_AND_FREE(Malloc<char>, 0);
TEST_AND_FREE(Malloc<char>, 1);
TEST_AND_FREE(Malloc, 0);
TEST_AND_FREE(Malloc, 1);
}

TEST(UtilTest, Calloc) {
TEST_AND_FREE(Calloc<char>(0));
TEST_AND_FREE(Calloc<char>(1));
TEST_AND_FREE(Calloc(0));
TEST_AND_FREE(Calloc(1));
TEST_AND_FREE(Calloc<char>, 0);
TEST_AND_FREE(Calloc<char>, 1);
TEST_AND_FREE(Calloc, 0);
TEST_AND_FREE(Calloc, 1);
}

TEST(UtilTest, UncheckedMalloc) {
TEST_AND_FREE(UncheckedMalloc<char>(0));
TEST_AND_FREE(UncheckedMalloc<char>(1));
TEST_AND_FREE(UncheckedMalloc(0));
TEST_AND_FREE(UncheckedMalloc(1));
TEST_AND_FREE(UncheckedMalloc<char>, 0);
TEST_AND_FREE(UncheckedMalloc<char>, 1);
TEST_AND_FREE(UncheckedMalloc, 0);
TEST_AND_FREE(UncheckedMalloc, 1);
}

TEST(UtilTest, UncheckedCalloc) {
TEST_AND_FREE(UncheckedCalloc<char>(0));
TEST_AND_FREE(UncheckedCalloc<char>(1));
TEST_AND_FREE(UncheckedCalloc(0));
TEST_AND_FREE(UncheckedCalloc(1));
TEST_AND_FREE(UncheckedCalloc<char>, 0);
TEST_AND_FREE(UncheckedCalloc<char>, 1);
TEST_AND_FREE(UncheckedCalloc, 0);
TEST_AND_FREE(UncheckedCalloc, 1);
}

template <typename T>
Expand Down