#include "node.h" #include "node_buffer.h" #include "env.h" #include "env-inl.h" #include "smalloc.h" #include "string_bytes.h" #include "v8-profiler.h" #include "v8.h" #include #include #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define CHECK_NOT_OOB(r) \ do { \ if (!(r)) return env->ThrowRangeError("out of range index"); \ } while (0) #define ARGS_THIS(argT) \ Local obj = argT; \ size_t obj_length = obj->GetIndexedPropertiesExternalArrayDataLength(); \ char* obj_data = static_cast( \ obj->GetIndexedPropertiesExternalArrayData()); \ if (obj_length > 0) \ CHECK_NE(obj_data, nullptr); #define SLICE_START_END(start_arg, end_arg, end_max) \ size_t start; \ size_t end; \ CHECK_NOT_OOB(ParseArrayIndex(start_arg, 0, &start)); \ CHECK_NOT_OOB(ParseArrayIndex(end_arg, end_max, &end)); \ if (end < start) end = start; \ CHECK_NOT_OOB(end <= end_max); \ size_t length = end - start; namespace node { namespace Buffer { using v8::Context; using v8::EscapableHandleScope; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Handle; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::Number; using v8::Object; using v8::String; using v8::Uint32; using v8::Value; bool HasInstance(Handle val) { return val->IsObject() && HasInstance(val.As()); } bool HasInstance(Handle obj) { if (!obj->HasIndexedPropertiesInExternalArrayData()) return false; v8::ExternalArrayType type = obj->GetIndexedPropertiesExternalArrayDataType(); return type == v8::kExternalUint8Array; } char* Data(Handle val) { CHECK(val->IsObject()); // Use a fully qualified name here to work around a bug in gcc 4.2. // It mistakes an unadorned call to Data() for the v8::String::Data type. return node::Buffer::Data(val.As()); } char* Data(Handle obj) { CHECK(obj->HasIndexedPropertiesInExternalArrayData()); return static_cast(obj->GetIndexedPropertiesExternalArrayData()); } size_t Length(Handle val) { CHECK(val->IsObject()); return Length(val.As()); } size_t Length(Handle obj) { CHECK(obj->HasIndexedPropertiesInExternalArrayData()); return obj->GetIndexedPropertiesExternalArrayDataLength(); } Local New(Isolate* isolate, Handle string, enum encoding enc) { EscapableHandleScope scope(isolate); size_t length = StringBytes::Size(isolate, string, enc); Local buf = New(length); char* data = Buffer::Data(buf); StringBytes::Write(isolate, data, length, string, enc); return scope.Escape(buf); } Local New(Isolate* isolate, size_t length) { EscapableHandleScope handle_scope(isolate); Local obj = Buffer::New(Environment::GetCurrent(isolate), length); return handle_scope.Escape(obj); } // TODO(trevnorris): these have a flaw by needing to call the Buffer inst then // Alloc. continue to look for a better architecture. Local New(Environment* env, size_t length) { EscapableHandleScope scope(env->isolate()); CHECK_LE(length, kMaxLength); Local arg = Uint32::NewFromUnsigned(env->isolate(), length); Local obj = env->buffer_constructor_function()->NewInstance(1, &arg); // TODO(trevnorris): done like this to handle HasInstance since only checks // if external array data has been set, but would like to use a better // approach if v8 provided one. char* data; if (length > 0) { data = static_cast(malloc(length)); if (data == nullptr) FatalError("node::Buffer::New(size_t)", "Out Of Memory"); } else { data = nullptr; } smalloc::Alloc(env, obj, data, length); return scope.Escape(obj); } Local New(Isolate* isolate, const char* data, size_t length) { Environment* env = Environment::GetCurrent(isolate); EscapableHandleScope handle_scope(env->isolate()); Local obj = Buffer::New(env, data, length); return handle_scope.Escape(obj); } // TODO(trevnorris): for backwards compatibility this is left to copy the data, // but for consistency w/ the other should use data. And a copy version renamed // to something else. Local New(Environment* env, const char* data, size_t length) { EscapableHandleScope scope(env->isolate()); CHECK_LE(length, kMaxLength); Local arg = Uint32::NewFromUnsigned(env->isolate(), length); Local obj = env->buffer_constructor_function()->NewInstance(1, &arg); // TODO(trevnorris): done like this to handle HasInstance since only checks // if external array data has been set, but would like to use a better // approach if v8 provided one. char* new_data; if (length > 0) { new_data = static_cast(malloc(length)); if (new_data == nullptr) FatalError("node::Buffer::New(const char*, size_t)", "Out Of Memory"); memcpy(new_data, data, length); } else { new_data = nullptr; } smalloc::Alloc(env, obj, new_data, length); return scope.Escape(obj); } Local New(Isolate* isolate, char* data, size_t length, smalloc::FreeCallback callback, void* hint) { Environment* env = Environment::GetCurrent(isolate); EscapableHandleScope handle_scope(env->isolate()); Local obj = Buffer::New(env, data, length, callback, hint); return handle_scope.Escape(obj); } Local New(Environment* env, char* data, size_t length, smalloc::FreeCallback callback, void* hint) { EscapableHandleScope scope(env->isolate()); CHECK_LE(length, kMaxLength); Local arg = Uint32::NewFromUnsigned(env->isolate(), length); Local obj = env->buffer_constructor_function()->NewInstance(1, &arg); smalloc::Alloc(env, obj, data, length, callback, hint); return scope.Escape(obj); } Local Use(Isolate* isolate, char* data, uint32_t length) { Environment* env = Environment::GetCurrent(isolate); EscapableHandleScope handle_scope(env->isolate()); Local obj = Buffer::Use(env, data, length); return handle_scope.Escape(obj); } Local Use(Environment* env, char* data, uint32_t length) { EscapableHandleScope scope(env->isolate()); CHECK_LE(length, kMaxLength); Local arg = Uint32::NewFromUnsigned(env->isolate(), length); Local obj = env->buffer_constructor_function()->NewInstance(1, &arg); smalloc::Alloc(env, obj, data, length); return scope.Escape(obj); } template void StringSlice(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ARGS_THIS(args.This()) SLICE_START_END(args[0], args[1], obj_length) args.GetReturnValue().Set( StringBytes::Encode(env->isolate(), obj_data + start, length, encoding)); } template <> void StringSlice(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ARGS_THIS(args.This()) SLICE_START_END(args[0], args[1], obj_length) length /= 2; const char* data = obj_data + start; const uint16_t* buf; bool release = false; // Node's "ucs2" encoding expects LE character data inside a Buffer, so we // need to reorder on BE platforms. See http://nodejs.org/api/buffer.html // regarding Node's "ucs2" encoding specification. const bool aligned = (reinterpret_cast(data) % sizeof(*buf) == 0); if (IsLittleEndian() && aligned) { buf = reinterpret_cast(data); } else { // Make a copy to avoid unaligned accesses in v8::String::NewFromTwoByte(). uint16_t* copy = new uint16_t[length]; for (size_t i = 0, k = 0; i < length; i += 1, k += 2) { // Assumes that the input is little endian. const uint8_t lo = static_cast(data[k + 0]); const uint8_t hi = static_cast(data[k + 1]); copy[i] = lo | hi << 8; } buf = copy; release = true; } args.GetReturnValue().Set(StringBytes::Encode(env->isolate(), buf, length)); if (release) delete[] buf; } void BinarySlice(const FunctionCallbackInfo& args) { StringSlice(args); } void AsciiSlice(const FunctionCallbackInfo& args) { StringSlice(args); } void Utf8Slice(const FunctionCallbackInfo& args) { StringSlice(args); } void Ucs2Slice(const FunctionCallbackInfo& args) { StringSlice(args); } void HexSlice(const FunctionCallbackInfo& args) { StringSlice(args); } void Base64Slice(const FunctionCallbackInfo& args) { StringSlice(args); } // bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]); void Copy(const FunctionCallbackInfo &args) { Environment* env = Environment::GetCurrent(args); Local target = args[0]->ToObject(env->isolate()); if (!HasInstance(target)) return env->ThrowTypeError("first arg should be a Buffer"); ARGS_THIS(args.This()) size_t target_length = target->GetIndexedPropertiesExternalArrayDataLength(); char* target_data = static_cast( target->GetIndexedPropertiesExternalArrayData()); size_t target_start; size_t source_start; size_t source_end; CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &target_start)); CHECK_NOT_OOB(ParseArrayIndex(args[2], 0, &source_start)); CHECK_NOT_OOB(ParseArrayIndex(args[3], obj_length, &source_end)); // Copy 0 bytes; we're done if (target_start >= target_length || source_start >= source_end) return args.GetReturnValue().Set(0); if (source_start > obj_length) return env->ThrowRangeError("out of range index"); if (source_end - source_start > target_length - target_start) source_end = source_start + target_length - target_start; uint32_t to_copy = MIN(MIN(source_end - source_start, target_length - target_start), obj_length - source_start); memmove(target_data + target_start, obj_data + source_start, to_copy); args.GetReturnValue().Set(to_copy); } void Fill(const FunctionCallbackInfo& args) { ARGS_THIS(args[0].As()) size_t start = args[2]->Uint32Value(); size_t end = args[3]->Uint32Value(); size_t length = end - start; CHECK(length + start <= obj_length); if (args[1]->IsNumber()) { int value = args[1]->Uint32Value() & 255; memset(obj_data + start, value, length); return; } node::Utf8Value str(args.GetIsolate(), args[1]); size_t str_length = str.length(); size_t in_there = str_length; char* ptr = obj_data + start + str_length; if (str_length == 0) return; memcpy(obj_data + start, *str, MIN(str_length, length)); if (str_length >= length) return; while (in_there < length - in_there) { memcpy(ptr, obj_data + start, in_there); ptr += in_there; in_there *= 2; } if (in_there < length) { memcpy(ptr, obj_data + start, length - in_there); in_there = length; } } template void StringWrite(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ARGS_THIS(args.This()) if (!args[0]->IsString()) return env->ThrowTypeError("Argument must be a string"); Local str = args[0]->ToString(env->isolate()); if (encoding == HEX && str->Length() % 2 != 0) return env->ThrowTypeError("Invalid hex string"); size_t offset; size_t max_length; CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset)); CHECK_NOT_OOB(ParseArrayIndex(args[2], obj_length - offset, &max_length)); max_length = MIN(obj_length - offset, max_length); if (max_length == 0) return args.GetReturnValue().Set(0); if (encoding == UCS2) max_length = max_length / 2; if (offset >= obj_length) return env->ThrowRangeError("Offset is out of bounds"); uint32_t written = StringBytes::Write(env->isolate(), obj_data + offset, max_length, str, encoding, nullptr); args.GetReturnValue().Set(written); } void Base64Write(const FunctionCallbackInfo& args) { StringWrite(args); } void BinaryWrite(const FunctionCallbackInfo& args) { StringWrite(args); } void Utf8Write(const FunctionCallbackInfo& args) { StringWrite(args); } void Ucs2Write(const FunctionCallbackInfo& args) { StringWrite(args); } void HexWrite(const FunctionCallbackInfo& args) { StringWrite(args); } void AsciiWrite(const FunctionCallbackInfo& args) { StringWrite(args); } static inline void Swizzle(char* start, unsigned int len) { char* end = start + len - 1; while (start < end) { char tmp = *start; *start++ = *end; *end-- = tmp; } } template void ReadFloatGeneric(const FunctionCallbackInfo& args) { ARGS_THIS(args[0].As()); uint32_t offset = args[1]->Uint32Value(); CHECK_LE(offset + sizeof(T), obj_length); union NoAlias { T val; char bytes[sizeof(T)]; }; union NoAlias na; const char* ptr = static_cast(obj_data) + offset; memcpy(na.bytes, ptr, sizeof(na.bytes)); if (endianness != GetEndianness()) Swizzle(na.bytes, sizeof(na.bytes)); args.GetReturnValue().Set(na.val); } void ReadFloatLE(const FunctionCallbackInfo& args) { ReadFloatGeneric(args); } void ReadFloatBE(const FunctionCallbackInfo& args) { ReadFloatGeneric(args); } void ReadDoubleLE(const FunctionCallbackInfo& args) { ReadFloatGeneric(args); } void ReadDoubleBE(const FunctionCallbackInfo& args) { ReadFloatGeneric(args); } template uint32_t WriteFloatGeneric(const FunctionCallbackInfo& args) { ARGS_THIS(args[0].As()) T val = args[1]->NumberValue(); uint32_t offset = args[2]->Uint32Value(); CHECK_LE(offset + sizeof(T), obj_length); union NoAlias { T val; char bytes[sizeof(T)]; }; union NoAlias na = { val }; char* ptr = static_cast(obj_data) + offset; if (endianness != GetEndianness()) Swizzle(na.bytes, sizeof(na.bytes)); memcpy(ptr, na.bytes, sizeof(na.bytes)); return offset + sizeof(na.bytes); } void WriteFloatLE(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(WriteFloatGeneric(args)); } void WriteFloatBE(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(WriteFloatGeneric(args)); } void WriteDoubleLE(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(WriteFloatGeneric(args)); } void WriteDoubleBE(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(WriteFloatGeneric(args)); } void ByteLength(const FunctionCallbackInfo &args) { Environment* env = Environment::GetCurrent(args); if (!args[0]->IsString()) return env->ThrowTypeError("Argument must be a string"); Local s = args[0]->ToString(env->isolate()); enum encoding e = ParseEncoding(env->isolate(), args[1], UTF8); uint32_t size = StringBytes::Size(env->isolate(), s, e); args.GetReturnValue().Set(size); } void Compare(const FunctionCallbackInfo &args) { Local obj_a = args[0].As(); char* obj_a_data = static_cast(obj_a->GetIndexedPropertiesExternalArrayData()); size_t obj_a_len = obj_a->GetIndexedPropertiesExternalArrayDataLength(); Local obj_b = args[1].As(); char* obj_b_data = static_cast(obj_b->GetIndexedPropertiesExternalArrayData()); size_t obj_b_len = obj_b->GetIndexedPropertiesExternalArrayDataLength(); size_t cmp_length = MIN(obj_a_len, obj_b_len); int32_t val = memcmp(obj_a_data, obj_b_data, cmp_length); // Normalize val to be an integer in the range of [1, -1] since // implementations of memcmp() can vary by platform. if (val == 0) { if (obj_a_len > obj_b_len) val = 1; else if (obj_a_len < obj_b_len) val = -1; } else { if (val > 0) val = 1; else val = -1; } args.GetReturnValue().Set(val); } // pass Buffer object to load prototype methods void SetupBufferJS(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsFunction()); Local bv = args[0].As(); env->set_buffer_constructor_function(bv); Local proto_v = bv->Get(env->prototype_string()); CHECK(proto_v->IsObject()); Local proto = proto_v.As(); env->SetMethod(proto, "asciiSlice", AsciiSlice); env->SetMethod(proto, "base64Slice", Base64Slice); env->SetMethod(proto, "binarySlice", BinarySlice); env->SetMethod(proto, "hexSlice", HexSlice); env->SetMethod(proto, "ucs2Slice", Ucs2Slice); env->SetMethod(proto, "utf8Slice", Utf8Slice); env->SetMethod(proto, "asciiWrite", AsciiWrite); env->SetMethod(proto, "base64Write", Base64Write); env->SetMethod(proto, "binaryWrite", BinaryWrite); env->SetMethod(proto, "hexWrite", HexWrite); env->SetMethod(proto, "ucs2Write", Ucs2Write); env->SetMethod(proto, "utf8Write", Utf8Write); env->SetMethod(proto, "copy", Copy); // for backwards compatibility proto->ForceSet(env->offset_string(), Uint32::New(env->isolate(), 0), v8::ReadOnly); CHECK(args[1]->IsObject()); Local internal = args[1].As(); ASSERT(internal->IsObject()); env->SetMethod(internal, "byteLength", ByteLength); env->SetMethod(internal, "compare", Compare); env->SetMethod(internal, "fill", Fill); env->SetMethod(internal, "readDoubleBE", ReadDoubleBE); env->SetMethod(internal, "readDoubleLE", ReadDoubleLE); env->SetMethod(internal, "readFloatBE", ReadFloatBE); env->SetMethod(internal, "readFloatLE", ReadFloatLE); env->SetMethod(internal, "writeDoubleBE", WriteDoubleBE); env->SetMethod(internal, "writeDoubleLE", WriteDoubleLE); env->SetMethod(internal, "writeFloatBE", WriteFloatBE); env->SetMethod(internal, "writeFloatLE", WriteFloatLE); } void Initialize(Handle target, Handle unused, Handle context) { Environment* env = Environment::GetCurrent(context); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "setupBufferJS"), env->NewFunctionTemplate(SetupBufferJS)->GetFunction()); } } // namespace Buffer } // namespace node NODE_MODULE_CONTEXT_AWARE_BUILTIN(buffer, node::Buffer::Initialize)