Browse Source

src: do proper StringBytes error handling

- Return `MaybeLocal`s from `StringBytes::Encode`
- Add an `error` out parameter to pass JS exceptions to the callers
  (instead of directly throwing)
- Simplify some of the string generation methods in `string_bytes.cc`
  by unifying the `EXTERN_APEX` logic
- Reduce usage of deprecated V8 APIs.
- Remove error handling logic from JS, the `buffer.*Slice()` methods
  now throw errors themselves.
- Left TODO comments for future semver-major error message
  improvements.

This paves the way for better error messages coming out of the
StringBytes methods.

Ref: https://github.com/nodejs/node/issues/3175
PR-URL: https://github.com/nodejs/node/pull/12765
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
v6
Anna Henningsen 8 years ago
parent
commit
d56a7e640f
No known key found for this signature in database GPG Key ID: D8B9F5AEAE84E4CF
  1. 42
      lib/buffer.js
  2. 14
      src/fs_event_wrap.cc
  3. 8
      src/node.cc
  4. 32
      src/node_buffer.cc
  5. 37
      src/node_crypto.cc
  6. 78
      src/node_file.cc
  7. 29
      src/node_os.cc
  8. 244
      src/string_bytes.cc
  9. 25
      src/string_bytes.h

42
lib/buffer.js

@ -567,34 +567,30 @@ Buffer.prototype.copy = function(target, targetStart, sourceStart, sourceEnd) {
// This behaves neither like String nor Uint8Array in that we set start/end // This behaves neither like String nor Uint8Array in that we set start/end
// to their upper/lower bounds if the value passed is out of range. // to their upper/lower bounds if the value passed is out of range.
Buffer.prototype.toString = function(encoding, start, end) { Buffer.prototype.toString = function(encoding, start, end) {
var result;
if (arguments.length === 0) { if (arguments.length === 0) {
result = this.utf8Slice(0, this.length); return this.utf8Slice(0, this.length);
} else { }
const len = this.length;
if (len === 0)
return '';
if (!start || start < 0) const len = this.length;
start = 0; if (len === 0)
else if (start >= len) return '';
return '';
if (end === undefined || end > len) if (!start || start < 0)
end = len; start = 0;
else if (end <= 0) else if (start >= len)
return ''; return '';
start |= 0; if (end === undefined || end > len)
end |= 0; end = len;
else if (end <= 0)
return '';
if (end <= start) start |= 0;
return ''; end |= 0;
result = stringSlice(this, encoding, start, end);
} if (end <= start)
if (result === undefined) return '';
throw new Error('"toString()" failed'); return stringSlice(this, encoding, start, end);
return result;
}; };

14
src/fs_event_wrap.cc

@ -39,6 +39,7 @@ using v8::FunctionTemplate;
using v8::HandleScope; using v8::HandleScope;
using v8::Integer; using v8::Integer;
using v8::Local; using v8::Local;
using v8::MaybeLocal;
using v8::Object; using v8::Object;
using v8::String; using v8::String;
using v8::Value; using v8::Value;
@ -187,17 +188,20 @@ void FSEventWrap::OnEvent(uv_fs_event_t* handle, const char* filename,
}; };
if (filename != nullptr) { if (filename != nullptr) {
Local<Value> fn = StringBytes::Encode(env->isolate(), Local<Value> error;
filename, MaybeLocal<Value> fn = StringBytes::Encode(env->isolate(),
wrap->encoding_); filename,
wrap->encoding_,
&error);
if (fn.IsEmpty()) { if (fn.IsEmpty()) {
argv[0] = Integer::New(env->isolate(), UV_EINVAL); argv[0] = Integer::New(env->isolate(), UV_EINVAL);
argv[2] = StringBytes::Encode(env->isolate(), argv[2] = StringBytes::Encode(env->isolate(),
filename, filename,
strlen(filename), strlen(filename),
BUFFER); BUFFER,
&error).ToLocalChecked();
} else { } else {
argv[2] = fn; argv[2] = fn.ToLocalChecked();
} }
} }

8
src/node.cc

@ -1570,11 +1570,15 @@ Local<Value> Encode(Isolate* isolate,
size_t len, size_t len,
enum encoding encoding) { enum encoding encoding) {
CHECK_NE(encoding, UCS2); CHECK_NE(encoding, UCS2);
return StringBytes::Encode(isolate, buf, len, encoding); Local<Value> error;
return StringBytes::Encode(isolate, buf, len, encoding, &error)
.ToLocalChecked();
} }
Local<Value> Encode(Isolate* isolate, const uint16_t* buf, size_t len) { Local<Value> Encode(Isolate* isolate, const uint16_t* buf, size_t len) {
return StringBytes::Encode(isolate, buf, len); Local<Value> error;
return StringBytes::Encode(isolate, buf, len, &error)
.ToLocalChecked();
} }
// Returns -1 if the handle was not valid for decoding // Returns -1 if the handle was not valid for decoding

32
src/node_buffer.cc

@ -465,14 +465,26 @@ void StringSlice(const FunctionCallbackInfo<Value>& args) {
SLICE_START_END(args[0], args[1], ts_obj_length) SLICE_START_END(args[0], args[1], ts_obj_length)
args.GetReturnValue().Set( Local<Value> error;
StringBytes::Encode(isolate, ts_obj_data + start, length, encoding)); MaybeLocal<Value> ret =
StringBytes::Encode(isolate,
ts_obj_data + start,
length,
encoding,
&error);
if (ret.IsEmpty()) {
CHECK(!error.IsEmpty());
isolate->ThrowException(error);
return;
}
args.GetReturnValue().Set(ret.ToLocalChecked());
} }
template <> template <>
void StringSlice<UCS2>(const FunctionCallbackInfo<Value>& args) { void StringSlice<UCS2>(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Isolate* isolate = args.GetIsolate();
Environment* env = Environment::GetCurrent(isolate);
THROW_AND_RETURN_UNLESS_BUFFER(env, args.This()); THROW_AND_RETURN_UNLESS_BUFFER(env, args.This());
SPREAD_BUFFER_ARG(args.This(), ts_obj); SPREAD_BUFFER_ARG(args.This(), ts_obj);
@ -509,10 +521,22 @@ void StringSlice<UCS2>(const FunctionCallbackInfo<Value>& args) {
buf = reinterpret_cast<const uint16_t*>(data); buf = reinterpret_cast<const uint16_t*>(data);
} }
args.GetReturnValue().Set(StringBytes::Encode(env->isolate(), buf, length)); Local<Value> error;
MaybeLocal<Value> ret =
StringBytes::Encode(isolate,
buf,
length,
&error);
if (release) if (release)
delete[] buf; delete[] buf;
if (ret.IsEmpty()) {
CHECK(!error.IsEmpty());
isolate->ThrowException(error);
return;
}
args.GetReturnValue().Set(ret.ToLocalChecked());
} }

37
src/node_crypto.cc

@ -104,6 +104,7 @@ using v8::Integer;
using v8::Isolate; using v8::Isolate;
using v8::Local; using v8::Local;
using v8::Maybe; using v8::Maybe;
using v8::MaybeLocal;
using v8::Null; using v8::Null;
using v8::Object; using v8::Object;
using v8::Persistent; using v8::Persistent;
@ -3811,12 +3812,20 @@ void Hmac::HmacDigest(const FunctionCallbackInfo<Value>& args) {
md_len = 0; md_len = 0;
} }
Local<Value> rc = StringBytes::Encode(env->isolate(), Local<Value> error;
reinterpret_cast<const char*>(md_value), MaybeLocal<Value> rc =
md_len, StringBytes::Encode(env->isolate(),
encoding); reinterpret_cast<const char*>(md_value),
md_len,
encoding,
&error);
delete[] md_value; delete[] md_value;
args.GetReturnValue().Set(rc); if (rc.IsEmpty()) {
CHECK(!error.IsEmpty());
env->isolate()->ThrowException(error);
return;
}
args.GetReturnValue().Set(rc.ToLocalChecked());
} }
@ -3936,11 +3945,19 @@ void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
EVP_MD_CTX_cleanup(&hash->mdctx_); EVP_MD_CTX_cleanup(&hash->mdctx_);
hash->finalized_ = true; hash->finalized_ = true;
Local<Value> rc = StringBytes::Encode(env->isolate(), Local<Value> error;
reinterpret_cast<const char*>(md_value), MaybeLocal<Value> rc =
md_len, StringBytes::Encode(env->isolate(),
encoding); reinterpret_cast<const char*>(md_value),
args.GetReturnValue().Set(rc); md_len,
encoding,
&error);
if (rc.IsEmpty()) {
CHECK(!error.IsEmpty());
env->isolate()->ThrowException(error);
return;
}
args.GetReturnValue().Set(rc.ToLocalChecked());
} }

78
src/node_file.cc

@ -57,6 +57,7 @@ using v8::FunctionTemplate;
using v8::HandleScope; using v8::HandleScope;
using v8::Integer; using v8::Integer;
using v8::Local; using v8::Local;
using v8::MaybeLocal;
using v8::Number; using v8::Number;
using v8::Object; using v8::Object;
using v8::String; using v8::String;
@ -172,7 +173,8 @@ void After(uv_fs_t *req) {
// Allocate space for two args. We may only use one depending on the case. // Allocate space for two args. We may only use one depending on the case.
// (Feel free to increase this if you need more) // (Feel free to increase this if you need more)
Local<Value> argv[2]; Local<Value> argv[2];
Local<Value> link; MaybeLocal<Value> link;
Local<Value> error;
if (req->result < 0) { if (req->result < 0) {
// An error happened. // An error happened.
@ -232,10 +234,13 @@ void After(uv_fs_t *req) {
break; break;
case UV_FS_MKDTEMP: case UV_FS_MKDTEMP:
{
link = StringBytes::Encode(env->isolate(), link = StringBytes::Encode(env->isolate(),
static_cast<const char*>(req->path), static_cast<const char*>(req->path),
req_wrap->encoding_); req_wrap->encoding_,
&error);
if (link.IsEmpty()) { if (link.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
argv[0] = UVException(env->isolate(), argv[0] = UVException(env->isolate(),
UV_EINVAL, UV_EINVAL,
req_wrap->syscall(), req_wrap->syscall(),
@ -243,15 +248,18 @@ void After(uv_fs_t *req) {
req->path, req->path,
req_wrap->data()); req_wrap->data());
} else { } else {
argv[1] = link; argv[1] = link.ToLocalChecked();
} }
break; break;
}
case UV_FS_READLINK: case UV_FS_READLINK:
link = StringBytes::Encode(env->isolate(), link = StringBytes::Encode(env->isolate(),
static_cast<const char*>(req->ptr), static_cast<const char*>(req->ptr),
req_wrap->encoding_); req_wrap->encoding_,
&error);
if (link.IsEmpty()) { if (link.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
argv[0] = UVException(env->isolate(), argv[0] = UVException(env->isolate(),
UV_EINVAL, UV_EINVAL,
req_wrap->syscall(), req_wrap->syscall(),
@ -259,15 +267,17 @@ void After(uv_fs_t *req) {
req->path, req->path,
req_wrap->data()); req_wrap->data());
} else { } else {
argv[1] = link; argv[1] = link.ToLocalChecked();
} }
break; break;
case UV_FS_REALPATH: case UV_FS_REALPATH:
link = StringBytes::Encode(env->isolate(), link = StringBytes::Encode(env->isolate(),
static_cast<const char*>(req->ptr), static_cast<const char*>(req->ptr),
req_wrap->encoding_); req_wrap->encoding_,
&error);
if (link.IsEmpty()) { if (link.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
argv[0] = UVException(env->isolate(), argv[0] = UVException(env->isolate(),
UV_EINVAL, UV_EINVAL,
req_wrap->syscall(), req_wrap->syscall(),
@ -275,7 +285,7 @@ void After(uv_fs_t *req) {
req->path, req->path,
req_wrap->data()); req_wrap->data());
} else { } else {
argv[1] = link; argv[1] = link.ToLocalChecked();
} }
break; break;
@ -306,10 +316,13 @@ void After(uv_fs_t *req) {
break; break;
} }
Local<Value> filename = StringBytes::Encode(env->isolate(), MaybeLocal<Value> filename =
ent.name, StringBytes::Encode(env->isolate(),
req_wrap->encoding_); ent.name,
req_wrap->encoding_,
&error);
if (filename.IsEmpty()) { if (filename.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
argv[0] = UVException(env->isolate(), argv[0] = UVException(env->isolate(),
UV_EINVAL, UV_EINVAL,
req_wrap->syscall(), req_wrap->syscall(),
@ -318,7 +331,7 @@ void After(uv_fs_t *req) {
req_wrap->data()); req_wrap->data());
break; break;
} }
name_argv[name_idx++] = filename; name_argv[name_idx++] = filename.ToLocalChecked();
if (name_idx >= arraysize(name_argv)) { if (name_idx >= arraysize(name_argv)) {
fn->Call(env->context(), names, name_idx, name_argv) fn->Call(env->context(), names, name_idx, name_argv)
@ -681,16 +694,20 @@ static void ReadLink(const FunctionCallbackInfo<Value>& args) {
} else { } else {
SYNC_CALL(readlink, *path, *path) SYNC_CALL(readlink, *path, *path)
const char* link_path = static_cast<const char*>(SYNC_REQ.ptr); const char* link_path = static_cast<const char*>(SYNC_REQ.ptr);
Local<Value> rc = StringBytes::Encode(env->isolate(),
link_path, Local<Value> error;
encoding); MaybeLocal<Value> rc = StringBytes::Encode(env->isolate(),
link_path,
encoding,
&error);
if (rc.IsEmpty()) { if (rc.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
return env->ThrowUVException(UV_EINVAL, return env->ThrowUVException(UV_EINVAL,
"readlink", "readlink",
"Invalid character encoding for link", "Invalid character encoding for link",
*path); *path);
} }
args.GetReturnValue().Set(rc); args.GetReturnValue().Set(rc.ToLocalChecked());
} }
} }
@ -852,16 +869,20 @@ static void RealPath(const FunctionCallbackInfo<Value>& args) {
} else { } else {
SYNC_CALL(realpath, *path, *path); SYNC_CALL(realpath, *path, *path);
const char* link_path = static_cast<const char*>(SYNC_REQ.ptr); const char* link_path = static_cast<const char*>(SYNC_REQ.ptr);
Local<Value> rc = StringBytes::Encode(env->isolate(),
link_path, Local<Value> error;
encoding); MaybeLocal<Value> rc = StringBytes::Encode(env->isolate(),
link_path,
encoding,
&error);
if (rc.IsEmpty()) { if (rc.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
return env->ThrowUVException(UV_EINVAL, return env->ThrowUVException(UV_EINVAL,
"realpath", "realpath",
"Invalid character encoding for path", "Invalid character encoding for path",
*path); *path);
} }
args.GetReturnValue().Set(rc); args.GetReturnValue().Set(rc.ToLocalChecked());
} }
} }
@ -903,17 +924,20 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
if (r != 0) if (r != 0)
return env->ThrowUVException(r, "readdir", "", *path); return env->ThrowUVException(r, "readdir", "", *path);
Local<Value> filename = StringBytes::Encode(env->isolate(), Local<Value> error;
ent.name, MaybeLocal<Value> filename = StringBytes::Encode(env->isolate(),
encoding); ent.name,
encoding,
&error);
if (filename.IsEmpty()) { if (filename.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
return env->ThrowUVException(UV_EINVAL, return env->ThrowUVException(UV_EINVAL,
"readdir", "readdir",
"Invalid character encoding for filename", "Invalid character encoding for filename",
*path); *path);
} }
name_v[name_idx++] = filename; name_v[name_idx++] = filename.ToLocalChecked();
if (name_idx >= arraysize(name_v)) { if (name_idx >= arraysize(name_v)) {
fn->Call(env->context(), names, name_idx, name_v) fn->Call(env->context(), names, name_idx, name_v)
@ -1367,14 +1391,18 @@ static void Mkdtemp(const FunctionCallbackInfo<Value>& args) {
} else { } else {
SYNC_CALL(mkdtemp, *tmpl, *tmpl); SYNC_CALL(mkdtemp, *tmpl, *tmpl);
const char* path = static_cast<const char*>(SYNC_REQ.path); const char* path = static_cast<const char*>(SYNC_REQ.path);
Local<Value> rc = StringBytes::Encode(env->isolate(), path, encoding);
Local<Value> error;
MaybeLocal<Value> rc =
StringBytes::Encode(env->isolate(), path, encoding, &error);
if (rc.IsEmpty()) { if (rc.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
return env->ThrowUVException(UV_EINVAL, return env->ThrowUVException(UV_EINVAL,
"mkdtemp", "mkdtemp",
"Invalid character encoding for filename", "Invalid character encoding for filename",
*tmpl); *tmpl);
} }
args.GetReturnValue().Set(rc); args.GetReturnValue().Set(rc.ToLocalChecked());
} }
} }

29
src/node_os.cc

@ -357,36 +357,43 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
return env->ThrowUVException(err, "uv_os_get_passwd"); return env->ThrowUVException(err, "uv_os_get_passwd");
} }
Local<Value> error;
Local<Value> uid = Number::New(env->isolate(), pwd.uid); Local<Value> uid = Number::New(env->isolate(), pwd.uid);
Local<Value> gid = Number::New(env->isolate(), pwd.gid); Local<Value> gid = Number::New(env->isolate(), pwd.gid);
Local<Value> username = StringBytes::Encode(env->isolate(), MaybeLocal<Value> username = StringBytes::Encode(env->isolate(),
pwd.username, pwd.username,
encoding); encoding,
Local<Value> homedir = StringBytes::Encode(env->isolate(), &error);
pwd.homedir, MaybeLocal<Value> homedir = StringBytes::Encode(env->isolate(),
encoding); pwd.homedir,
Local<Value> shell; encoding,
&error);
MaybeLocal<Value> shell;
if (pwd.shell == NULL) if (pwd.shell == NULL)
shell = Null(env->isolate()); shell = Null(env->isolate());
else else
shell = StringBytes::Encode(env->isolate(), pwd.shell, encoding); shell = StringBytes::Encode(env->isolate(), pwd.shell, encoding, &error);
uv_os_free_passwd(&pwd); uv_os_free_passwd(&pwd);
if (username.IsEmpty()) { if (username.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
return env->ThrowUVException(UV_EINVAL, return env->ThrowUVException(UV_EINVAL,
"uv_os_get_passwd", "uv_os_get_passwd",
"Invalid character encoding for username"); "Invalid character encoding for username");
} }
if (homedir.IsEmpty()) { if (homedir.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
return env->ThrowUVException(UV_EINVAL, return env->ThrowUVException(UV_EINVAL,
"uv_os_get_passwd", "uv_os_get_passwd",
"Invalid character encoding for homedir"); "Invalid character encoding for homedir");
} }
if (shell.IsEmpty()) { if (shell.IsEmpty()) {
// TODO(addaleax): Use `error` itself here.
return env->ThrowUVException(UV_EINVAL, return env->ThrowUVException(UV_EINVAL,
"uv_os_get_passwd", "uv_os_get_passwd",
"Invalid character encoding for shell"); "Invalid character encoding for shell");
@ -396,9 +403,9 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
entry->Set(env->uid_string(), uid); entry->Set(env->uid_string(), uid);
entry->Set(env->gid_string(), gid); entry->Set(env->gid_string(), gid);
entry->Set(env->username_string(), username); entry->Set(env->username_string(), username.ToLocalChecked());
entry->Set(env->homedir_string(), homedir); entry->Set(env->homedir_string(), homedir.ToLocalChecked());
entry->Set(env->shell_string(), shell); entry->Set(env->shell_string(), shell.ToLocalChecked());
args.GetReturnValue().Set(entry); args.GetReturnValue().Set(entry);
} }

244
src/string_bytes.cc

@ -35,14 +35,26 @@
// use external string resources. // use external string resources.
#define EXTERN_APEX 0xFBEE9 #define EXTERN_APEX 0xFBEE9
// TODO(addaleax): These should all have better error messages. In particular,
// they should mention what the actual limits are.
#define SB_MALLOC_FAILED_ERROR \
v8::Exception::Error(OneByteString(isolate, "\"toString()\" failed"))
#define SB_STRING_TOO_LONG_ERROR \
v8::Exception::Error(OneByteString(isolate, "\"toString()\" failed"))
#define SB_BUFFER_CREATION_ERROR \
v8::Exception::Error(OneByteString(isolate, "\"toString()\" failed"))
#define SB_BUFFER_SIZE_EXCEEDED_ERROR \
v8::Exception::Error(OneByteString(isolate, "\"toString()\" failed"))
namespace node { namespace node {
using v8::EscapableHandleScope;
using v8::HandleScope; using v8::HandleScope;
using v8::Isolate; using v8::Isolate;
using v8::Local; using v8::Local;
using v8::MaybeLocal; using v8::MaybeLocal;
using v8::Object;
using v8::String; using v8::String;
using v8::Value; using v8::Value;
@ -68,46 +80,56 @@ class ExternString: public ResourceType {
return length() * sizeof(*data()); return length() * sizeof(*data());
} }
static Local<String> NewFromCopy(Isolate* isolate, static MaybeLocal<Value> NewFromCopy(Isolate* isolate,
const TypeName* data, const TypeName* data,
size_t length) { size_t length,
EscapableHandleScope scope(isolate); Local<Value>* error) {
if (length == 0) if (length == 0)
return scope.Escape(String::Empty(isolate)); return String::Empty(isolate);
if (length < EXTERN_APEX)
return NewSimpleFromCopy(isolate, data, length, error);
TypeName* new_data = node::UncheckedMalloc<TypeName>(length); TypeName* new_data = node::UncheckedMalloc<TypeName>(length);
if (new_data == nullptr) { if (new_data == nullptr) {
return Local<String>(); *error = SB_MALLOC_FAILED_ERROR;
return MaybeLocal<Value>();
} }
memcpy(new_data, data, length * sizeof(*new_data)); memcpy(new_data, data, length * sizeof(*new_data));
return scope.Escape(ExternString<ResourceType, TypeName>::New(isolate, return ExternString<ResourceType, TypeName>::New(isolate,
new_data, new_data,
length)); length,
error);
} }
// uses "data" for external resource, and will be free'd on gc // uses "data" for external resource, and will be free'd on gc
static Local<String> New(Isolate* isolate, static MaybeLocal<Value> New(Isolate* isolate,
const TypeName* data, TypeName* data,
size_t length) { size_t length,
EscapableHandleScope scope(isolate); Local<Value>* error) {
if (length == 0) if (length == 0)
return scope.Escape(String::Empty(isolate)); return String::Empty(isolate);
if (length < EXTERN_APEX) {
MaybeLocal<Value> str = NewSimpleFromCopy(isolate, data, length, error);
free(data);
return str;
}
ExternString* h_str = new ExternString<ResourceType, TypeName>(isolate, ExternString* h_str = new ExternString<ResourceType, TypeName>(isolate,
data, data,
length); length);
MaybeLocal<String> str = NewExternal(isolate, h_str); MaybeLocal<Value> str = NewExternal(isolate, h_str);
isolate->AdjustAmountOfExternalAllocatedMemory(h_str->byte_length()); isolate->AdjustAmountOfExternalAllocatedMemory(h_str->byte_length());
if (str.IsEmpty()) { if (str.IsEmpty()) {
delete h_str; delete h_str;
return Local<String>(); *error = SB_STRING_TOO_LONG_ERROR;
return MaybeLocal<Value>();
} }
return scope.Escape(str.ToLocalChecked()); return str.ToLocalChecked();
} }
inline Isolate* isolate() const { return isolate_; } inline Isolate* isolate() const { return isolate_; }
@ -115,8 +137,14 @@ class ExternString: public ResourceType {
private: private:
ExternString(Isolate* isolate, const TypeName* data, size_t length) ExternString(Isolate* isolate, const TypeName* data, size_t length)
: isolate_(isolate), data_(data), length_(length) { } : isolate_(isolate), data_(data), length_(length) { }
static MaybeLocal<String> NewExternal(Isolate* isolate, static MaybeLocal<Value> NewExternal(Isolate* isolate,
ExternString* h_str); ExternString* h_str);
// This method does not actually create ExternString instances.
static MaybeLocal<Value> NewSimpleFromCopy(Isolate* isolate,
const TypeName* data,
size_t length,
Local<Value>* error);
Isolate* isolate_; Isolate* isolate_;
const TypeName* data_; const TypeName* data_;
@ -131,16 +159,51 @@ typedef ExternString<String::ExternalStringResource,
template <> template <>
MaybeLocal<String> ExternOneByteString::NewExternal( MaybeLocal<Value> ExternOneByteString::NewExternal(
Isolate* isolate, ExternOneByteString* h_str) { Isolate* isolate, ExternOneByteString* h_str) {
return String::NewExternalOneByte(isolate, h_str); return String::NewExternalOneByte(isolate, h_str).FromMaybe(Local<Value>());
} }
template <> template <>
MaybeLocal<String> ExternTwoByteString::NewExternal( MaybeLocal<Value> ExternTwoByteString::NewExternal(
Isolate* isolate, ExternTwoByteString* h_str) { Isolate* isolate, ExternTwoByteString* h_str) {
return String::NewExternalTwoByte(isolate, h_str); return String::NewExternalTwoByte(isolate, h_str).FromMaybe(Local<Value>());
}
template <>
MaybeLocal<Value> ExternOneByteString::NewSimpleFromCopy(Isolate* isolate,
const char* data,
size_t length,
Local<Value>* error) {
MaybeLocal<String> str =
String::NewFromOneByte(isolate,
reinterpret_cast<const uint8_t*>(data),
v8::NewStringType::kNormal,
length);
if (str.IsEmpty()) {
*error = SB_STRING_TOO_LONG_ERROR;
return MaybeLocal<Value>();
}
return str.ToLocalChecked();
}
template <>
MaybeLocal<Value> ExternTwoByteString::NewSimpleFromCopy(Isolate* isolate,
const uint16_t* data,
size_t length,
Local<Value>* error) {
MaybeLocal<String> str =
String::NewFromTwoByte(isolate,
data,
v8::NewStringType::kNormal,
length);
if (str.IsEmpty()) {
*error = SB_STRING_TOO_LONG_ERROR;
return MaybeLocal<Value>();
}
return str.ToLocalChecked();
} }
} // anonymous namespace } // anonymous namespace
@ -610,97 +673,93 @@ static size_t hex_encode(const char* src, size_t slen, char* dst, size_t dlen) {
} }
#define CHECK_BUFLEN_IN_RANGE(len) \
do { \
if ((len) > Buffer::kMaxLength) { \
*error = SB_BUFFER_SIZE_EXCEEDED_ERROR; \
return MaybeLocal<Value>(); \
} \
} while (0)
Local<Value> StringBytes::Encode(Isolate* isolate,
const char* buf,
size_t buflen,
enum encoding encoding) {
EscapableHandleScope scope(isolate);
MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
const char* buf,
size_t buflen,
enum encoding encoding,
Local<Value>* error) {
CHECK_NE(encoding, UCS2); CHECK_NE(encoding, UCS2);
CHECK_LE(buflen, Buffer::kMaxLength); CHECK_BUFLEN_IN_RANGE(buflen);
if (!buflen && encoding != BUFFER)
return scope.Escape(String::Empty(isolate)); *error = Local<Value>();
if (!buflen && encoding != BUFFER) {
return String::Empty(isolate);
}
MaybeLocal<String> val;
Local<String> val;
switch (encoding) { switch (encoding) {
case BUFFER: case BUFFER:
{ {
Local<Object> vbuf = auto maybe_buf = Buffer::Copy(isolate, buf, buflen);
Buffer::Copy(isolate, buf, buflen).ToLocalChecked(); if (maybe_buf.IsEmpty()) {
return scope.Escape(vbuf); *error = SB_BUFFER_CREATION_ERROR;
return MaybeLocal<Value>();
}
return maybe_buf.ToLocalChecked();
} }
case ASCII: case ASCII:
if (contains_non_ascii(buf, buflen)) { if (contains_non_ascii(buf, buflen)) {
char* out = node::UncheckedMalloc(buflen); char* out = node::UncheckedMalloc(buflen);
if (out == nullptr) { if (out == nullptr) {
return Local<String>(); *error = SB_MALLOC_FAILED_ERROR;
return MaybeLocal<Value>();
} }
force_ascii(buf, out, buflen); force_ascii(buf, out, buflen);
if (buflen < EXTERN_APEX) { return ExternOneByteString::New(isolate, out, buflen, error);
val = OneByteString(isolate, out, buflen);
free(out);
} else {
val = ExternOneByteString::New(isolate, out, buflen);
}
} else { } else {
if (buflen < EXTERN_APEX) return ExternOneByteString::NewFromCopy(isolate, buf, buflen, error);
val = OneByteString(isolate, buf, buflen);
else
val = ExternOneByteString::NewFromCopy(isolate, buf, buflen);
} }
break;
case UTF8: case UTF8:
val = String::NewFromUtf8(isolate, val = String::NewFromUtf8(isolate,
buf, buf,
String::kNormalString, v8::NewStringType::kNormal,
buflen); buflen);
break; if (val.IsEmpty()) {
*error = SB_STRING_TOO_LONG_ERROR;
return MaybeLocal<Value>();
}
return val.ToLocalChecked();
case LATIN1: case LATIN1:
if (buflen < EXTERN_APEX) return ExternOneByteString::NewFromCopy(isolate, buf, buflen, error);
val = OneByteString(isolate, buf, buflen);
else
val = ExternOneByteString::NewFromCopy(isolate, buf, buflen);
break;
case BASE64: { case BASE64: {
size_t dlen = base64_encoded_size(buflen); size_t dlen = base64_encoded_size(buflen);
char* dst = node::UncheckedMalloc(dlen); char* dst = node::UncheckedMalloc(dlen);
if (dst == nullptr) { if (dst == nullptr) {
return Local<String>(); *error = SB_MALLOC_FAILED_ERROR;
return MaybeLocal<Value>();
} }
size_t written = base64_encode(buf, buflen, dst, dlen); size_t written = base64_encode(buf, buflen, dst, dlen);
CHECK_EQ(written, dlen); CHECK_EQ(written, dlen);
if (dlen < EXTERN_APEX) { return ExternOneByteString::New(isolate, dst, dlen, error);
val = OneByteString(isolate, dst, dlen);
free(dst);
} else {
val = ExternOneByteString::New(isolate, dst, dlen);
}
break;
} }
case HEX: { case HEX: {
size_t dlen = buflen * 2; size_t dlen = buflen * 2;
char* dst = node::UncheckedMalloc(dlen); char* dst = node::UncheckedMalloc(dlen);
if (dst == nullptr) { if (dst == nullptr) {
return Local<String>(); *error = SB_MALLOC_FAILED_ERROR;
return MaybeLocal<Value>();
} }
size_t written = hex_encode(buf, buflen, dst, dlen); size_t written = hex_encode(buf, buflen, dst, dlen);
CHECK_EQ(written, dlen); CHECK_EQ(written, dlen);
if (dlen < EXTERN_APEX) { return ExternOneByteString::New(isolate, dst, dlen, error);
val = OneByteString(isolate, dst, dlen);
free(dst);
} else {
val = ExternOneByteString::New(isolate, dst, dlen);
}
break;
} }
default: default:
@ -708,13 +767,17 @@ Local<Value> StringBytes::Encode(Isolate* isolate,
break; break;
} }
return scope.Escape(val); UNREACHABLE();
} }
Local<Value> StringBytes::Encode(Isolate* isolate, MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
const uint16_t* buf, const uint16_t* buf,
size_t buflen) { size_t buflen,
Local<Value>* error) {
CHECK_BUFLEN_IN_RANGE(buflen);
*error = Local<Value>();
// Node's "ucs2" encoding expects LE character data inside a // Node's "ucs2" encoding expects LE character data inside a
// Buffer, so we need to reorder on BE platforms. See // Buffer, so we need to reorder on BE platforms. See
// http://nodejs.org/api/buffer.html regarding Node's "ucs2" // http://nodejs.org/api/buffer.html regarding Node's "ucs2"
@ -727,24 +790,15 @@ Local<Value> StringBytes::Encode(Isolate* isolate,
buf = &dst[0]; buf = &dst[0];
} }
Local<String> val; return ExternTwoByteString::NewFromCopy(isolate, buf, buflen, error);
if (buflen < EXTERN_APEX) {
val = String::NewFromTwoByte(isolate,
buf,
String::kNormalString,
buflen);
} else {
val = ExternTwoByteString::NewFromCopy(isolate, buf, buflen);
}
return val;
} }
Local<Value> StringBytes::Encode(Isolate* isolate, MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
const char* buf, const char* buf,
enum encoding encoding) { enum encoding encoding,
Local<Value>* error) {
const size_t len = strlen(buf); const size_t len = strlen(buf);
Local<Value> ret; MaybeLocal<Value> ret;
if (encoding == UCS2) { if (encoding == UCS2) {
// In Node, UCS2 means utf16le. The data must be in little-endian // In Node, UCS2 means utf16le. The data must be in little-endian
// order and must be aligned on 2-bytes. This returns an empty // order and must be aligned on 2-bytes. This returns an empty
@ -763,9 +817,9 @@ Local<Value> StringBytes::Encode(Isolate* isolate,
} }
ret = vec.empty() ? ret = vec.empty() ?
static_cast< Local<Value> >(String::Empty(isolate)) static_cast< Local<Value> >(String::Empty(isolate))
: StringBytes::Encode(isolate, &vec[0], vec.size()); : StringBytes::Encode(isolate, &vec[0], vec.size(), error);
} else { } else {
ret = StringBytes::Encode(isolate, buf, len, encoding); ret = StringBytes::Encode(isolate, buf, len, encoding, error);
} }
return ret; return ret;
} }

25
src/string_bytes.h

@ -105,19 +105,22 @@ class StringBytes {
// Take the bytes in the src, and turn it into a Buffer or String. // Take the bytes in the src, and turn it into a Buffer or String.
// Don't call with encoding=UCS2. // Don't call with encoding=UCS2.
static v8::Local<v8::Value> Encode(v8::Isolate* isolate, static v8::MaybeLocal<v8::Value> Encode(v8::Isolate* isolate,
const char* buf, const char* buf,
size_t buflen, size_t buflen,
enum encoding encoding); enum encoding encoding,
v8::Local<v8::Value>* error);
// The input buffer should be in host endianness. // The input buffer should be in host endianness.
static v8::Local<v8::Value> Encode(v8::Isolate* isolate, static v8::MaybeLocal<v8::Value> Encode(v8::Isolate* isolate,
const uint16_t* buf, const uint16_t* buf,
size_t buflen); size_t buflen,
v8::Local<v8::Value>* error);
static v8::Local<v8::Value> Encode(v8::Isolate* isolate,
const char* buf, static v8::MaybeLocal<v8::Value> Encode(v8::Isolate* isolate,
enum encoding encoding); const char* buf,
enum encoding encoding,
v8::Local<v8::Value>* error);
private: private:
static size_t WriteUCS2(char* buf, static size_t WriteUCS2(char* buf,

Loading…
Cancel
Save