mirror of
https://github.com/nodejs/node-v0.x-archive.git
synced 2026-04-28 03:01:10 -04:00
Reimplment Buffers
This commit is contained in:
@@ -10,254 +10,198 @@ namespace node {
|
||||
|
||||
using namespace v8;
|
||||
|
||||
#define SLICE_ARGS(start_arg, end_arg) \
|
||||
if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \
|
||||
return ThrowException(Exception::TypeError( \
|
||||
String::New("Bad argument."))); \
|
||||
} \
|
||||
int32_t start = start_arg->Int32Value(); \
|
||||
int32_t end = end_arg->Int32Value(); \
|
||||
if (start < 0 || end < 0) { \
|
||||
return ThrowException(Exception::TypeError( \
|
||||
String::New("Bad argument."))); \
|
||||
}
|
||||
#define SLICE_ARGS(start_arg, end_arg) \
|
||||
if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \
|
||||
return ThrowException(Exception::TypeError( \
|
||||
String::New("Bad argument."))); \
|
||||
} \
|
||||
int32_t start = start_arg->Int32Value(); \
|
||||
int32_t end = end_arg->Int32Value(); \
|
||||
if (start < 0 || end < 0) { \
|
||||
return ThrowException(Exception::TypeError( \
|
||||
String::New("Bad argument."))); \
|
||||
} \
|
||||
if (!(start <= end)) { \
|
||||
return ThrowException(Exception::Error( \
|
||||
String::New("Must have start <= end"))); \
|
||||
} \
|
||||
if ((size_t)end > parent->length_) { \
|
||||
return ThrowException(Exception::Error( \
|
||||
String::New("end cannot be longer than parent.length"))); \
|
||||
}
|
||||
|
||||
|
||||
static Persistent<String> length_symbol;
|
||||
static Persistent<FunctionTemplate> constructor_template;
|
||||
|
||||
bool IsBuffer(v8::Handle<v8::Value> val) {
|
||||
if (!val->IsObject()) return false;
|
||||
Local<Object> obj = val->ToObject();
|
||||
return constructor_template->HasInstance(obj);
|
||||
}
|
||||
Persistent<FunctionTemplate> Buffer::constructor_template;
|
||||
|
||||
|
||||
/* Determines the absolute position for a relative offset */
|
||||
size_t buffer_abs_off(buffer *buffer, size_t off) {
|
||||
struct buffer *root = buffer_root(buffer);
|
||||
off += buffer->offset;
|
||||
return MIN(root->length, off);
|
||||
}
|
||||
|
||||
|
||||
void buffer_ref(struct buffer *buffer) {
|
||||
buffer_root(buffer)->refs++;
|
||||
}
|
||||
|
||||
|
||||
struct buffer* BufferUnwrap(v8::Handle<v8::Value> val) {
|
||||
assert(val->IsObject());
|
||||
HandleScope scope;
|
||||
Local<Object> obj = val->ToObject();
|
||||
assert(obj->InternalFieldCount() == 1);
|
||||
Local<External> ext = Local<External>::Cast(obj->GetInternalField(0));
|
||||
return static_cast<struct buffer*>(ext->Value());
|
||||
}
|
||||
|
||||
|
||||
static void RootWeakCallback(Persistent<Value> value, void *data)
|
||||
{
|
||||
struct buffer *buffer = static_cast<struct buffer*>(data);
|
||||
assert(buffer->root == NULL); // this is the root
|
||||
assert(value == buffer->handle);
|
||||
value.ClearWeak();
|
||||
if (buffer->refs) {
|
||||
buffer->weak = true;
|
||||
} else {
|
||||
buffer->handle.Dispose();
|
||||
free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void buffer_unref(struct buffer *buffer) {
|
||||
struct buffer * root = buffer_root(buffer);
|
||||
assert(root->refs > 0);
|
||||
root->refs--;
|
||||
if (root->refs == 0 && root->weak) {
|
||||
root->handle.MakeWeak(root, RootWeakCallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void SliceWeakCallback(Persistent<Value> value, void *data)
|
||||
{
|
||||
struct buffer *buffer = static_cast<struct buffer*>(data);
|
||||
assert(buffer->root != NULL); // this is a slice
|
||||
assert(value == buffer->handle);
|
||||
buffer->handle.Dispose();
|
||||
buffer_unref(buffer->root);
|
||||
}
|
||||
|
||||
|
||||
static Handle<Value> Constructor(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
// Each javascript Buffer object is backed by a Blob object.
|
||||
// the Blob is just a C-level chunk of bytes.
|
||||
// It has a reference count.
|
||||
struct Blob_ {
|
||||
unsigned int refs;
|
||||
size_t length;
|
||||
struct buffer *buffer;
|
||||
char data[1];
|
||||
};
|
||||
typedef struct Blob_ Blob;
|
||||
|
||||
if (constructor_template->HasInstance(args[0])) {
|
||||
// slice slice
|
||||
SLICE_ARGS(args[1], args[2])
|
||||
|
||||
struct buffer *parent = BufferUnwrap(args[0]);
|
||||
static inline Blob * blob_new(size_t length) {
|
||||
size_t s = sizeof(Blob) - 1 + length;
|
||||
Blob * blob = (Blob*) malloc(s);
|
||||
if (!blob) return NULL;
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(s);
|
||||
blob->length = length;
|
||||
blob->refs = 0;
|
||||
//fprintf(stderr, "alloc %d bytes\n", length);
|
||||
return blob;
|
||||
}
|
||||
|
||||
size_t start_abs = buffer_abs_off(parent, start);
|
||||
size_t end_abs = buffer_abs_off(parent, end);
|
||||
assert(start_abs <= end_abs);
|
||||
length = end_abs - start_abs;
|
||||
|
||||
void *d = malloc(sizeof(struct buffer));
|
||||
static inline void blob_ref(Blob *blob) {
|
||||
blob->refs++;
|
||||
}
|
||||
|
||||
if (!d) {
|
||||
V8::LowMemoryNotification();
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Could not allocate enough memory")));
|
||||
|
||||
}
|
||||
static inline void blob_unref(Blob *blob) {
|
||||
assert(blob->refs > 0);
|
||||
if (--blob->refs == 0) {
|
||||
//fprintf(stderr, "free %d bytes\n", blob->length);
|
||||
size_t s = sizeof(Blob) - 1 + blob->length;
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(-s);
|
||||
free(blob);
|
||||
}
|
||||
}
|
||||
|
||||
buffer = static_cast<struct buffer*>(d);
|
||||
|
||||
buffer->length = length;
|
||||
buffer->offset = start_abs;
|
||||
buffer->weak = false;
|
||||
buffer->refs = 0;
|
||||
buffer->root = buffer_root(parent);
|
||||
buffer->handle = Persistent<Object>::New(args.This());
|
||||
buffer->handle.MakeWeak(buffer, SliceWeakCallback);
|
||||
// When someone calls buffer.asciiSlice, data is not copied. Instead V8
|
||||
// references in the underlying Blob with this ExternalAsciiStringResource.
|
||||
class AsciiSliceExt: public String::ExternalAsciiStringResource {
|
||||
friend class Buffer;
|
||||
public:
|
||||
AsciiSliceExt(Buffer *parent, size_t start, size_t end) {
|
||||
blob_ = parent->blob();
|
||||
blob_ref(blob_);
|
||||
|
||||
buffer_ref(buffer->root);
|
||||
} else {
|
||||
// Root slice
|
||||
|
||||
length = args[0]->Uint32Value();
|
||||
|
||||
if (length < 1) {
|
||||
return ThrowException(Exception::TypeError(
|
||||
String::New("Bad argument. Length must be positive")));
|
||||
}
|
||||
|
||||
// TODO alignment. modify the length?
|
||||
void *d = malloc(sizeof(struct buffer) + length - 1);
|
||||
|
||||
if (!d) {
|
||||
V8::LowMemoryNotification();
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Could not allocate enough memory")));
|
||||
}
|
||||
|
||||
buffer = static_cast<struct buffer*>(d);
|
||||
|
||||
buffer->offset = 0;
|
||||
buffer->length = length;
|
||||
buffer->weak = false;
|
||||
buffer->refs = 0;
|
||||
buffer->root = NULL;
|
||||
buffer->handle = Persistent<Object>::New(args.This());
|
||||
buffer->handle.MakeWeak(buffer, RootWeakCallback);
|
||||
assert(start <= end);
|
||||
length_ = end - start;
|
||||
assert(length_ <= parent->length());
|
||||
data_ = parent->data() + start;
|
||||
}
|
||||
|
||||
args.This()->SetInternalField(0, v8::External::New(buffer));
|
||||
|
||||
struct buffer *root = buffer_root(buffer);
|
||||
~AsciiSliceExt() {
|
||||
//fprintf(stderr, "free ascii slice (%d refs left)\n", blob_->refs);
|
||||
blob_unref(blob_);
|
||||
}
|
||||
|
||||
args.This()->
|
||||
SetIndexedPropertiesToExternalArrayData(&root->bytes + buffer->offset,
|
||||
kExternalUnsignedByteArray,
|
||||
length);
|
||||
|
||||
args.This()->Set(length_symbol, Integer::New(length));
|
||||
const char* data() const { return data_; }
|
||||
size_t length() const { return length_; }
|
||||
|
||||
private:
|
||||
const char *data_;
|
||||
size_t length_;
|
||||
Blob *blob_;
|
||||
};
|
||||
|
||||
|
||||
Handle<Value> Buffer::New(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
Buffer *buffer;
|
||||
if (args[0]->IsInt32()) {
|
||||
// var buffer = new Buffer(1024);
|
||||
size_t length = args[0]->Uint32Value();
|
||||
buffer = new Buffer(length);
|
||||
|
||||
} else if (Buffer::HasInstance(args[0]) && args.Length() > 2) {
|
||||
// var slice = new Buffer(buffer, 123, 130);
|
||||
// args: parent, start, end
|
||||
Buffer *parent = ObjectWrap::Unwrap<Buffer>(args[0]->ToObject());
|
||||
SLICE_ARGS(args[1], args[2])
|
||||
buffer = new Buffer(parent, start, end);
|
||||
} else {
|
||||
return ThrowException(Exception::TypeError(String::New("Bad argument")));
|
||||
}
|
||||
|
||||
buffer->Wrap(args.This());
|
||||
args.This()->SetIndexedPropertiesToExternalArrayData((void*)buffer->data_,
|
||||
kExternalUnsignedByteArray,
|
||||
buffer->length_);
|
||||
args.This()->Set(length_symbol, Integer::New(buffer->length_));
|
||||
return args.This();
|
||||
}
|
||||
|
||||
|
||||
class AsciiSliceExt: public String::ExternalAsciiStringResource {
|
||||
public:
|
||||
Buffer::Buffer(size_t length) : ObjectWrap() {
|
||||
blob_ = blob_new(length);
|
||||
length_ = length;
|
||||
data_ = blob_->data;
|
||||
blob_ref(blob_);
|
||||
|
||||
AsciiSliceExt(struct buffer *root, size_t start, size_t end)
|
||||
{
|
||||
data_ = root->bytes + start;
|
||||
len_ = end - start;
|
||||
root_ = root;
|
||||
buffer_ref(root_);
|
||||
}
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer));
|
||||
}
|
||||
|
||||
~AsciiSliceExt() {
|
||||
buffer_unref(root_);
|
||||
}
|
||||
|
||||
const char* data() const {
|
||||
return data_;
|
||||
}
|
||||
Buffer::Buffer(Buffer *parent, size_t start, size_t end) : ObjectWrap() {
|
||||
blob_ = parent->blob_;
|
||||
assert(blob_->refs > 0);
|
||||
blob_ref(blob_);
|
||||
|
||||
size_t length() const {
|
||||
return len_;
|
||||
}
|
||||
assert(start <= end);
|
||||
length_ = end - start;
|
||||
assert(length_ <= parent->length_);
|
||||
data_ = parent->data_ + start;
|
||||
|
||||
private:
|
||||
const char *data_;
|
||||
size_t len_;
|
||||
struct buffer *root_;
|
||||
};
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer));
|
||||
}
|
||||
|
||||
static Handle<Value> AsciiSlice(const Arguments &args) {
|
||||
|
||||
Buffer::~Buffer() {
|
||||
assert(blob_->refs > 0);
|
||||
//fprintf(stderr, "free buffer (%d refs left)\n", blob_->refs);
|
||||
blob_unref(blob_);
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(-sizeof(Buffer));
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> Buffer::AsciiSlice(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
Buffer *parent = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
SLICE_ARGS(args[0], args[1])
|
||||
|
||||
assert(args.This()->InternalFieldCount() == 1);
|
||||
struct buffer *parent = BufferUnwrap(args.This());
|
||||
|
||||
size_t start_abs = buffer_abs_off(parent, start);
|
||||
size_t end_abs = buffer_abs_off(parent, end);
|
||||
|
||||
assert(start_abs <= end_abs);
|
||||
|
||||
AsciiSliceExt *s = new AsciiSliceExt(buffer_root(parent), start_abs, end_abs);
|
||||
Local<String> string = String::NewExternal(s);
|
||||
|
||||
struct buffer *root = buffer_root(parent);
|
||||
assert(root->refs > 0);
|
||||
|
||||
AsciiSliceExt *ext = new AsciiSliceExt(parent, start, end);
|
||||
Local<String> string = String::NewExternal(ext);
|
||||
// There should be at least two references to the blob now - the parent
|
||||
// and the slice.
|
||||
assert(parent->blob_->refs >= 2);
|
||||
return scope.Close(string);
|
||||
}
|
||||
|
||||
static Handle<Value> Utf8Slice(const Arguments &args) {
|
||||
|
||||
Handle<Value> Buffer::Utf8Slice(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
Buffer *parent = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
SLICE_ARGS(args[0], args[1])
|
||||
|
||||
struct buffer *parent = BufferUnwrap(args.This());
|
||||
size_t start_abs = buffer_abs_off(parent, start);
|
||||
size_t end_abs = buffer_abs_off(parent, end);
|
||||
assert(start_abs <= end_abs);
|
||||
|
||||
struct buffer *root = buffer_root(parent);
|
||||
|
||||
Local<String> string =
|
||||
String::New(reinterpret_cast<const char*>(&root->bytes + start_abs),
|
||||
end_abs - start_abs);
|
||||
const char *data = reinterpret_cast<const char*>(parent->data_ + start);
|
||||
Local<String> string = String::New(data, end - start);
|
||||
return scope.Close(string);
|
||||
}
|
||||
|
||||
static Handle<Value> Slice(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
Handle<Value> Buffer::Slice(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
Local<Value> argv[3] = { args.This(), args[0], args[1] };
|
||||
|
||||
Local<Object> slice =
|
||||
constructor_template->GetFunction()->NewInstance(3, argv);
|
||||
|
||||
return scope.Close(slice);
|
||||
}
|
||||
|
||||
|
||||
// var charsWritten = buffer.utf8Write(string, offset, length);
|
||||
static Handle<Value> Utf8Write(const Arguments &args) {
|
||||
// var charsWritten = buffer.utf8Write(string, offset);
|
||||
Handle<Value> Buffer::Utf8Write(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
struct buffer *buffer = BufferUnwrap(args.This());
|
||||
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
@@ -268,30 +212,28 @@ static Handle<Value> Utf8Write(const Arguments &args) {
|
||||
|
||||
size_t offset = args[1]->Int32Value();
|
||||
|
||||
char *p = buffer_p(buffer, offset);
|
||||
if (buffer_p(buffer, offset) == NULL) {
|
||||
if (offset >= buffer->length_) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Offset is out of bounds")));
|
||||
}
|
||||
|
||||
size_t toWrite = args[2]->Int32Value();
|
||||
const char *p = buffer->data_ + offset;
|
||||
|
||||
if (buffer_remaining(buffer, offset) < toWrite) {
|
||||
if (s->Length() + offset > buffer->length_) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Length is out of bounds")));
|
||||
"Not enough space in Buffer for string")));
|
||||
}
|
||||
|
||||
int written = s->WriteUtf8(p, toWrite);
|
||||
|
||||
int written = s->WriteUtf8((char*)p);
|
||||
return scope.Close(Integer::New(written));
|
||||
}
|
||||
|
||||
|
||||
// var charsWritten = buffer.asciiWrite(string, offset, length);
|
||||
static Handle<Value> AsciiWrite(const Arguments &args) {
|
||||
// var charsWritten = buffer.asciiWrite(string, offset);
|
||||
Handle<Value> Buffer::AsciiWrite(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
struct buffer *buffer = BufferUnwrap(args.This());
|
||||
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
@@ -302,30 +244,25 @@ static Handle<Value> AsciiWrite(const Arguments &args) {
|
||||
|
||||
size_t offset = args[1]->Int32Value();
|
||||
|
||||
char *p = buffer_p(buffer, offset);
|
||||
if (buffer_p(buffer, offset) == NULL) {
|
||||
if (offset >= buffer->length_) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Offset is out of bounds")));
|
||||
}
|
||||
|
||||
size_t toWrite = args[2]->Int32Value();
|
||||
const char *p = buffer->data_ + offset;
|
||||
|
||||
if (buffer_remaining(buffer, offset) < toWrite) {
|
||||
if (s->Length() + offset > buffer->length_) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Length is out of bounds")));
|
||||
"Not enough space in Buffer for string")));
|
||||
}
|
||||
|
||||
// TODO Expose the second argument of WriteAscii?
|
||||
// Could avoid doing slices when the string doesn't fit in a buffer. V8
|
||||
// slice() does copy the string, so exposing that argument would help.
|
||||
|
||||
int written = s->WriteAscii(p, 0, toWrite);
|
||||
|
||||
int written = s->WriteAscii((char*)p);
|
||||
return scope.Close(Integer::New(written));
|
||||
}
|
||||
|
||||
|
||||
static Handle<Value> Utf8Length(const Arguments &args) {
|
||||
// var nbytes = Buffer.utf8Length("string")
|
||||
Handle<Value> Buffer::Utf8Length(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
if (!args[0]->IsString()) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
@@ -336,29 +273,37 @@ static Handle<Value> Utf8Length(const Arguments &args) {
|
||||
}
|
||||
|
||||
|
||||
void InitBuffer(Handle<Object> target) {
|
||||
bool Buffer::HasInstance(Handle<Value> val) {
|
||||
if (!val->IsObject()) return false;
|
||||
Local<Object> obj = val->ToObject();
|
||||
return constructor_template->HasInstance(obj);
|
||||
}
|
||||
|
||||
|
||||
void Buffer::Initialize(Handle<Object> target) {
|
||||
HandleScope scope;
|
||||
|
||||
length_symbol = Persistent<String>::New(String::NewSymbol("length"));
|
||||
|
||||
Local<FunctionTemplate> t = FunctionTemplate::New(Constructor);
|
||||
Local<FunctionTemplate> t = FunctionTemplate::New(Buffer::New);
|
||||
constructor_template = Persistent<FunctionTemplate>::New(t);
|
||||
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
constructor_template->SetClassName(String::NewSymbol("Buffer"));
|
||||
|
||||
// copy free
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", AsciiSlice);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "slice", Slice);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", Buffer::AsciiSlice);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "slice", Buffer::Slice);
|
||||
// TODO NODE_SET_PROTOTYPE_METHOD(t, "utf16Slice", Utf16Slice);
|
||||
// copy
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Utf8Slice);
|
||||
// copy
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Buffer::Utf8Slice);
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Utf8Write);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", AsciiWrite);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Buffer::Utf8Write);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", Buffer::AsciiWrite);
|
||||
|
||||
NODE_SET_METHOD(constructor_template->GetFunction(), "utf8Length", Utf8Length);
|
||||
NODE_SET_METHOD(constructor_template->GetFunction(), "utf8Length", Buffer::Utf8Length);
|
||||
|
||||
target->Set(String::NewSymbol("Buffer"), constructor_template->GetFunction());
|
||||
}
|
||||
|
||||
|
||||
} // namespace node
|
||||
|
||||
Reference in New Issue
Block a user