Skip to content

Commit

Permalink
n-api: throw RangeError napi_create_typedarray()
Browse files Browse the repository at this point in the history
According to the ECMA spec, we should throw a RangeError in the
following cases:
  - `(length * elementSize) + offset` > the size of the array passed in
  - `offset % elementSize` != `0`

In the current implementation, this check was omitted. So, the following
code will cause a crash.
  ```
  napi_create_typedarray(env, napi_uint16_array, 2 /* length */,
                         buffer, 1 /* byte_offset */, &output_array);
  ```

This change fixes the problem and write some related tests.

Refs:
https://tc39.github.io/ecma262/#sec-typedarray-buffer-byteoffset-length

PR-URL: #18037
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
romandev authored and evanlucas committed Jan 30, 2018
1 parent b05f09a commit 9edf023
Showing 5 changed files with 100 additions and 12 deletions.
12 changes: 12 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
@@ -1271,6 +1271,18 @@ While using `N-API`, `Constructor.prototype` was not an object.
While calling `napi_create_dataview()`, a given `offset` was outside the bounds
of the dataview or `offset + length` was larger than a length of given `buffer`.

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

While calling `napi_create_typedarray()`, the provided `offset` was not a
multiple of the element size.

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

While calling `napi_create_typedarray()`, `(length * size_of_element) +
byte_offset` was larger than the length of given `buffer`.

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

3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
@@ -319,6 +319,9 @@ E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object');
E('ERR_NAPI_INVALID_DATAVIEW_ARGS',
'byte_offset + byte_length should be less than or eqaul to the size in ' +
'bytes of the array passed in');
E('ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT', 'start offset of %s should be a ' +
'multiple of %s');
E('ERR_NAPI_INVALID_TYPEDARRAY_LENGTH', 'Invalid typed array length');
E('ERR_NO_CRYPTO', 'Node.js is not compiled with OpenSSL crypto support');
E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU');
E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported');
51 changes: 42 additions & 9 deletions src/node_api.cc
Original file line number Diff line number Diff line change
@@ -142,6 +142,30 @@ struct napi_env__ {
(!try_catch.HasCaught() ? napi_ok \
: napi_set_last_error((env), napi_pending_exception))

#define THROW_RANGE_ERROR_IF_FALSE(env, condition, error, message) \
do { \
if (!(condition)) { \
napi_throw_range_error((env), (error), (message)); \
return napi_set_last_error((env), napi_generic_failure); \
} \
} while (0)

#define CREATE_TYPED_ARRAY( \
env, type, size_of_element, buffer, byte_offset, length, out) \
do { \
if ((size_of_element) > 1) { \
THROW_RANGE_ERROR_IF_FALSE( \
(env), (byte_offset) % (size_of_element) == 0, \
"ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT", \
"start offset of "#type" should be a multiple of "#size_of_element); \
} \
THROW_RANGE_ERROR_IF_FALSE((env), (length) * (size_of_element) + \
(byte_offset) <= buffer->ByteLength(), \
"ERR_NAPI_INVALID_TYPEDARRAY_LENGTH", \
"Invalid typed array length"); \
(out) = v8::type::New((buffer), (byte_offset), (length)); \
} while (0)

namespace {
namespace v8impl {

@@ -3063,31 +3087,40 @@ napi_status napi_create_typedarray(napi_env env,

switch (type) {
case napi_int8_array:
typedArray = v8::Int8Array::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Int8Array, 1, buffer, byte_offset, length, typedArray);
break;
case napi_uint8_array:
typedArray = v8::Uint8Array::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Uint8Array, 1, buffer, byte_offset, length, typedArray);
break;
case napi_uint8_clamped_array:
typedArray = v8::Uint8ClampedArray::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Uint8ClampedArray, 1, buffer, byte_offset, length, typedArray);
break;
case napi_int16_array:
typedArray = v8::Int16Array::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Int16Array, 2, buffer, byte_offset, length, typedArray);
break;
case napi_uint16_array:
typedArray = v8::Uint16Array::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Uint16Array, 2, buffer, byte_offset, length, typedArray);
break;
case napi_int32_array:
typedArray = v8::Int32Array::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Int32Array, 4, buffer, byte_offset, length, typedArray);
break;
case napi_uint32_array:
typedArray = v8::Uint32Array::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Uint32Array, 4, buffer, byte_offset, length, typedArray);
break;
case napi_float32_array:
typedArray = v8::Float32Array::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Float32Array, 4, buffer, byte_offset, length, typedArray);
break;
case napi_float64_array:
typedArray = v8::Float64Array::New(buffer, byte_offset, length);
CREATE_TYPED_ARRAY(
env, Float64Array, 8, buffer, byte_offset, length, typedArray);
break;
default:
return napi_set_last_error(env, napi_invalid_arg);
18 changes: 18 additions & 0 deletions test/addons-napi/test_typedarray/test.js
Original file line number Diff line number Diff line change
@@ -55,3 +55,21 @@ arrayTypes.forEach((currentType) => {
assert.notStrictEqual(theArray, template);
assert.strictEqual(theArray.buffer, buffer);
});

arrayTypes.forEach((currentType) => {
const template = Reflect.construct(currentType, buffer);
assert.throws(() => {
test_typedarray.CreateTypedArray(template, buffer, 0, 136);
}, /Invalid typed array length/);
});

const nonByteArrayTypes = [ Int16Array, Uint16Array, Int32Array, Uint32Array,
Float32Array, Float64Array ];
nonByteArrayTypes.forEach((currentType) => {
const template = Reflect.construct(currentType, buffer);
assert.throws(() => {
test_typedarray.CreateTypedArray(template, buffer,
currentType.BYTES_PER_ELEMENT + 1, 1);
console.log(`start of offset ${currentType}`);
}, /start offset of/);
});
28 changes: 25 additions & 3 deletions test/addons-napi/test_typedarray/test_typedarray.c
Original file line number Diff line number Diff line change
@@ -97,11 +97,11 @@ napi_value External(napi_env env, napi_callback_info info) {
}

napi_value CreateTypedArray(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
size_t argc = 4;
napi_value args[4];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));

NAPI_ASSERT(env, argc == 2, "Wrong number of arguments");
NAPI_ASSERT(env, argc == 2 || argc == 4, "Wrong number of arguments");

napi_value input_array = args[0];
napi_valuetype valuetype0;
@@ -136,6 +136,28 @@ napi_value CreateTypedArray(napi_env env, napi_callback_info info) {
NAPI_CALL(env, napi_get_typedarray_info(
env, input_array, &type, &length, NULL, &in_array_buffer, &byte_offset));

if (argc == 4) {
napi_valuetype valuetype2;
NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));

NAPI_ASSERT(env, valuetype2 == napi_number,
"Wrong type of arguments. Expects a number as third argument.");

uint32_t uint32_length;
NAPI_CALL(env, napi_get_value_uint32(env, args[2], &uint32_length));
length = uint32_length;

napi_valuetype valuetype3;
NAPI_CALL(env, napi_typeof(env, args[3], &valuetype3));

NAPI_ASSERT(env, valuetype3 == napi_number,
"Wrong type of arguments. Expects a number as third argument.");

uint32_t uint32_byte_offset;
NAPI_CALL(env, napi_get_value_uint32(env, args[3], &uint32_byte_offset));
byte_offset = uint32_byte_offset;
}

napi_value output_array;
NAPI_CALL(env, napi_create_typedarray(
env, type, length, input_buffer, byte_offset, &output_array));

0 comments on commit 9edf023

Please sign in to comment.