Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: add ERR_* helpers in C++ and migrates toString() errors in string_bytes.cc #19739

Closed
wants to merge 2 commits into from
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
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,12 @@ An attempt was made to open an IPC communication channel with a synchronously
forked Node.js process. See the documentation for the [`child_process`][] module
for more information.

<a id="ERR_MEMORY_ALLOCATION_FAILED"></a>
### ERR_MEMORY_ALLOCATION_FAILED

An attempt was made to allocate memory (usually in the C++ layer) but it
failed.

<a id="ERR_METHOD_NOT_IMPLEMENTED"></a>
### ERR_METHOD_NOT_IMPLEMENTED

Expand Down Expand Up @@ -1468,6 +1474,12 @@ additional details.
A stream method was called that cannot complete because the stream was
destroyed using `stream.destroy()`.

<a id="ERR_STRING_TOO_LARGE"></a>
### ERR_STRING_TOO_LARGE

An attempt has been made to create a string larger than the maximum allowed
size.

<a id="ERR_TLS_CERT_ALTNAME_INVALID"></a>
### ERR_TLS_CERT_ALTNAME_INVALID

Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ stream.end();
stream.on('finish', common.mustCall(function() {
fs.readFile(file, 'utf8', common.mustCall(function(err, buf) {
assert.ok(err instanceof Error);
assert.ok(/^(Array buffer allocation failed|"toString\(\)" failed)$/
.test(err.message));
if (err.message !== 'Array buffer allocation failed') {
const stringLengthHex = kStringMaxLength.toString(16);
common.expectsError({
message: 'Cannot create a string larger than ' +
`0x${stringLengthHex} bytes`,
code: 'ERR_STRING_TOO_LARGE',
type: Error
})(err);
}
assert.strictEqual(buf, undefined);
}));
}));
Expand Down