From 41550fd519db25a69fb4f6baa59e5b00e4aeeefa Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Tue, 31 Oct 2017 10:45:52 -0700 Subject: [PATCH] [LEAK FIX] Use Nan::ObjectWrap to handle memory management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nan::ObjectWrap Docs: https://github.com/nodejs/nan/blob/master/doc/object_wrappers.md#api_nan_object_wrap Prior to this, all wrapper Objects would never be deleted, allowing memory to grow unbounded. Example: The following, would leak WrapperObjects, and never ``` while (true) { new sass.types.String(‘I LEAK’); } ``` --- src/custom_function_bridge.cpp | 13 ++- src/sass_types/boolean.cpp | 20 ++-- src/sass_types/boolean.h | 3 +- src/sass_types/color.cpp | 16 +-- src/sass_types/factory.cpp | 11 +- src/sass_types/list.cpp | 10 +- src/sass_types/map.cpp | 15 +-- src/sass_types/null.cpp | 8 +- src/sass_types/number.cpp | 8 +- src/sass_types/sass_value_wrapper.h | 152 +++++++++++----------------- src/sass_types/string.cpp | 4 +- src/sass_types/value.h | 29 +++++- 12 files changed, 141 insertions(+), 148 deletions(-) diff --git a/src/custom_function_bridge.cpp b/src/custom_function_bridge.cpp index f0e49b6c8..f27c69545 100644 --- a/src/custom_function_bridge.cpp +++ b/src/custom_function_bridge.cpp @@ -4,10 +4,10 @@ #include "sass_types/factory.h" #include "sass_types/value.h" -Sass_Value* CustomFunctionBridge::post_process_return_value(v8::Local val) const { - SassTypes::Value *v_; - if ((v_ = SassTypes::Factory::unwrap(val))) { - return v_->get_sass_value(); +Sass_Value* CustomFunctionBridge::post_process_return_value(v8::Local _val) const { + SassTypes::Value *value = SassTypes::Factory::unwrap(_val); + if (value) { + return value->get_sass_value(); } else { return sass_make_error("A SassValue object was expected."); } @@ -17,7 +17,10 @@ std::vector> CustomFunctionBridge::pre_process_args(std::ve std::vector> argv = std::vector>(); for (void* value : in) { - argv.push_back(SassTypes::Factory::create(static_cast(value))->get_js_object()); + Sass_Value* x = static_cast(value); + SassTypes::Value* y = SassTypes::Factory::create(x); + + argv.push_back(y->get_js_object()); } return argv; diff --git a/src/sass_types/boolean.cpp b/src/sass_types/boolean.cpp index 401ed14fb..2d4793238 100644 --- a/src/sass_types/boolean.cpp +++ b/src/sass_types/boolean.cpp @@ -6,7 +6,9 @@ namespace SassTypes Nan::Persistent Boolean::constructor; bool Boolean::constructor_locked = false; - Boolean::Boolean(bool v) : value(v) {} + Boolean::Boolean(bool _value) { + value = sass_make_boolean(_value); + } Boolean& Boolean::get_singleton(bool v) { static Boolean instance_false(false), instance_true(true); @@ -15,7 +17,7 @@ namespace SassTypes v8::Local Boolean::get_constructor() { Nan::EscapableHandleScope scope; - v8::Local conslocal; + v8::Local conslocal; if (constructor.IsEmpty()) { v8::Local tpl = Nan::New(New); @@ -42,16 +44,15 @@ namespace SassTypes return scope.Escape(conslocal); } - Sass_Value* Boolean::get_sass_value() { - return sass_make_boolean(value); - } - v8::Local Boolean::get_js_object() { return Nan::New(this->js_object); } - NAN_METHOD(Boolean::New) { + v8::Local Boolean::get_js_boolean() { + return sass_boolean_get_value(this->value) ? Nan::True() : Nan::False(); + } + NAN_METHOD(Boolean::New) { if (info.IsConstructCall()) { if (constructor_locked) { return Nan::ThrowTypeError("Cannot instantiate SassBoolean"); @@ -67,9 +68,6 @@ namespace SassTypes } NAN_METHOD(Boolean::GetValue) { - Boolean *out; - if ((out = static_cast(Factory::unwrap(info.This())))) { - info.GetReturnValue().Set(Nan::New(out->value)); - } + info.GetReturnValue().Set(Boolean::Unwrap(info.This())->get_js_boolean()); } } diff --git a/src/sass_types/boolean.h b/src/sass_types/boolean.h index ac2a254ff..721a41c02 100644 --- a/src/sass_types/boolean.h +++ b/src/sass_types/boolean.h @@ -12,7 +12,6 @@ namespace SassTypes static Boolean& get_singleton(bool); static v8::Local get_constructor(); - Sass_Value* get_sass_value(); v8::Local get_js_object(); static NAN_METHOD(New); @@ -21,11 +20,11 @@ namespace SassTypes private: Boolean(bool); - bool value; Nan::Persistent js_object; static Nan::Persistent constructor; static bool constructor_locked; + v8::Local get_js_boolean(); }; } diff --git a/src/sass_types/color.cpp b/src/sass_types/color.cpp index f484b196e..57efab3da 100644 --- a/src/sass_types/color.cpp +++ b/src/sass_types/color.cpp @@ -62,19 +62,19 @@ namespace SassTypes } NAN_METHOD(Color::GetR) { - info.GetReturnValue().Set(sass_color_get_r(unwrap(info.This())->value)); + info.GetReturnValue().Set(sass_color_get_r(Color::Unwrap(info.This())->value)); } NAN_METHOD(Color::GetG) { - info.GetReturnValue().Set(sass_color_get_g(unwrap(info.This())->value)); + info.GetReturnValue().Set(sass_color_get_g(Color::Unwrap(info.This())->value)); } NAN_METHOD(Color::GetB) { - info.GetReturnValue().Set(sass_color_get_b(unwrap(info.This())->value)); + info.GetReturnValue().Set(sass_color_get_b(Color::Unwrap(info.This())->value)); } NAN_METHOD(Color::GetA) { - info.GetReturnValue().Set(sass_color_get_a(unwrap(info.This())->value)); + info.GetReturnValue().Set(sass_color_get_a(Color::Unwrap(info.This())->value)); } NAN_METHOD(Color::SetR) { @@ -86,7 +86,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_color_set_r(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_color_set_r(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } NAN_METHOD(Color::SetG) { @@ -98,7 +98,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_color_set_g(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_color_set_g(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } NAN_METHOD(Color::SetB) { @@ -110,7 +110,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_color_set_b(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_color_set_b(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } NAN_METHOD(Color::SetA) { @@ -122,6 +122,6 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_color_set_a(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_color_set_a(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } } diff --git a/src/sass_types/factory.cpp b/src/sass_types/factory.cpp index 98bda5d58..c650710c3 100644 --- a/src/sass_types/factory.cpp +++ b/src/sass_types/factory.cpp @@ -61,11 +61,12 @@ namespace SassTypes } Value* Factory::unwrap(v8::Local obj) { - // Todo: non-SassValue objects could easily fall under that condition, need to be more specific. - if (!obj->IsObject() || obj.As()->InternalFieldCount() != 1) { + if (obj->IsObject()) { + v8::Local v8_obj = obj.As(); + if (v8_obj->InternalFieldCount() == 1) { + return SassTypes::Value::Unwrap(v8_obj); + } + } return NULL; - } - - return static_cast(Nan::GetInternalFieldPointer(obj.As(), 0)); } } diff --git a/src/sass_types/list.cpp b/src/sass_types/list.cpp index cc94729a3..4c946ec90 100644 --- a/src/sass_types/list.cpp +++ b/src/sass_types/list.cpp @@ -47,7 +47,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied index should be an integer"); } - Sass_Value* list = unwrap(info.This())->value; + Sass_Value* list = List::Unwrap(info.This())->value; size_t index = Nan::To(info[0]).FromJust(); @@ -73,14 +73,14 @@ namespace SassTypes Value* sass_value = Factory::unwrap(info[1]); if (sass_value) { - sass_list_set_value(unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); + sass_list_set_value(List::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); } else { Nan::ThrowTypeError("A SassValue is expected as the list item"); } } NAN_METHOD(List::GetSeparator) { - info.GetReturnValue().Set(sass_list_get_separator(unwrap(info.This())->value) == SASS_COMMA); + info.GetReturnValue().Set(sass_list_get_separator(List::Unwrap(info.This())->value) == SASS_COMMA); } NAN_METHOD(List::SetSeparator) { @@ -92,10 +92,10 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a boolean"); } - sass_list_set_separator(unwrap(info.This())->value, Nan::To(info[0]).FromJust() ? SASS_COMMA : SASS_SPACE); + sass_list_set_separator(List::Unwrap(info.This())->value, Nan::To(info[0]).FromJust() ? SASS_COMMA : SASS_SPACE); } NAN_METHOD(List::GetLength) { - info.GetReturnValue().Set(Nan::New(sass_list_get_length(unwrap(info.This())->value))); + info.GetReturnValue().Set(Nan::New(sass_list_get_length(List::Unwrap(info.This())->value))); } } diff --git a/src/sass_types/map.cpp b/src/sass_types/map.cpp index ad147e6f9..ae4a260bf 100644 --- a/src/sass_types/map.cpp +++ b/src/sass_types/map.cpp @@ -37,7 +37,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied index should be an integer"); } - Sass_Value* map = unwrap(info.This())->value; + Sass_Value* map = Map::Unwrap(info.This())->value; size_t index = Nan::To(info[0]).FromJust(); @@ -63,14 +63,13 @@ namespace SassTypes Value* sass_value = Factory::unwrap(info[1]); if (sass_value) { - sass_map_set_value(unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); + sass_map_set_value(Map::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); } else { Nan::ThrowTypeError("A SassValue is expected as a map value"); } } NAN_METHOD(Map::GetKey) { - if (info.Length() != 1) { return Nan::ThrowTypeError("Expected just one argument"); } @@ -79,7 +78,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied index should be an integer"); } - Sass_Value* map = unwrap(info.This())->value; + Sass_Value* map = Map::Unwrap(info.This())->value; size_t index = Nan::To(info[0]).FromJust(); @@ -87,7 +86,9 @@ namespace SassTypes return Nan::ThrowRangeError(Nan::New("Out of bound index").ToLocalChecked()); } - info.GetReturnValue().Set(Factory::create(sass_map_get_key(map, Nan::To(info[0]).FromJust()))->get_js_object()); + SassTypes::Value* obj = Factory::create(sass_map_get_key(map, Nan::To(info[0]).FromJust())); + v8::Local js_obj = obj->get_js_object(); + info.GetReturnValue().Set(js_obj); } NAN_METHOD(Map::SetKey) { @@ -105,13 +106,13 @@ namespace SassTypes Value* sass_value = Factory::unwrap(info[1]); if (sass_value) { - sass_map_set_key(unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); + sass_map_set_key(Map::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); } else { Nan::ThrowTypeError("A SassValue is expected as a map key"); } } NAN_METHOD(Map::GetLength) { - info.GetReturnValue().Set(Nan::New(sass_map_get_length(unwrap(info.This())->value))); + info.GetReturnValue().Set(Nan::New(sass_map_get_length(Map::Unwrap(info.This())->value))); } } diff --git a/src/sass_types/null.cpp b/src/sass_types/null.cpp index 766cdf935..69f4c216d 100644 --- a/src/sass_types/null.cpp +++ b/src/sass_types/null.cpp @@ -6,7 +6,9 @@ namespace SassTypes Nan::Persistent Null::constructor; bool Null::constructor_locked = false; - Null::Null() {} + Null::Null() { + value = sass_make_null(); + } Null& Null::get_singleton() { static Null singleton_instance; @@ -37,10 +39,6 @@ namespace SassTypes return scope.Escape(conslocal); } - Sass_Value* Null::get_sass_value() { - return sass_make_null(); - } - v8::Local Null::get_js_object() { return Nan::New(this->js_object); } diff --git a/src/sass_types/number.cpp b/src/sass_types/number.cpp index d74a0d14d..d8d303ee0 100644 --- a/src/sass_types/number.cpp +++ b/src/sass_types/number.cpp @@ -41,11 +41,11 @@ namespace SassTypes } NAN_METHOD(Number::GetValue) { - info.GetReturnValue().Set(Nan::New(sass_number_get_value(unwrap(info.This())->value))); + info.GetReturnValue().Set(Nan::New(sass_number_get_value(Number::Unwrap(info.This())->value))); } NAN_METHOD(Number::GetUnit) { - info.GetReturnValue().Set(Nan::New(sass_number_get_unit(unwrap(info.This())->value)).ToLocalChecked()); + info.GetReturnValue().Set(Nan::New(sass_number_get_unit(Number::Unwrap(info.This())->value)).ToLocalChecked()); } NAN_METHOD(Number::SetValue) { @@ -58,7 +58,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_number_set_value(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_number_set_value(Number::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } NAN_METHOD(Number::SetUnit) { @@ -70,6 +70,6 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a string"); } - sass_number_set_unit(unwrap(info.This())->value, create_string(info[0])); + sass_number_set_unit(Number::Unwrap(info.This())->value, create_string(info[0])); } } diff --git a/src/sass_types/sass_value_wrapper.h b/src/sass_types/sass_value_wrapper.h index 54eb16a02..52a351187 100644 --- a/src/sass_types/sass_value_wrapper.h +++ b/src/sass_types/sass_value_wrapper.h @@ -12,122 +12,90 @@ namespace SassTypes // Include this in any SassTypes::Value subclasses to handle all the heavy lifting of constructing JS // objects and wrapping sass values inside them template - class SassValueWrapper : public SassTypes::Value { - public: - static char const* get_constructor_name() { return "SassValue"; } + /* class SassValueWrapper : public SassTypes::Value { */ + class SassValueWrapper : public SassTypes::Value { + public: + static char const* get_constructor_name() { return "SassValue"; } - SassValueWrapper(Sass_Value*); - virtual ~SassValueWrapper(); + SassValueWrapper(Sass_Value* v) : Value(v) { } + v8::Local get_js_object(); - Sass_Value* get_sass_value(); - v8::Local get_js_object(); + static v8::Local get_constructor(); + static v8::Local get_constructor_template(); + static NAN_METHOD(New); + static Sass_Value *fail(const char *, Sass_Value **); - static v8::Local get_constructor(); - static v8::Local get_constructor_template(); - static NAN_METHOD(New); - static Sass_Value *fail(const char *, Sass_Value **); - - protected: - Sass_Value* value; - static T* unwrap(v8::Local); - - private: - static Nan::Persistent constructor; - Nan::Persistent js_object; - }; + /* private: */ + static Nan::Persistent constructor; + }; template - Nan::Persistent SassValueWrapper::constructor; + Nan::Persistent SassValueWrapper::constructor; template - SassValueWrapper::SassValueWrapper(Sass_Value* v) { - this->value = sass_clone_value(v); - } - - template - SassValueWrapper::~SassValueWrapper() { - this->js_object.Reset(); - sass_delete_value(this->value); - } + v8::Local SassValueWrapper::get_js_object() { + if (this->persistent().IsEmpty()) { + v8::Local wrapper = Nan::NewInstance(T::get_constructor()).ToLocalChecked(); + this->Wrap(wrapper); + } - template - Sass_Value* SassValueWrapper::get_sass_value() { - return sass_clone_value(this->value); - } + return this->handle(); + } template - v8::Local SassValueWrapper::get_js_object() { - if (this->js_object.IsEmpty()) { - v8::Local wrapper = Nan::NewInstance(T::get_constructor()).ToLocalChecked(); - delete static_cast(Nan::GetInternalFieldPointer(wrapper, 0)); - Nan::SetInternalFieldPointer(wrapper, 0, this); - this->js_object.Reset(wrapper); + v8::Local SassValueWrapper::get_constructor_template() { + Nan::EscapableHandleScope scope; + v8::Local tpl = Nan::New(New); + tpl->SetClassName(Nan::New(T::get_constructor_name()).ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + T::initPrototype(tpl); + + return scope.Escape(tpl); } - return Nan::New(this->js_object); - } - template - v8::Local SassValueWrapper::get_constructor_template() { - Nan::EscapableHandleScope scope; - v8::Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New(T::get_constructor_name()).ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - T::initPrototype(tpl); - - return scope.Escape(tpl); - } + v8::Local SassValueWrapper::get_constructor() { + if (constructor.IsEmpty()) { + constructor.Reset(Nan::GetFunction(T::get_constructor_template()).ToLocalChecked()); + } - template - v8::Local SassValueWrapper::get_constructor() { - if (constructor.IsEmpty()) { - constructor.Reset(Nan::GetFunction(T::get_constructor_template()).ToLocalChecked()); + return Nan::New(constructor); } - return Nan::New(constructor); - } - template - NAN_METHOD(SassValueWrapper::New) { - std::vector> localArgs(info.Length()); + NAN_METHOD(SassValueWrapper::New) { + std::vector> localArgs(info.Length()); - for (auto i = 0; i < info.Length(); ++i) { - localArgs[i] = info[i]; - } - if (info.IsConstructCall()) { - Sass_Value* value; - if (T::construct(localArgs, &value) != NULL) { - T* obj = new T(value); - sass_delete_value(value); - - Nan::SetInternalFieldPointer(info.This(), 0, obj); - obj->js_object.Reset(info.This()); - } else { - return Nan::ThrowError(Nan::New(sass_error_get_message(value)).ToLocalChecked()); + for (auto i = 0; i < info.Length(); ++i) { + localArgs[i] = info[i]; } - } else { - v8::Local cons = T::get_constructor(); - v8::Local inst; - if (Nan::NewInstance(cons, info.Length(), &localArgs[0]).ToLocal(&inst)) { - info.GetReturnValue().Set(inst); + if (info.IsConstructCall()) { + Sass_Value* value; + if (T::construct(localArgs, &value) != NULL) { + T* obj = new T(value); + sass_delete_value(value); + + obj->Wrap(info.This()); + info.GetReturnValue().Set(info.This()); + } else { + return Nan::ThrowError(Nan::New(sass_error_get_message(value)).ToLocalChecked()); + } } else { - info.GetReturnValue().Set(Nan::Undefined()); + v8::Local cons = T::get_constructor(); + v8::Local inst; + if (Nan::NewInstance(cons, info.Length(), &localArgs[0]).ToLocal(&inst)) { + info.GetReturnValue().Set(inst); + } else { + info.GetReturnValue().Set(Nan::Undefined()); + } } } - } template - T* SassValueWrapper::unwrap(v8::Local obj) { - /* This maybe NULL */ - return static_cast(Factory::unwrap(obj)); - } - - template - Sass_Value *SassValueWrapper::fail(const char *reason, Sass_Value **out) { - *out = sass_make_error(reason); - return NULL; - } + Sass_Value *SassValueWrapper::fail(const char *reason, Sass_Value **out) { + *out = sass_make_error(reason); + return NULL; + } } - #endif diff --git a/src/sass_types/string.cpp b/src/sass_types/string.cpp index 9235f9e02..c6f2c48fc 100644 --- a/src/sass_types/string.cpp +++ b/src/sass_types/string.cpp @@ -31,7 +31,7 @@ namespace SassTypes } NAN_METHOD(String::GetValue) { - info.GetReturnValue().Set(Nan::New(sass_string_get_value(unwrap(info.This())->value)).ToLocalChecked()); + info.GetReturnValue().Set(Nan::New(sass_string_get_value(String::Unwrap(info.This())->value)).ToLocalChecked()); } NAN_METHOD(String::SetValue) { @@ -43,6 +43,6 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a string"); } - sass_string_set_value(unwrap(info.This())->value, create_string(info[0])); + sass_string_set_value(String::Unwrap(info.This())->value, create_string(info[0])); } } diff --git a/src/sass_types/value.h b/src/sass_types/value.h index c1cfdaff1..fa4703cb3 100644 --- a/src/sass_types/value.h +++ b/src/sass_types/value.h @@ -7,10 +7,35 @@ namespace SassTypes { // This is the interface that all sass values must comply with - class Value { + class Value : public Nan::ObjectWrap { + public: - virtual Sass_Value* get_sass_value() =0; virtual v8::Local get_js_object() =0; + + Value() { + + } + + Sass_Value* get_sass_value() { + return sass_clone_value(this->value); + } + + protected: + + Sass_Value* value; + + Value(Sass_Value* v) { + this->value = sass_clone_value(v); + } + + ~Value() { + sass_delete_value(this->value); + } + + static Sass_Value* fail(const char *reason, Sass_Value **out) { + *out = sass_make_error(reason); + return NULL; + } }; }