Skip to content

Commit 897cec4

Browse files
committed
fs: fix memory leak in WriteString
In the async case, if the buffer was copied instead of being moved then the buf will not get deleted after the request is done. This was introduced when the FSReqWrap:: Ownership was removed in 4b9ba9b, and ReleaseEarly was no longer called upon destruction of FSReqWrap. Create a custom Init function so we can use the MaybeStackBuffer in the FSReqBase to copy the string in the async case. The data will then get destructed when the FSReqBase is destructed. Fixes: #19356 PR-URL: #19357 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 9c93247 commit 897cec4

File tree

2 files changed

+46
-17
lines changed

2 files changed

+46
-17
lines changed

src/node_file.cc

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,7 +1527,6 @@ static void WriteString(const FunctionCallbackInfo<Value>& args) {
15271527

15281528
const auto enc = ParseEncoding(env->isolate(), args[3], UTF8);
15291529

1530-
std::unique_ptr<char[]> delete_on_return;
15311530
Local<Value> value = args[1];
15321531
char* buf = nullptr;
15331532
size_t len;
@@ -1555,24 +1554,42 @@ static void WriteString(const FunctionCallbackInfo<Value>& args) {
15551554
}
15561555
}
15571556

1558-
if (buf == nullptr) {
1557+
if (is_async) { // write(fd, string, pos, enc, req)
1558+
CHECK_NE(req_wrap, nullptr);
15591559
len = StringBytes::StorageSize(env->isolate(), value, enc);
1560-
buf = new char[len];
1561-
// SYNC_CALL returns on error. Make sure to always free the memory.
1562-
if (!is_async) delete_on_return.reset(buf);
1560+
FSReqBase::FSReqBuffer& stack_buffer =
1561+
req_wrap->Init("write", len, enc);
15631562
// StorageSize may return too large a char, so correct the actual length
15641563
// by the write size
1565-
len = StringBytes::Write(env->isolate(), buf, len, args[1], enc);
1566-
}
1567-
1568-
uv_buf_t uvbuf = uv_buf_init(buf, len);
1569-
1570-
if (is_async) { // write(fd, string, pos, enc, req)
1571-
AsyncCall(env, req_wrap, args, "write", UTF8, AfterInteger,
1572-
uv_fs_write, fd, &uvbuf, 1, pos);
1564+
len = StringBytes::Write(env->isolate(), *stack_buffer, len, args[1], enc);
1565+
stack_buffer.SetLengthAndZeroTerminate(len);
1566+
uv_buf_t uvbuf = uv_buf_init(*stack_buffer, len);
1567+
int err = uv_fs_write(env->event_loop(), req_wrap->req(),
1568+
fd, &uvbuf, 1, pos, AfterInteger);
1569+
req_wrap->Dispatched();
1570+
if (err < 0) {
1571+
uv_fs_t* uv_req = req_wrap->req();
1572+
uv_req->result = err;
1573+
uv_req->path = nullptr;
1574+
AfterInteger(uv_req); // after may delete req_wrap if there is an error
1575+
} else {
1576+
req_wrap->SetReturnValue(args);
1577+
}
15731578
} else { // write(fd, string, pos, enc, undefined, ctx)
15741579
CHECK_EQ(argc, 6);
15751580
fs_req_wrap req_wrap;
1581+
FSReqBase::FSReqBuffer stack_buffer;
1582+
if (buf == nullptr) {
1583+
len = StringBytes::StorageSize(env->isolate(), value, enc);
1584+
stack_buffer.AllocateSufficientStorage(len + 1);
1585+
// StorageSize may return too large a char, so correct the actual length
1586+
// by the write size
1587+
len = StringBytes::Write(env->isolate(), *stack_buffer,
1588+
len, args[1], enc);
1589+
stack_buffer.SetLengthAndZeroTerminate(len);
1590+
buf = *stack_buffer;
1591+
}
1592+
uv_buf_t uvbuf = uv_buf_init(buf, len);
15761593
int bytesWritten = SyncCall(env, args[5], &req_wrap, "write",
15771594
uv_fs_write, fd, &uvbuf, 1, pos);
15781595
args.GetReturnValue().Set(bytesWritten);

src/node_file.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ namespace fs {
2424

2525
class FSReqBase : public ReqWrap<uv_fs_t> {
2626
public:
27+
typedef MaybeStackBuffer<char, 64> FSReqBuffer;
28+
2729
FSReqBase(Environment* env, Local<Object> req, AsyncWrap::ProviderType type)
2830
: ReqWrap(env, req, type) {
2931
Wrap(object(), this);
@@ -34,9 +36,9 @@ class FSReqBase : public ReqWrap<uv_fs_t> {
3436
}
3537

3638
void Init(const char* syscall,
37-
const char* data = nullptr,
38-
size_t len = 0,
39-
enum encoding encoding = UTF8) {
39+
const char* data,
40+
size_t len,
41+
enum encoding encoding) {
4042
syscall_ = syscall;
4143
encoding_ = encoding;
4244

@@ -49,6 +51,16 @@ class FSReqBase : public ReqWrap<uv_fs_t> {
4951
}
5052
}
5153

54+
FSReqBuffer& Init(const char* syscall, size_t len,
55+
enum encoding encoding) {
56+
syscall_ = syscall;
57+
encoding_ = encoding;
58+
59+
buffer_.AllocateSufficientStorage(len + 1);
60+
has_data_ = false; // so that the data does not show up in error messages
61+
return buffer_;
62+
}
63+
5264
virtual void FillStatsArray(const uv_stat_t* stat) = 0;
5365
virtual void Reject(Local<Value> reject) = 0;
5466
virtual void Resolve(Local<Value> value) = 0;
@@ -68,7 +80,7 @@ class FSReqBase : public ReqWrap<uv_fs_t> {
6880

6981
// Typically, the content of buffer_ is something like a file name, so
7082
// something around 64 bytes should be enough.
71-
MaybeStackBuffer<char, 64> buffer_;
83+
FSReqBuffer buffer_;
7284

7385
DISALLOW_COPY_AND_ASSIGN(FSReqBase);
7486
};

0 commit comments

Comments
 (0)