src: add ERR_* helpers in C++ and migrates toString() errors in string_bytes.cc by joyeecheung · Pull Request #19739 · nodejs/node · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/api/errors.md
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@
'src/node_contextify.cc',
'src/node_debug_options.cc',
'src/node_domain.cc',
'src/node_errors.h',
'src/node_file.cc',
'src/node_http2.cc',
'src/node_http_parser.cc',
Expand Down
73 changes: 73 additions & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#ifndef SRC_NODE_ERRORS_H_
#define SRC_NODE_ERRORS_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "node.h"
#include "util-inl.h"
#include "env-inl.h"
#include "v8.h"

namespace node {

// Helpers to construct errors similar to the ones provided by
// lib/internal/errors.js.
// Example: with `V(ERR_INVALID_ARG_TYPE, TypeError)`, there will be
// `node::ERR_INVALID_ARG_TYPE(isolate, "message")` returning
// a `Local<Value>` containing the TypeError with proper code and message

#define ERRORS_WITH_CODE(V) \
V(ERR_MEMORY_ALLOCATION_FAILED, Error) \
V(ERR_STRING_TOO_LARGE, Error) \
V(ERR_BUFFER_TOO_LARGE, Error)

#define V(code, type) \
inline v8::Local<v8::Value> code(v8::Isolate* isolate, \
const char* message) { \
v8::Local<v8::String> js_code = OneByteString(isolate, #code); \
v8::Local<v8::String> js_msg = OneByteString(isolate, message); \
v8::Local<v8::Object> e = \
v8::Exception::type(js_msg)->ToObject( \
isolate->GetCurrentContext()).ToLocalChecked(); \
e->Set(isolate->GetCurrentContext(), OneByteString(isolate, "code"), \
js_code).FromJust(); \
return e; \
}
ERRORS_WITH_CODE(V)
#undef V

// Errors with predefined static messages

#define PREDEFINED_ERROR_MESSAGES(V) \
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory")

#define V(code, message) \
inline v8::Local<v8::Value> code(v8::Isolate* isolate) { \
return code(isolate, message); \
}
PREDEFINED_ERROR_MESSAGES(V)
#undef V

// Errors with predefined non-static messages

inline v8::Local<v8::Value> ERR_BUFFER_TOO_LARGE(v8::Isolate *isolate) {
char message[128];
snprintf(message, sizeof(message),
"Cannot create a Buffer larger than 0x%lx bytes",
v8::TypedArray::kMaxLength);
return ERR_BUFFER_TOO_LARGE(isolate, message);
}

inline v8::Local<v8::Value> ERR_STRING_TOO_LARGE(v8::Isolate *isolate) {
char message[128];
snprintf(message, sizeof(message),
"Cannot create a string larger than 0x%x bytes",
v8::String::kMaxLength);
return ERR_STRING_TOO_LARGE(isolate, message);
}

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_ERRORS_H_
41 changes: 16 additions & 25 deletions src/string_bytes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include "base64.h"
#include "node_internals.h"
#include "node_errors.h"
#include "node_buffer.h"

#include <limits.h>
Expand All @@ -36,20 +37,6 @@
// use external string resources.
#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 {

using v8::HandleScope;
Expand Down Expand Up @@ -93,7 +80,7 @@ class ExternString: public ResourceType {

TypeName* new_data = node::UncheckedMalloc<TypeName>(length);
if (new_data == nullptr) {
*error = SB_MALLOC_FAILED_ERROR;
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
return MaybeLocal<Value>();
}
memcpy(new_data, data, length * sizeof(*new_data));
Expand Down Expand Up @@ -126,7 +113,7 @@ class ExternString: public ResourceType {

if (str.IsEmpty()) {
delete h_str;
*error = SB_STRING_TOO_LONG_ERROR;
*error = node::ERR_STRING_TOO_LARGE(isolate);
return MaybeLocal<Value>();
}

Expand Down Expand Up @@ -183,7 +170,7 @@ MaybeLocal<Value> ExternOneByteString::NewSimpleFromCopy(Isolate* isolate,
v8::NewStringType::kNormal,
length);
if (str.IsEmpty()) {
*error = SB_STRING_TOO_LONG_ERROR;
*error = node::ERR_STRING_TOO_LARGE(isolate);
return MaybeLocal<Value>();
}
return str.ToLocalChecked();
Expand All @@ -201,7 +188,7 @@ MaybeLocal<Value> ExternTwoByteString::NewSimpleFromCopy(Isolate* isolate,
v8::NewStringType::kNormal,
length);
if (str.IsEmpty()) {
*error = SB_STRING_TOO_LONG_ERROR;
*error = node::ERR_STRING_TOO_LARGE(isolate);
return MaybeLocal<Value>();
}
return str.ToLocalChecked();
Expand Down Expand Up @@ -616,7 +603,7 @@ 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; \
*error = node::ERR_BUFFER_TOO_LARGE(isolate); \
return MaybeLocal<Value>(); \
} \
} while (0)
Expand All @@ -639,9 +626,13 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
switch (encoding) {
case BUFFER:
{
if (buflen > node::Buffer::kMaxLength) {
*error = node::ERR_BUFFER_TOO_LARGE(isolate);
return MaybeLocal<Value>();
}
auto maybe_buf = Buffer::Copy(isolate, buf, buflen);
if (maybe_buf.IsEmpty()) {
*error = SB_BUFFER_CREATION_ERROR;
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
return MaybeLocal<Value>();
}
return maybe_buf.ToLocalChecked();
Expand All @@ -651,7 +642,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
if (contains_non_ascii(buf, buflen)) {
char* out = node::UncheckedMalloc(buflen);
if (out == nullptr) {
*error = SB_MALLOC_FAILED_ERROR;
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
return MaybeLocal<Value>();
}
force_ascii(buf, out, buflen);
Expand All @@ -666,7 +657,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
v8::NewStringType::kNormal,
buflen);
if (val.IsEmpty()) {
*error = SB_STRING_TOO_LONG_ERROR;
*error = node::ERR_STRING_TOO_LARGE(isolate);
return MaybeLocal<Value>();
}
return val.ToLocalChecked();
Expand All @@ -678,7 +669,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
size_t dlen = base64_encoded_size(buflen);
char* dst = node::UncheckedMalloc(dlen);
if (dst == nullptr) {
*error = SB_MALLOC_FAILED_ERROR;
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
return MaybeLocal<Value>();
}

Expand All @@ -692,7 +683,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
size_t dlen = buflen * 2;
char* dst = node::UncheckedMalloc(dlen);
if (dst == nullptr) {
*error = SB_MALLOC_FAILED_ERROR;
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
return MaybeLocal<Value>();
}
size_t written = hex_encode(buf, buflen, dst, dlen);
Expand Down Expand Up @@ -723,7 +714,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
if (IsBigEndian()) {
uint16_t* dst = node::UncheckedMalloc<uint16_t>(buflen);
if (dst == nullptr) {
*error = SB_MALLOC_FAILED_ERROR;
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
return MaybeLocal<Value>();
}
size_t nbytes = buflen * sizeof(uint16_t);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ if (!common.enoughTestMem)
common.skip(skipMessage);

const binding = require(`./build/${common.buildType}/binding`);
const assert = require('assert');

// v8 fails silently if string length > v8::String::kMaxLength
// v8::String::kMaxLength defined in v8.h
Expand All @@ -25,6 +24,11 @@ try {
if (!binding.ensureAllocation(2 * kStringMaxLength))
common.skip(skipMessage);

assert.throws(function() {
const stringLengthHex = kStringMaxLength.toString(16);
common.expectsError(function() {
buf.toString('ascii');
}, /"toString\(\)" failed/);
}, {
message: `Cannot create a string larger than 0x${stringLengthHex} bytes`,
code: 'ERR_STRING_TOO_LARGE',
type: Error
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ if (!common.enoughTestMem)
common.skip(skipMessage);

const binding = require(`./build/${common.buildType}/binding`);
const assert = require('assert');

// v8 fails silently if string length > v8::String::kMaxLength
// v8::String::kMaxLength defined in v8.h
Expand All @@ -25,6 +24,11 @@ try {
if (!binding.ensureAllocation(2 * kStringMaxLength))
common.skip(skipMessage);

assert.throws(function() {
const stringLengthHex = kStringMaxLength.toString(16);
common.expectsError(function() {
buf.toString('base64');
}, /"toString\(\)" failed/);
}, {
message: `Cannot create a string larger than 0x${stringLengthHex} bytes`,
code: 'ERR_STRING_TOO_LARGE',
type: Error
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ try {
if (!binding.ensureAllocation(2 * kStringMaxLength))
common.skip(skipMessage);

assert.throws(function() {
const stringLengthHex = kStringMaxLength.toString(16);
common.expectsError(function() {
buf.toString('latin1');
}, /"toString\(\)" failed/);
}, {
message: `Cannot create a string larger than 0x${stringLengthHex} bytes`,
code: 'ERR_STRING_TOO_LARGE',
type: Error
});

let maxString = buf.toString('latin1', 1);
assert.strictEqual(maxString.length, kStringMaxLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ if (!common.enoughTestMem)
common.skip(skipMessage);

const binding = require(`./build/${common.buildType}/binding`);
const assert = require('assert');

// v8 fails silently if string length > v8::String::kMaxLength
// v8::String::kMaxLength defined in v8.h
Expand All @@ -25,6 +24,11 @@ try {
if (!binding.ensureAllocation(2 * kStringMaxLength))
common.skip(skipMessage);

assert.throws(function() {
const stringLengthHex = kStringMaxLength.toString(16);
common.expectsError(function() {
buf.toString('hex');
}, /"toString\(\)" failed/);
}, {
message: `Cannot create a string larger than 0x${stringLengthHex} bytes`,
code: 'ERR_STRING_TOO_LARGE',
type: Error
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,27 @@ try {
if (!binding.ensureAllocation(2 * kStringMaxLength))
common.skip(skipMessage);

const stringLengthHex = kStringMaxLength.toString(16);

assert.throws(function() {
buf.toString();
}, /"toString\(\)" failed|Array buffer allocation failed/);
}, function(e) {
if (e.message !== 'Array buffer allocation failed') {
common.expectsError({
message: `Cannot create a string larger than 0x${stringLengthHex} bytes`,
code: 'ERR_STRING_TOO_LARGE',
type: Error
})(e);
return true;
} else {
return true;
}
});

assert.throws(function() {
common.expectsError(function() {
buf.toString('utf8');
}, /"toString\(\)" failed/);
}, {
message: `Cannot create a string larger than 0x${stringLengthHex} bytes`,
code: 'ERR_STRING_TOO_LARGE',
type: Error
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ if (!common.enoughTestMem)
common.skip(skipMessage);

const binding = require(`./build/${common.buildType}/binding`);
const assert = require('assert');

// v8 fails silently if string length > v8::String::kMaxLength
// v8::String::kMaxLength defined in v8.h
Expand All @@ -25,6 +24,12 @@ try {
if (!binding.ensureAllocation(2 * kStringMaxLength))
common.skip(skipMessage);

assert.throws(function() {
const stringLengthHex = kStringMaxLength.toString(16);

common.expectsError(function() {
buf.toString('utf16le');
}, /"toString\(\)" failed/);
}, {
message: `Cannot create a string larger than 0x${stringLengthHex} bytes`,
code: 'ERR_STRING_TOO_LARGE',
type: Error
});
11 changes: 9 additions & 2 deletions test/sequential/test-fs-readfile-tostring-fail.js