#include "smalloc.h" #include "env.h" #include "env-inl.h" #include "node.h" #include "node_internals.h" #include "v8-profiler.h" #include "v8.h" #include #define ALLOC_ID (0xA10C) #define EXTERNAL_ARRAY_TYPES(V) \ V(Int8, kExternalInt8Array) \ V(Uint8, kExternalUint8Array) \ V(Int16, kExternalInt16Array) \ V(Uint16, kExternalUint16Array) \ V(Int32, kExternalInt32Array) \ V(Uint32, kExternalUint32Array) \ V(Float, kExternalFloat32Array) \ V(Double, kExternalFloat64Array) \ V(Uint8Clamped, kExternalUint8ClampedArray) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) namespace node { namespace smalloc { using v8::Context; using v8::External; using v8::ExternalArrayType; using v8::FunctionCallbackInfo; using v8::Handle; using v8::HandleScope; using v8::HeapProfiler; using v8::Isolate; using v8::Local; using v8::Object; using v8::Persistent; using v8::RetainedObjectInfo; using v8::Uint32; using v8::Value; using v8::WeakCallbackData; using v8::kExternalUint8Array; class CallbackInfo { public: static inline void Free(char* data, void* hint); static inline CallbackInfo* New(Isolate* isolate, Handle object, FreeCallback callback, void* hint = 0); inline void Dispose(Isolate* isolate); inline Persistent* persistent(); private: static void WeakCallback(const WeakCallbackData&); inline void WeakCallback(Isolate* isolate, Local object); inline CallbackInfo(Isolate* isolate, Handle object, FreeCallback callback, void* hint); ~CallbackInfo(); Persistent persistent_; FreeCallback const callback_; void* const hint_; DISALLOW_COPY_AND_ASSIGN(CallbackInfo); }; void CallbackInfo::Free(char* data, void*) { ::free(data); } CallbackInfo* CallbackInfo::New(Isolate* isolate, Handle object, FreeCallback callback, void* hint) { return new CallbackInfo(isolate, object, callback, hint); } void CallbackInfo::Dispose(Isolate* isolate) { WeakCallback(isolate, PersistentToLocal(isolate, persistent_)); } Persistent* CallbackInfo::persistent() { return &persistent_; } CallbackInfo::CallbackInfo(Isolate* isolate, Handle object, FreeCallback callback, void* hint) : persistent_(isolate, object), callback_(callback), hint_(hint) { persistent_.SetWeak(this, WeakCallback); persistent_.SetWrapperClassId(ALLOC_ID); persistent_.MarkIndependent(); } CallbackInfo::~CallbackInfo() { persistent_.Reset(); } void CallbackInfo::WeakCallback( const WeakCallbackData& data) { data.GetParameter()->WeakCallback(data.GetIsolate(), data.GetValue()); } void CallbackInfo::WeakCallback(Isolate* isolate, Local object) { void* array_data = object->GetIndexedPropertiesExternalArrayData(); size_t array_length = object->GetIndexedPropertiesExternalArrayDataLength(); enum ExternalArrayType array_type = object->GetIndexedPropertiesExternalArrayDataType(); size_t array_size = ExternalArraySize(array_type); CHECK_GT(array_size, 0); if (array_size > 1 && array_data != NULL) { CHECK_GT(array_length * array_size, array_length); // Overflow check. array_length *= array_size; } object->SetIndexedPropertiesToExternalArrayData(nullptr, array_type, 0); int64_t change_in_bytes = -static_cast(array_length + sizeof(*this)); isolate->AdjustAmountOfExternalAllocatedMemory(change_in_bytes); callback_(static_cast(array_data), hint_); delete this; } // return size of external array type, or 0 if unrecognized size_t ExternalArraySize(enum ExternalArrayType type) { switch (type) { case v8::kExternalUint8Array: return sizeof(uint8_t); case v8::kExternalInt8Array: return sizeof(int8_t); case v8::kExternalInt16Array: return sizeof(int16_t); case v8::kExternalUint16Array: return sizeof(uint16_t); case v8::kExternalInt32Array: return sizeof(int32_t); case v8::kExternalUint32Array: return sizeof(uint32_t); case v8::kExternalFloat32Array: return sizeof(float); // NOLINT(runtime/sizeof) case v8::kExternalFloat64Array: return sizeof(double); // NOLINT(runtime/sizeof) case v8::kExternalUint8ClampedArray: return sizeof(uint8_t); } return 0; } // copyOnto(source, source_start, dest, dest_start, copy_length) void CopyOnto(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ASSERT(args[0]->IsObject()); ASSERT(args[2]->IsObject()); Local source = args[0].As(); Local dest = args[2].As(); ASSERT(source->HasIndexedPropertiesInExternalArrayData()); ASSERT(dest->HasIndexedPropertiesInExternalArrayData()); size_t source_start = args[1]->Uint32Value(); size_t dest_start = args[3]->Uint32Value(); size_t copy_length = args[4]->Uint32Value(); char* source_data = static_cast( source->GetIndexedPropertiesExternalArrayData()); char* dest_data = static_cast( dest->GetIndexedPropertiesExternalArrayData()); size_t source_length = source->GetIndexedPropertiesExternalArrayDataLength(); enum ExternalArrayType source_type = source->GetIndexedPropertiesExternalArrayDataType(); size_t source_size = ExternalArraySize(source_type); size_t dest_length = dest->GetIndexedPropertiesExternalArrayDataLength(); enum ExternalArrayType dest_type = dest->GetIndexedPropertiesExternalArrayDataType(); size_t dest_size = ExternalArraySize(dest_type); // optimization for Uint8 arrays (i.e. Buffers) if (source_size != 1 || dest_size != 1) { if (source_size == 0) return env->ThrowTypeError("unknown source external array type"); if (dest_size == 0) return env->ThrowTypeError("unknown dest external array type"); if (source_length * source_size < source_length) return env->ThrowRangeError("source_length * source_size overflow"); if (copy_length * source_size < copy_length) return env->ThrowRangeError("copy_length * source_size overflow"); if (dest_length * dest_size < dest_length) return env->ThrowRangeError("dest_length * dest_size overflow"); source_length *= source_size; copy_length *= source_size; dest_length *= dest_size; } // necessary to check in case (source|dest)_start _and_ copy_length overflow if (copy_length > source_length) return env->ThrowRangeError("copy_length > source_length"); if (copy_length > dest_length) return env->ThrowRangeError("copy_length > dest_length"); if (source_start > source_length) return env->ThrowRangeError("source_start > source_length"); if (dest_start > dest_length) return env->ThrowRangeError("dest_start > dest_length"); // now we can guarantee these will catch oob access and *_start overflow if (source_start + copy_length > source_length) return env->ThrowRangeError("source_start + copy_length > source_length"); if (dest_start + copy_length > dest_length) return env->ThrowRangeError("dest_start + copy_length > dest_length"); memmove(dest_data + dest_start, source_data + source_start, copy_length); } // dest will always be same type as source // for internal use: // dest._data = sliceOnto(source, dest, start, end); void SliceOnto(const FunctionCallbackInfo& args) { Local source = args[0].As(); Local dest = args[1].As(); CHECK(source->HasIndexedPropertiesInExternalArrayData()); CHECK_EQ(false, dest->HasIndexedPropertiesInExternalArrayData()); char* source_data = static_cast( source->GetIndexedPropertiesExternalArrayData()); size_t source_len = source->GetIndexedPropertiesExternalArrayDataLength(); enum ExternalArrayType source_type = source->GetIndexedPropertiesExternalArrayDataType(); size_t source_size = ExternalArraySize(source_type); CHECK_NE(source_size, 0); size_t start = args[2]->Uint32Value(); size_t end = args[3]->Uint32Value(); size_t length = end - start; if (source_size > 1) { CHECK_GE(length * source_size, length); length *= source_size; } CHECK(source_data != nullptr || length == 0); CHECK_LE(end, source_len); CHECK_LE(start, end); dest->SetIndexedPropertiesToExternalArrayData(source_data + start, source_type, length); args.GetReturnValue().Set(source); } // for internal use: // alloc(obj, n[, type]); void Alloc(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ASSERT(args[0]->IsObject()); Local obj = args[0].As(); ASSERT(!obj->HasIndexedPropertiesInExternalArrayData()); size_t length = args[1]->Uint32Value(); enum ExternalArrayType array_type; // it's faster to not pass the default argument then use Uint32Value if (args[2]->IsUndefined()) { array_type = kExternalUint8Array; } else { array_type = static_cast(args[2]->Uint32Value()); size_t type_length = ExternalArraySize(array_type); CHECK_GE(type_length * length, length); length *= type_length; } Alloc(env, obj, length, array_type); args.GetReturnValue().Set(obj); } void Alloc(Environment* env, Handle obj, size_t length, enum ExternalArrayType type) { size_t type_size = ExternalArraySize(type); CHECK_LE(length, kMaxLength); CHECK_GT(type_size, 0); if (length == 0) return Alloc(env, obj, nullptr, length, type); char* data = static_cast(malloc(length)); if (data == nullptr) { FatalError("node::smalloc::Alloc(v8::Handle, size_t," " v8::ExternalArrayType)", "Out Of Memory"); } Alloc(env, obj, data, length, type); } void Alloc(Environment* env, Handle obj, char* data, size_t length, enum ExternalArrayType type) { CHECK_EQ(false, obj->HasIndexedPropertiesInExternalArrayData()); env->isolate()->AdjustAmountOfExternalAllocatedMemory(length); size_t size = length / ExternalArraySize(type); obj->SetIndexedPropertiesToExternalArrayData(data, type, size); CallbackInfo::New(env->isolate(), obj, CallbackInfo::Free); } // for internal use: dispose(obj); void AllocDispose(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); AllocDispose(env, args[0].As()); } void AllocDispose(Environment* env, Handle obj) { HandleScope handle_scope(env->isolate()); if (env->using_smalloc_alloc_cb()) { Local ext_v = obj->GetHiddenValue(env->smalloc_p_string()); if (ext_v->IsExternal()) { Local ext = ext_v.As(); CallbackInfo* info = static_cast(ext->Value()); info->Dispose(env->isolate()); return; } } char* data = static_cast(obj->GetIndexedPropertiesExternalArrayData()); size_t length = obj->GetIndexedPropertiesExternalArrayDataLength(); enum ExternalArrayType array_type = obj->GetIndexedPropertiesExternalArrayDataType(); size_t array_size = ExternalArraySize(array_type); CHECK_GT(array_size, 0); CHECK_GE(length * array_size, length); length *= array_size; if (data != nullptr) { obj->SetIndexedPropertiesToExternalArrayData(nullptr, kExternalUint8Array, 0); free(data); } if (length != 0) { int64_t change_in_bytes = -static_cast(length); env->isolate()->AdjustAmountOfExternalAllocatedMemory(change_in_bytes); } } void Alloc(Environment* env, Handle obj, size_t length, FreeCallback fn, void* hint, enum ExternalArrayType type) { CHECK_LE(length, kMaxLength); size_t type_size = ExternalArraySize(type); CHECK_GT(type_size, 0); CHECK_GE(length * type_size, length); length *= type_size; char* data = new char[length]; Alloc(env, obj, data, length, fn, hint, type); } void Alloc(Environment* env, Handle obj, char* data, size_t length, FreeCallback fn, void* hint, enum ExternalArrayType type) { CHECK_EQ(false, obj->HasIndexedPropertiesInExternalArrayData()); Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); env->set_using_smalloc_alloc_cb(true); CallbackInfo* info = CallbackInfo::New(isolate, obj, fn, hint); obj->SetHiddenValue(env->smalloc_p_string(), External::New(isolate, info)); isolate->AdjustAmountOfExternalAllocatedMemory(length + sizeof(*info)); size_t size = length / ExternalArraySize(type); obj->SetIndexedPropertiesToExternalArrayData(data, type, size); } void HasExternalData(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ASSERT(args[0]->IsObject()); args.GetReturnValue().Set(HasExternalData(env, args[0].As())); } bool HasExternalData(Environment* env, Local obj) { return obj->HasIndexedPropertiesInExternalArrayData(); } void IsTypedArray(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(args[0]->IsTypedArray()); } void AllocTruncate(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local obj = args[0].As(); // can't perform this check in JS if (!obj->HasIndexedPropertiesInExternalArrayData()) return env->ThrowTypeError("object has no external array data"); char* data = static_cast(obj->GetIndexedPropertiesExternalArrayData()); enum ExternalArrayType array_type = obj->GetIndexedPropertiesExternalArrayDataType(); int length = obj->GetIndexedPropertiesExternalArrayDataLength(); unsigned int new_len = args[1]->Uint32Value(); if (new_len > kMaxLength) return env->ThrowRangeError("truncate length is bigger than kMaxLength"); if (static_cast(new_len) > length) return env->ThrowRangeError("truncate length is bigger than current one"); obj->SetIndexedPropertiesToExternalArrayData(data, array_type, static_cast(new_len)); } class RetainedAllocInfo: public RetainedObjectInfo { public: explicit RetainedAllocInfo(Handle wrapper); virtual void Dispose() override; virtual bool IsEquivalent(RetainedObjectInfo* other) override; virtual intptr_t GetHash() override; virtual const char* GetLabel() override; virtual intptr_t GetSizeInBytes() override; private: static const char label_[]; char* data_; int length_; }; const char RetainedAllocInfo::label_[] = "smalloc"; RetainedAllocInfo::RetainedAllocInfo(Handle wrapper) { Local obj = wrapper.As(); length_ = obj->GetIndexedPropertiesExternalArrayDataLength(); data_ = static_cast(obj->GetIndexedPropertiesExternalArrayData()); } void RetainedAllocInfo::Dispose() { delete this; } bool RetainedAllocInfo::IsEquivalent(RetainedObjectInfo* other) { return label_ == other->GetLabel() && data_ == static_cast(other)->data_; } intptr_t RetainedAllocInfo::GetHash() { return reinterpret_cast(data_); } const char* RetainedAllocInfo::GetLabel() { return label_; } intptr_t RetainedAllocInfo::GetSizeInBytes() { return length_; } RetainedObjectInfo* WrapperInfo(uint16_t class_id, Handle wrapper) { return new RetainedAllocInfo(wrapper); } // User facing API. void Alloc(Isolate* isolate, Handle obj, size_t length, enum ExternalArrayType type) { Alloc(Environment::GetCurrent(isolate), obj, length, type); } void Alloc(Isolate* isolate, Handle obj, char* data, size_t length, enum ExternalArrayType type) { Alloc(Environment::GetCurrent(isolate), obj, data, length, type); } void Alloc(Isolate* isolate, Handle obj, size_t length, FreeCallback fn, void* hint, enum ExternalArrayType type) { Alloc(Environment::GetCurrent(isolate), obj, length, fn, hint, type); } void Alloc(Isolate* isolate, Handle obj, char* data, size_t length, FreeCallback fn, void* hint, enum ExternalArrayType type) { Alloc(Environment::GetCurrent(isolate), obj, data, length, fn, hint, type); } void AllocDispose(Isolate* isolate, Handle obj) { AllocDispose(Environment::GetCurrent(isolate), obj); } bool HasExternalData(Isolate* isolate, Local obj) { return HasExternalData(Environment::GetCurrent(isolate), obj); } void Initialize(Handle exports, Handle unused, Handle context) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); env->SetMethod(exports, "copyOnto", CopyOnto); env->SetMethod(exports, "sliceOnto", SliceOnto); env->SetMethod(exports, "alloc", Alloc); env->SetMethod(exports, "dispose", AllocDispose); env->SetMethod(exports, "truncate", AllocTruncate); env->SetMethod(exports, "hasExternalData", HasExternalData); env->SetMethod(exports, "isTypedArray", IsTypedArray); exports->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kMaxLength"), Uint32::NewFromUnsigned(env->isolate(), kMaxLength)); Local types = Object::New(isolate); uint32_t kMinType = ~0; uint32_t kMaxType = 0; #define V(name, value) \ types->Set(FIXED_ONE_BYTE_STRING(env->isolate(), #name), \ Uint32::NewFromUnsigned(env->isolate(), v8::value)); \ kMinType = MIN(kMinType, static_cast(v8::value)); \ kMaxType = MAX(kMinType, static_cast(v8::value)); EXTERNAL_ARRAY_TYPES(V) #undef V exports->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "types"), types); exports->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kMinType"), Uint32::NewFromUnsigned(env->isolate(), kMinType)); exports->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kMaxType"), Uint32::NewFromUnsigned(env->isolate(), kMaxType)); HeapProfiler* heap_profiler = env->isolate()->GetHeapProfiler(); heap_profiler->SetWrapperClassInfoProvider(ALLOC_ID, WrapperInfo); } } // namespace smalloc } // namespace node NODE_MODULE_CONTEXT_AWARE_BUILTIN(smalloc, node::smalloc::Initialize)