-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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: fix memory leak in ExternString #2402
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,13 +22,14 @@ using v8::Local; | |
using v8::Object; | ||
using v8::String; | ||
using v8::Value; | ||
using v8::MaybeLocal; | ||
|
||
|
||
template <typename ResourceType, typename TypeName> | ||
class ExternString: public ResourceType { | ||
public: | ||
~ExternString() override { | ||
delete[] data_; | ||
free(const_cast<TypeName*>(data_)); | ||
isolate()->AdjustAmountOfExternalAllocatedMemory(-byte_length()); | ||
} | ||
|
||
|
@@ -52,7 +53,11 @@ class ExternString: public ResourceType { | |
if (length == 0) | ||
return scope.Escape(String::Empty(isolate)); | ||
|
||
TypeName* new_data = new TypeName[length]; | ||
TypeName* new_data = | ||
static_cast<TypeName*>(malloc(length * sizeof(*new_data))); | ||
if (new_data == nullptr) { | ||
return Local<String>(); | ||
} | ||
memcpy(new_data, data, length * sizeof(*new_data)); | ||
|
||
return scope.Escape(ExternString<ResourceType, TypeName>::New(isolate, | ||
|
@@ -72,10 +77,15 @@ class ExternString: public ResourceType { | |
ExternString* h_str = new ExternString<ResourceType, TypeName>(isolate, | ||
data, | ||
length); | ||
Local<String> str = String::NewExternal(isolate, h_str); | ||
MaybeLocal<String> str = String::NewExternal(isolate, h_str); | ||
isolate->AdjustAmountOfExternalAllocatedMemory(h_str->byte_length()); | ||
|
||
return scope.Escape(str); | ||
if (str.IsEmpty()) { | ||
delete h_str; | ||
return Local<String>(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't NewFromCopy() have the same check? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NewFromCopy uses New There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but it leaks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because it's the copy of the data? Maybe I'm misunderstanding the question. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using var a = [];
var f = false;
while (!f) {
try {
a.push(new Buffer(1024 * 1024 * 1024).fill('a'));
} catch (e) {
f = true;
}
}
console.log(a[a.length - 1].length); // 1073741824
console.log(a[a.length - 1].toString()); // undefined So There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your example code does not use Example code with
If
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @skomski Thanks for the test fix. That's actually what I expected, but I ignored whether the value was intercepted in transit. So my comment about handling this case still stands. Don't want to see aborts on devices that have little memory (e.g. raspberry pi). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Complaint withdrawn, I misread the code. Aside, it's kind of weird that both New() and NewFromCopy() call AdjustAmountOfExternalAllocatedMemory() but depend on the destructor to lower it again. I'd expect the constructor to take care of that, now the logic is duplicated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only It would make sense to only call |
||
|
||
return scope.Escape(str.ToLocalChecked()); | ||
} | ||
|
||
inline Isolate* isolate() const { return isolate_; } | ||
|
@@ -765,11 +775,14 @@ Local<Value> StringBytes::Encode(Isolate* isolate, | |
|
||
case ASCII: | ||
if (contains_non_ascii(buf, buflen)) { | ||
char* out = new char[buflen]; | ||
char* out = static_cast<char*>(malloc(buflen)); | ||
if (out == nullptr) { | ||
return Local<String>(); | ||
} | ||
force_ascii(buf, out, buflen); | ||
if (buflen < EXTERN_APEX) { | ||
val = OneByteString(isolate, out, buflen); | ||
delete[] out; | ||
free(out); | ||
} else { | ||
val = ExternOneByteString::New(isolate, out, buflen); | ||
} | ||
|
@@ -797,14 +810,17 @@ Local<Value> StringBytes::Encode(Isolate* isolate, | |
|
||
case BASE64: { | ||
size_t dlen = base64_encoded_size(buflen); | ||
char* dst = new char[dlen]; | ||
char* dst = static_cast<char*>(malloc(dlen)); | ||
if (dst == nullptr) { | ||
return Local<String>(); | ||
} | ||
|
||
size_t written = base64_encode(buf, buflen, dst, dlen); | ||
CHECK_EQ(written, dlen); | ||
|
||
if (dlen < EXTERN_APEX) { | ||
val = OneByteString(isolate, dst, dlen); | ||
delete[] dst; | ||
free(dst); | ||
} else { | ||
val = ExternOneByteString::New(isolate, dst, dlen); | ||
} | ||
|
@@ -813,13 +829,16 @@ Local<Value> StringBytes::Encode(Isolate* isolate, | |
|
||
case HEX: { | ||
size_t dlen = buflen * 2; | ||
char* dst = new char[dlen]; | ||
char* dst = static_cast<char*>(malloc(dlen)); | ||
if (dst == nullptr) { | ||
return Local<String>(); | ||
} | ||
size_t written = hex_encode(buf, buflen, dst, dlen); | ||
CHECK_EQ(written, dlen); | ||
|
||
if (dlen < EXTERN_APEX) { | ||
val = OneByteString(isolate, dst, dlen); | ||
delete[] dst; | ||
free(dst); | ||
} else { | ||
val = ExternOneByteString::New(isolate, dst, dlen); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-declaration of
result
in different branches.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's legal JS and we do it in other places as well.