New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: simplify memory management using `node::Malloc()` and friends #8482

Merged
merged 4 commits into from Sep 29, 2016

Conversation

Projects
None yet
9 participants
@addaleax
Member

addaleax commented Sep 10, 2016

Checklist
  • make -j4 test (UNIX), or vcbuild test nosign (Windows) passes
  • commit message follows commit guidelines
Affected core subsystem(s)

src

Description of change
  • Add an optional size param to node::Malloc() and node::Realloc() in calloc(3)-style and use proper overflow detection.
  • Make the allocation methods templates so that they directly give the correct return type, removing a number of static_casts to the desired pointer types.
  • Add shortcuts for the node::Malloc() + CHECK_NE(·, nullptr) combinations that often occur together in the codebase.
  • Call v8::Isolate::GetCurrent()->LowMemoryNotification() when an allocation fails to give V8 a chance to clean up and return memory before retrying (and possibly giving up).

Any of the changes here can be left out but I believe that they all pretty much make sense to have.

CI: https://ci.nodejs.org/job/node-test-commit/4994/
CI: https://ci.nodejs.org/job/node-test-commit/4995/
CI: https://ci.nodejs.org/job/node-test-commit/4996/
CI: https://ci.nodejs.org/job/node-test-commit/4997/
CI: https://ci.nodejs.org/job/node-test-commit/5008/
CI: https://ci.nodejs.org/job/node-test-commit/5013/
CI: https://ci.nodejs.org/job/node-test-commit/5025/

@addaleax addaleax added the lib / src label Sep 10, 2016

@addaleax addaleax force-pushed the addaleax:allocation-rework branch 3 times, most recently Sep 10, 2016

@addaleax

This comment has been minimized.

Member

addaleax commented Sep 10, 2016

Woooo, internal compiler error on Windows: https://ci.nodejs.org/job/node-compile-windows/4115/label=win-vs2015/console

Edit for future reference: This happened with HEAD = f04c5ed4bc1ae3dcb639da469605ae1ea47ca612

One can probably eliminate that by playing with the code a bit, but maybe this is interesting for someone from @nodejs/platform-windows ?

@addaleax

This comment has been minimized.

Member

addaleax commented Sep 10, 2016

Also – @joshgav do you want to/should you be added to the platform-windows team?

@bnoordhuis

View changes

src/cares_wrap.cc Outdated
@@ -142,8 +142,7 @@ static void ares_poll_close_cb(uv_handle_t* watcher) {

/* Allocates and returns a new node_ares_task */
static node_ares_task* ares_task_create(Environment* env, ares_socket_t sock) {
node_ares_task* task =
static_cast<node_ares_task*>(node::Malloc(sizeof(*task)));
node_ares_task* task = node::Malloc<node_ares_task>(1);

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 11, 2016

Member

You're welcome to write this as auto task = ... while you're here.

This comment has been minimized.

@addaleax
@bnoordhuis

View changes

src/node_buffer.cc Outdated
@@ -50,7 +50,8 @@
size_t length = end - start;

#define BUFFER_MALLOC(length) \
zero_fill_all_buffers ? node::Calloc(length, 1) : node::Malloc(length)
zero_fill_all_buffers ? node::Calloc<void>(length) : \
node::Malloc<void>(length)

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 11, 2016

Member

Maybe turn this into a function.

This comment has been minimized.

@addaleax
@bnoordhuis

View changes

src/util-inl.h Outdated
T ret;

#if NODE_GNUC_AT_LEAST(5, 0, 0) || __has_builtin(__builtin_mul_overflow)
CHECK(!__builtin_mul_overflow(a, b, &ret));

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 11, 2016

Member

I'm not a big fan of using compiler builtins when plain C++ suffices. Is there a dramatic, real world performance difference? If not, I would suggest dropping this.

This comment has been minimized.

@addaleax

addaleax Sep 11, 2016

Member

I'm not a big fan of using compiler builtins when plain C++ suffices. Is there a dramatic, real world performance difference? If not, I would suggest dropping this.

Hmm, yeah. I’ve removed it. I’d guess any performance difference it makes would pale in comparison to the realloc() call itself.

@bnoordhuis

View changes

src/util-inl.h Outdated
#else
ret = a * b;
if (a != 0)
CHECK_EQ(b, ret / a);

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 11, 2016

Member

Wouldn't CHECK_GE(ret, a) be sufficient?

This comment has been minimized.

@addaleax

addaleax Sep 11, 2016

Member

Wouldn't CHECK_GE(ret, a) be sufficient?

No, unfortunately not. uint8 example: in 140 * 3 == 164 the multiplication wraps around, but a CHECK_GE(164, 140) would pass.

@bnoordhuis

View changes

src/util-inl.h Outdated
#define __has_builtin(x) (0)
#endif

template<typename T>

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 11, 2016

Member

Tiny style nit but in most places we write template <typename T> (space before the <.)

This comment has been minimized.

@addaleax

addaleax Sep 11, 2016

Member

Tiny style nit but in most places we write template <typename T> (space before the <.)

Yeah… I’m always forgetting that. 😄

@bnoordhuis

View changes

src/util.cc Outdated
@@ -76,4 +77,10 @@ BufferValue::BufferValue(Isolate* isolate, Local<Value> value) {
}
}

void LowMemoryNotification() {
if (v8_initialized) {
v8::Isolate::GetCurrent()->LowMemoryNotification();

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 11, 2016

Member

Can you check that v8::Isolate::GetCurrent() != nullptr in case it's called from a different thread?

This comment has been minimized.

@addaleax
@bnoordhuis

View changes

src/util.h Outdated
template<typename T>
inline T* CheckedMalloc(size_t n, size_t size = size_of<T>::value);
template<typename T>
inline T* CheckedCalloc(size_t n, size_t size = size_of<T>::value);

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 11, 2016

Member

I would like to make two suggestions:

  1. Make checked alloc/realloc the default. IOW, make unchecked opt-in instead of opt-out. There is almost never any going forward when memory allocation fails.
  2. For programmer economy, provide non-template versions that default to T = char.

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 11, 2016

Member

Also, when would you call this with size != sizeof(T)? If the answer is 'never', can you drop the size parameter?

This comment has been minimized.

@addaleax

addaleax Sep 11, 2016

Member

Make checked alloc/realloc the default. IOW, make unchecked opt-in instead of opt-out. There is almost never any going forward when memory allocation fails.

👍

For programmer economy, provide non-template versions that default to T = char.

Done, with the exception of Realloc which doesn’t need to be explicitly called as a template.

If the answer is 'never', can you drop the size parameter?

Done… the struct size_of still seems necessary, though, to handle Malloc<void>.

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 12, 2016

Member

Done… the struct size_of still seems necessary, though, to handle Malloc.

I think in most cases you can replace it with the non-template version that returns a char*. C++ prohibits implicit upcasts from void* to e.g. char* but the reverse is allowed.

This comment has been minimized.

@addaleax

addaleax Sep 12, 2016

Member

I think in most cases you can replace it with the non-template version that returns a char*. C++ prohibits implicit upcasts from void* to e.g. char* but the reverse is allowed.

You’re right, makes sense. :)

@bnoordhuis

This comment has been minimized.

Member

bnoordhuis commented Sep 11, 2016

Woooo, internal compiler error on Windows: https://ci.nodejs.org/job/node-compile-windows/4115/label=win-vs2015/console

Interesting. For all its faults I've never gotten VS to ICE.

@seishun

This comment has been minimized.

Member

seishun commented Sep 11, 2016

I'm not getting an ICE locally. Maybe try updating VS on CI?

@addaleax addaleax force-pushed the addaleax:allocation-rework branch 2 times, most recently Sep 11, 2016

@addaleax

This comment has been minimized.

Member

addaleax commented Sep 11, 2016

Trying to run a new CI at https://ci.nodejs.org/job/node-test-pull-request/4012/ but it seems to be stuck reading data from Github?

Edit: https://ci.nodejs.org/job/node-test-commit/5008/

@bnoordhuis

View changes

src/stream_wrap.cc Outdated
@@ -204,7 +198,7 @@ void StreamWrap::OnReadImpl(ssize_t nread,
return;
}

char* base = static_cast<char*>(node::Realloc(buf->base, nread));
char* base = node::UncheckedRealloc(buf->base, nread);

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 12, 2016

Member

I'd make this a checked realloc. It can't really fail because it shrinks but I doubt the extra check hurts.

@bnoordhuis

View changes

src/stream_wrap.cc Outdated
@@ -204,7 +198,7 @@ void StreamWrap::OnReadImpl(ssize_t nread,
return;
}

char* base = static_cast<char*>(node::Realloc(buf->base, nread));
char* base = node::UncheckedRealloc(buf->base, nread);
CHECK_LE(static_cast<size_t>(nread), buf->len);

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 12, 2016

Member

Also, this CHECK should really have gone before the realloc... Ah well, existing code.

This comment has been minimized.

@addaleax

addaleax Sep 12, 2016

Member

I’ve moved the existing check and turned the Realloc into a checked one, I guess that makes sense.

@addaleax addaleax force-pushed the addaleax:allocation-rework branch Sep 12, 2016

@addaleax

This comment has been minimized.

Member

addaleax commented Sep 12, 2016

@bnoordhuis

View changes

src/util-inl.h Outdated
T MultiplyWithOverflowCheck(T a, T b) {
T ret = a * b;
if (a != 0)
CHECK_EQ(b, ret / a);

This comment has been minimized.

@bnoordhuis

bnoordhuis Sep 12, 2016

Member

EDIT: No, I got that wrong. The branch is always taken because a == sizeof(T) and therefore never zero.

@bnoordhuis

This comment has been minimized.

Member

bnoordhuis commented Sep 12, 2016

LGTM

@seishun

View changes

src/util-inl.h Outdated
@@ -229,30 +229,74 @@ bool StringEqualNoCaseN(const char* a, const char* b, size_t length) {
return true;
}

template <typename T>
T MultiplyWithOverflowCheck(T a, T b) {

This comment has been minimized.

@seishun

seishun Sep 12, 2016

Member

I don't think making this a template is a good idea. It can't be used with signed types since signed overflow is UB, so the check could be optimized out.

This comment has been minimized.

@addaleax

addaleax Sep 12, 2016

Member

@seishun How strongly do you feel about this? I don’t really care but it might be nice to have a generic utility if one is ever needed again. I can add a static_assert to check for unsignedness, if you want.

Also, I’m surprised, but your worries are not only theoretical; clang actually would optimize the check away for signed integers… oO

This comment has been minimized.

@seishun

seishun Sep 13, 2016

Member

I can add a static_assert to check for unsignedness, if you want.

That works, but I would personally prefer to have a function that works just with size_t. That way you can see that it doesn't work with signed values from the signature. Your call, though.

This comment has been minimized.

@addaleax

addaleax Sep 13, 2016

Member

@seishun ¯_(ツ)_/¯ updated with size_t :)

@addaleax addaleax force-pushed the addaleax:allocation-rework branch Sep 13, 2016

@jasnell

LGTM

@addaleax addaleax added the blocked label Sep 17, 2016

@addaleax addaleax referenced this pull request Sep 17, 2016

Closed

src: Malloc/Calloc size 0 returns non-null pointer #8572

3 of 3 tasks complete

jasnell added a commit that referenced this pull request Sep 29, 2016

src: add Malloc() size param + overflow detection
Adds an optional second parameter to `node::Malloc()` and
an optional third parameter to `node::Realloc()` giving the
size/number of items to be allocated, in the style of `calloc(3)`.

Use a proper overflow check using division;
the previous `CHECK_GE(n * size, n);` would not detect all cases
of overflow (e.g. `size == SIZE_MAX / 2 && n == 3`).

PR-URL: #8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

jasnell added a commit that referenced this pull request Sep 29, 2016

src: pass desired return type to allocators
Pass the desired return type directly to the allocation functions,
so that the resulting `static_cast` from `void*` becomes unneccessary
and the return type can be use as a reasonable default value for the
`size` parameter.

PR-URL: #8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

jasnell added a commit that referenced this pull request Sep 29, 2016

src: provide allocation + nullptr check shortcuts
Provide shortcut `node::CheckedMalloc()` and friends that
replace `node::Malloc()` + `CHECK_NE(·, nullptr);` combinations
in a few places.

PR-URL: #8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

jasnell added a commit that referenced this pull request Sep 29, 2016

src: notify V8 for low memory when alloc fails
Call `v8::Isolate::GetCurrent()->LowMemoryNotification()` when
an allocation fails to give V8 a chance to clean up and return
memory before retrying (and possibly giving up).

PR-URL: #8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

@addaleax addaleax deleted the addaleax:allocation-rework branch Sep 30, 2016

@MylesBorins

This comment has been minimized.

Member

MylesBorins commented Oct 6, 2016

@addaleax I've set this as do not land... please feel free to change if it should be backported

@addaleax addaleax referenced this pull request Oct 7, 2016

Closed

core: normalize malloc, realloc #7564

2 of 2 tasks complete
@Fishrock123

This comment has been minimized.

Member

Fishrock123 commented Oct 10, 2016

depends on #7082

bnoordhuis added a commit to bnoordhuis/io.js that referenced this pull request Nov 18, 2016

test: fix memory leaks in malloc cctests
Make cctest valgrind-clean again by freeing heap-allocated memory.
Overlooked in commit ea94086 ("src: provide allocation + nullptr check
shortcuts.")

PR-URL: nodejs#9667
Refs: nodejs#8482
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>

addaleax added a commit that referenced this pull request Nov 22, 2016

test: fix memory leaks in malloc cctests
Make cctest valgrind-clean again by freeing heap-allocated memory.
Overlooked in commit ea94086 ("src: provide allocation + nullptr check
shortcuts.")

PR-URL: #9667
Refs: #8482
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>

@gibfahn gibfahn referenced this pull request Jun 15, 2017

Closed

Auditing for 6.11.1 #230

2 of 3 tasks complete

bnoordhuis added a commit to bnoordhuis/io.js that referenced this pull request Oct 29, 2017

src: add Malloc() size param + overflow detection
Adds an optional second parameter to `node::Malloc()` and
an optional third parameter to `node::Realloc()` giving the
size/number of items to be allocated, in the style of `calloc(3)`.

Use a proper overflow check using division;
the previous `CHECK_GE(n * size, n);` would not detect all cases
of overflow (e.g. `size == SIZE_MAX / 2 && n == 3`).

PR-URL: nodejs#8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

bnoordhuis added a commit to bnoordhuis/io.js that referenced this pull request Oct 29, 2017

src: pass desired return type to allocators
Pass the desired return type directly to the allocation functions,
so that the resulting `static_cast` from `void*` becomes unneccessary
and the return type can be use as a reasonable default value for the
`size` parameter.

PR-URL: nodejs#8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

bnoordhuis added a commit to bnoordhuis/io.js that referenced this pull request Oct 29, 2017

src: provide allocation + nullptr check shortcuts
Provide shortcut `node::CheckedMalloc()` and friends that
replace `node::Malloc()` + `CHECK_NE(·, nullptr);` combinations
in a few places.

PR-URL: nodejs#8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

bnoordhuis added a commit to bnoordhuis/io.js that referenced this pull request Oct 29, 2017

src: notify V8 for low memory when alloc fails
Call `v8::Isolate::GetCurrent()->LowMemoryNotification()` when
an allocation fails to give V8 a chance to clean up and return
memory before retrying (and possibly giving up).

PR-URL: nodejs#8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>
@bnoordhuis

This comment has been minimized.

Member

bnoordhuis commented Oct 29, 2017

v6.x: #16587

Removed the dont-land-on-v6.x label.

MylesBorins added a commit that referenced this pull request Nov 14, 2017

src: add Malloc() size param + overflow detection
Adds an optional second parameter to `node::Malloc()` and
an optional third parameter to `node::Realloc()` giving the
size/number of items to be allocated, in the style of `calloc(3)`.

Use a proper overflow check using division;
the previous `CHECK_GE(n * size, n);` would not detect all cases
of overflow (e.g. `size == SIZE_MAX / 2 && n == 3`).

Backport-PR-URL: #16587
PR-URL: #8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

MylesBorins added a commit that referenced this pull request Nov 14, 2017

src: pass desired return type to allocators
Pass the desired return type directly to the allocation functions,
so that the resulting `static_cast` from `void*` becomes unneccessary
and the return type can be use as a reasonable default value for the
`size` parameter.

Backport-PR-URL: #16587
PR-URL: #8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

MylesBorins added a commit that referenced this pull request Nov 14, 2017

src: provide allocation + nullptr check shortcuts
Provide shortcut `node::CheckedMalloc()` and friends that
replace `node::Malloc()` + `CHECK_NE(·, nullptr);` combinations
in a few places.

Backport-PR-URL: #16587
PR-URL: #8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

MylesBorins added a commit that referenced this pull request Nov 14, 2017

src: notify V8 for low memory when alloc fails
Call `v8::Isolate::GetCurrent()->LowMemoryNotification()` when
an allocation fails to give V8 a chance to clean up and return
memory before retrying (and possibly giving up).

Backport-PR-URL: #16587
PR-URL: #8482
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>

@MylesBorins MylesBorins referenced this pull request Nov 21, 2017

Merged

v6.12.1 proposal #17180

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment