Skip to content
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

tls: do not leak WriteWrap objects #1090

Closed
wants to merge 4 commits into from
Closed
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
26 changes: 26 additions & 0 deletions src/stream_base-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ using v8::FunctionTemplate;
using v8::Handle;
using v8::HandleScope;
using v8::Local;
using v8::Object;
using v8::PropertyAttribute;
using v8::PropertyCallbackInfo;
using v8::String;
Expand Down Expand Up @@ -82,6 +83,31 @@ void StreamBase::JSMethod(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set((wrap->*Method)(args));
}


WriteWrap* WriteWrap::New(Environment* env,
Local<Object> obj,
StreamBase* wrap,
DoneCb cb,
int extra) {
int storage_size = ROUND_UP(sizeof(WriteWrap), kAlignSize) + extra;
char* storage = new char[storage_size];

return new(storage) WriteWrap(env, obj, wrap, cb);
}


void WriteWrap::Dispose() {
this->~WriteWrap();
delete[] reinterpret_cast<char*>(this);
}


char* WriteWrap::Extra(int offset) {
return reinterpret_cast<char*>(this) +
ROUND_UP(sizeof(*this), kAlignSize) +
offset;
}

} // namespace node

#endif // SRC_STREAM_BASE_INL_H_
54 changes: 23 additions & 31 deletions src/stream_base.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "stream_base.h"
#include "stream_base-inl.h"
#include "stream_wrap.h"

#include "node.h"
Expand Down Expand Up @@ -108,6 +109,8 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
// Determine storage size first
size_t storage_size = 0;
for (size_t i = 0; i < count; i++) {
storage_size = ROUND_UP(storage_size, WriteWrap::kAlignSize);

Handle<Value> chunk = chunks->Get(i * 2);

if (Buffer::HasInstance(chunk))
Expand All @@ -124,7 +127,7 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
else
chunk_size = StringBytes::StorageSize(env->isolate(), string, encoding);

storage_size += chunk_size + 15;
storage_size += chunk_size;
}

if (storage_size > INT_MAX)
Expand All @@ -133,13 +136,14 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
if (ARRAY_SIZE(bufs_) < count)
bufs = new uv_buf_t[count];

storage_size += sizeof(WriteWrap);
char* storage = new char[storage_size];
WriteWrap* req_wrap =
new(storage) WriteWrap(env, req_wrap_obj, this, AfterWrite);
WriteWrap* req_wrap = WriteWrap::New(env,
req_wrap_obj,
this,
AfterWrite,
storage_size);

uint32_t bytes = 0;
size_t offset = sizeof(WriteWrap);
size_t offset = 0;
for (size_t i = 0; i < count; i++) {
Handle<Value> chunk = chunks->Get(i * 2);

Expand All @@ -152,9 +156,9 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
}

// Write string
offset = ROUND_UP(offset, 16);
offset = ROUND_UP(offset, WriteWrap::kAlignSize);
CHECK_LT(offset, storage_size);
char* str_storage = storage + offset;
char* str_storage = req_wrap->Extra(offset);
size_t str_size = storage_size - offset;

Handle<String> string = chunk->ToString(env->isolate());
Expand Down Expand Up @@ -187,10 +191,8 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
ClearError();
}

if (err) {
req_wrap->~WriteWrap();
delete[] storage;
}
if (err)
req_wrap->Dispose();

return err;
}
Expand All @@ -207,7 +209,6 @@ int StreamBase::WriteBuffer(const FunctionCallbackInfo<Value>& args) {
const char* data = Buffer::Data(args[1]);
size_t length = Buffer::Length(args[1]);

char* storage;
WriteWrap* req_wrap;
uv_buf_t buf;
buf.base = const_cast<char*>(data);
Expand All @@ -224,17 +225,14 @@ int StreamBase::WriteBuffer(const FunctionCallbackInfo<Value>& args) {
CHECK_EQ(count, 1);

// Allocate, or write rest
storage = new char[sizeof(WriteWrap)];
req_wrap = new(storage) WriteWrap(env, req_wrap_obj, this, AfterWrite);
req_wrap = WriteWrap::New(env, req_wrap_obj, this, AfterWrite);

err = DoWrite(req_wrap, bufs, count, nullptr);
req_wrap->Dispatched();
req_wrap_obj->Set(env->async(), True(env->isolate()));

if (err) {
req_wrap->~WriteWrap();
delete[] storage;
}
if (err)
req_wrap->Dispose();

done:
const char* msg = Error();
Expand Down Expand Up @@ -275,14 +273,13 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
return UV_ENOBUFS;

// Try writing immediately if write size isn't too big
char* storage;
WriteWrap* req_wrap;
char* data;
char stack_storage[16384]; // 16kb
size_t data_size;
uv_buf_t buf;

bool try_write = storage_size + 15 <= sizeof(stack_storage) &&
bool try_write = storage_size <= sizeof(stack_storage) &&
(!IsIPCPipe() || send_handle_obj.IsEmpty());
if (try_write) {
data_size = StringBytes::Write(env->isolate(),
Expand All @@ -308,11 +305,9 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
CHECK_EQ(count, 1);
}

storage = new char[sizeof(WriteWrap) + storage_size + 15];
req_wrap = new(storage) WriteWrap(env, req_wrap_obj, this, AfterWrite);
req_wrap = WriteWrap::New(env, req_wrap_obj, this, AfterWrite, storage_size);

data = reinterpret_cast<char*>(ROUND_UP(
reinterpret_cast<uintptr_t>(storage) + sizeof(WriteWrap), 16));
data = req_wrap->Extra();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis do you think this is still relevant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not - we may want to skip this 15 byte thing above too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is no relevant anymore. PTAL at follow-up commit.

if (try_write) {
// Copy partial data
Expand Down Expand Up @@ -355,10 +350,8 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
req_wrap->Dispatched();
req_wrap->object()->Set(env->async(), True(env->isolate()));

if (err) {
req_wrap->~WriteWrap();
delete[] storage;
}
if (err)
req_wrap->Dispose();

done:
const char* msg = Error();
Expand Down Expand Up @@ -404,8 +397,7 @@ void StreamBase::AfterWrite(WriteWrap* req_wrap, int status) {
if (req_wrap->object()->Has(env->oncomplete_string()))
req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv);

req_wrap->~WriteWrap();
delete[] reinterpret_cast<char*>(req_wrap);
req_wrap->Dispose();
}


Expand Down
27 changes: 18 additions & 9 deletions src/stream_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ class ShutdownWrap : public ReqWrap<uv_shutdown_t>,
class WriteWrap: public ReqWrap<uv_write_t>,
public StreamReq<WriteWrap> {
public:
static inline WriteWrap* New(Environment* env,
v8::Local<v8::Object> obj,
StreamBase* wrap,
DoneCb cb,
int extra = 0);
inline void Dispose();
inline char* Extra(int offset = 0);

inline StreamBase* wrap() const { return wrap_; }

static void NewWriteWrap(const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK(args.IsConstructCall());
}

static const int kAlignSize = 16;

protected:
WriteWrap(Environment* env,
v8::Local<v8::Object> obj,
StreamBase* wrap,
Expand All @@ -66,24 +83,16 @@ class WriteWrap: public ReqWrap<uv_write_t>,
Wrap(obj, this);
}

void* operator new(size_t size) = delete;
void* operator new(size_t size, char* storage) { return storage; }

// This is just to keep the compiler happy. It should never be called, since
// we don't use exceptions in node.
void operator delete(void* ptr, char* storage) { UNREACHABLE(); }

inline StreamBase* wrap() const {
return wrap_;
}

static void NewWriteWrap(const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK(args.IsConstructCall());
}

private:
// People should not be using the non-placement new and delete operator on a
// WriteWrap. Ensure this never happens.
void* operator new(size_t size) { UNREACHABLE(); }
void operator delete(void* ptr) { UNREACHABLE(); }

StreamBase* const wrap_;
Expand Down
11 changes: 6 additions & 5 deletions src/tls_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -295,16 +295,16 @@ void TLSWrap::EncOut() {

Local<Object> req_wrap_obj =
env()->write_wrap_constructor_function()->NewInstance();
char* storage = new char[sizeof(WriteWrap)];
WriteWrap* write_req = new(storage) WriteWrap(env(),
req_wrap_obj,
this,
EncOutCb);
WriteWrap* write_req = WriteWrap::New(env(),
req_wrap_obj,
this,
EncOutCb);

uv_buf_t buf[ARRAY_SIZE(data)];
for (size_t i = 0; i < count; i++)
buf[i] = uv_buf_init(data[i], size[i]);
int r = stream_->DoWrite(write_req, buf, count, nullptr);
write_req->Dispatched();

// Ignore errors, this should be already handled in js
if (!r)
Expand All @@ -314,6 +314,7 @@ void TLSWrap::EncOut() {

void TLSWrap::EncOutCb(WriteWrap* req_wrap, int status) {
TLSWrap* wrap = req_wrap->wrap()->Cast<TLSWrap>();
req_wrap->Dispose();

// Handle error
if (status) {
Expand Down