From 853b1c9f930d0ec4fb4ca1accadfb9ca4478c291 Mon Sep 17 00:00:00 2001 From: Marly Fleitas Date: Mon, 22 Jan 2018 15:37:40 +0900 Subject: [PATCH] Implement data manipulation methods for dataview This change implements additional methods for manipulating data in the dataview object. This change includes the following things: - Get/Set${type} methods to manipulate data - Tests for the methods PR-URL: https://github.com/nodejs/node-addon-api/pull/218 Reviewed-By: Michael Dawson --- napi-inl.h | 111 +++++++++++++++++++++++--- napi.h | 34 ++++++-- test/binding.cc | 3 + test/binding.gyp | 1 + test/dataview/dataview_read_write.cc | 115 +++++++++++++++++++++++++++ test/dataview/dataview_read_write.js | 86 ++++++++++++++++++++ test/index.js | 1 + 7 files changed, 336 insertions(+), 15 deletions(-) create mode 100644 test/dataview/dataview_read_write.cc create mode 100644 test/dataview/dataview_read_write.js diff --git a/napi-inl.h b/napi-inl.h index f34ed7a..3ee048e 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1211,6 +1211,14 @@ inline DataView::DataView() : Object() { } inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { + napi_status status = napi_get_dataview_info( + _env, + _value /* dataView */, + &_length /* byteLength */, + &_data /* data */, + nullptr /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status); } inline Napi::ArrayBuffer DataView::ArrayBuffer() const { @@ -1240,16 +1248,99 @@ inline size_t DataView::ByteOffset() const { } inline size_t DataView::ByteLength() const { - size_t byteLength; - napi_status status = napi_get_dataview_info( - _env, - _value /* dataView */, - &byteLength /* byteLength */, - nullptr /* data */, - nullptr /* arrayBuffer */, - nullptr /* byteOffset */); - NAPI_THROW_IF_FAILED(_env, status, 0); - return byteLength; + return _length; +} + +inline void* DataView::Data() const { + return _data; +} + +inline float DataView::GetFloat32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline double DataView::GetFloat64(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int8_t DataView::GetInt8(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int16_t DataView::GetInt16(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int32_t DataView::GetInt32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint8_t DataView::GetUint8(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint16_t DataView::GetUint16(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint32_t DataView::GetUint32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline void DataView::SetFloat32(size_t byteOffset, float value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetFloat64(size_t byteOffset, double value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt8(size_t byteOffset, int8_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt16(size_t byteOffset, int16_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt32(size_t byteOffset, int32_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint8(size_t byteOffset, uint8_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint16(size_t byteOffset, uint16_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint32(size_t byteOffset, uint32_t value) const { + WriteData(byteOffset, value); +} + +template +inline T DataView::ReadData(size_t byteOffset) const { + if (byteOffset + sizeof(T) > _length || + byteOffset + sizeof(T) < byteOffset) { // overflow + NAPI_THROW(RangeError::New(_env, + "Offset is outside the bounds of the DataView")); + return 0; + } + + return *reinterpret_cast(static_cast(_data) + byteOffset); +} + +template +inline void DataView::WriteData(size_t byteOffset, T value) const { + if (byteOffset + sizeof(T) > _length || + byteOffset + sizeof(T) < byteOffset) { // overflow + NAPI_THROW(RangeError::New(_env, + "Offset is outside the bounds of the DataView")); + return; + } + + *reinterpret_cast(static_cast(_data) + byteOffset) = value; } #endif diff --git a/napi.h b/napi.h index 3ec1fbf..6a3e879 100644 --- a/napi.h +++ b/napi.h @@ -800,11 +800,35 @@ namespace Napi { size_t ByteOffset() const; ///< Gets the offset into the buffer where the array starts. size_t ByteLength() const; ///< Gets the length of the array in bytes. - // TODO: This class isn't a complete implementation yet, and will - // incrementally add additional methods to read/write data into buffer. - // Currently, this class is wrapped by the NAPI_DATA_VIEW_FEATURE macro flag - // and this should be enabled only in the tests until the implementation is - // completed. + void* Data() const; + + float GetFloat32(size_t byteOffset) const; + double GetFloat64(size_t byteOffset) const; + int8_t GetInt8(size_t byteOffset) const; + int16_t GetInt16(size_t byteOffset) const; + int32_t GetInt32(size_t byteOffset) const; + uint8_t GetUint8(size_t byteOffset) const; + uint16_t GetUint16(size_t byteOffset) const; + uint32_t GetUint32(size_t byteOffset) const; + + void SetFloat32(size_t byteOffset, float value) const; + void SetFloat64(size_t byteOffset, double value) const; + void SetInt8(size_t byteOffset, int8_t value) const; + void SetInt16(size_t byteOffset, int16_t value) const; + void SetInt32(size_t byteOffset, int32_t value) const; + void SetUint8(size_t byteOffset, uint8_t value) const; + void SetUint16(size_t byteOffset, uint16_t value) const; + void SetUint32(size_t byteOffset, uint32_t value) const; + + private: + template + T ReadData(size_t byteOffset) const; + + template + void WriteData(size_t byteOffset, T value) const; + + void* _data; + size_t _length; }; #endif diff --git a/test/binding.cc b/test/binding.cc index d7cf264..6543f6b 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -8,6 +8,7 @@ Object InitBasicTypesNumber(Env env); Object InitBasicTypesValue(Env env); Object InitBuffer(Env env); Object InitDataView(Env env); +Object InitDataViewReadWrite(Env env); Object InitError(Env env); Object InitExternal(Env env); Object InitFunction(Env env); @@ -24,6 +25,8 @@ Object Init(Env env, Object exports) { exports.Set("basic_types_value", InitBasicTypesValue(env)); exports.Set("buffer", InitBuffer(env)); exports.Set("dataview", InitDataView(env)); + exports.Set("dataview_read_write", InitDataView(env)); + exports.Set("dataview_read_write", InitDataViewReadWrite(env)); exports.Set("error", InitError(env)); exports.Set("external", InitExternal(env)); exports.Set("function", InitFunction(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 36de592..c7272ed 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -8,6 +8,7 @@ 'binding.cc', 'buffer.cc', 'dataview/dataview.cc', + 'dataview/dataview_read_write.cc', 'error.cc', 'external.cc', 'function.cc', diff --git a/test/dataview/dataview_read_write.cc b/test/dataview/dataview_read_write.cc new file mode 100644 index 0000000..da2beed --- /dev/null +++ b/test/dataview/dataview_read_write.cc @@ -0,0 +1,115 @@ +#include "napi.h" + +using namespace Napi; + +static Value GetFloat32(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + return Number::New(info.Env(), info[0].As().GetFloat32(byteOffset)); +} + +static Value GetFloat64(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + return Number::New(info.Env(), info[0].As().GetFloat64(byteOffset)); +} + +static Value GetInt8(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + return Number::New(info.Env(), info[0].As().GetInt8(byteOffset)); +} + +static Value GetInt16(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + return Number::New(info.Env(), info[0].As().GetInt16(byteOffset)); +} + +static Value GetInt32(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + return Number::New(info.Env(), info[0].As().GetInt32(byteOffset)); +} + +static Value GetUint8(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + return Number::New(info.Env(), info[0].As().GetUint8(byteOffset)); +} + +static Value GetUint16(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + return Number::New(info.Env(), info[0].As().GetUint16(byteOffset)); +} + +static Value GetUint32(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + return Number::New(info.Env(), info[0].As().GetUint32(byteOffset)); +} + +static void SetFloat32(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + auto value = info[2].As().FloatValue(); + info[0].As().SetFloat32(byteOffset, value); +} + +static void SetFloat64(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + auto value = info[2].As().DoubleValue(); + info[0].As().SetFloat64(byteOffset, value); +} + +static void SetInt8(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + auto value = info[2].As().Int32Value(); + info[0].As().SetInt8(byteOffset, value); +} + +static void SetInt16(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + auto value = info[2].As().Int32Value(); + info[0].As().SetInt16(byteOffset, value); +} + +static void SetInt32(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + auto value = info[2].As().Int32Value(); + info[0].As().SetInt32(byteOffset, value); +} + +static void SetUint8(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + auto value = info[2].As().Uint32Value(); + info[0].As().SetUint8(byteOffset, value); +} + +static void SetUint16(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + auto value = info[2].As().Uint32Value(); + info[0].As().SetUint16(byteOffset, value); +} + +static void SetUint32(const CallbackInfo& info) { + size_t byteOffset = info[1].As().Uint32Value(); + auto value = info[2].As().Uint32Value(); + info[0].As().SetUint32(byteOffset, value); +} + +Object InitDataViewReadWrite(Env env) { + Object exports = Object::New(env); + + exports["getFloat32"] = Function::New(env, GetFloat32); + exports["getFloat64"] = Function::New(env, GetFloat64); + exports["getInt8"] = Function::New(env, GetInt8); + exports["getInt16"] = Function::New(env, GetInt16); + exports["getInt32"] = Function::New(env, GetInt32); + exports["getUint8"] = Function::New(env, GetUint8); + exports["getUint16"] = Function::New(env, GetUint16); + exports["getUint32"] = Function::New(env, GetUint32); + + exports["setFloat32"] = Function::New(env, SetFloat32); + exports["setFloat64"] = Function::New(env, SetFloat64); + exports["setInt8"] = Function::New(env, SetInt8); + exports["setInt16"] = Function::New(env, SetInt16); + exports["setInt32"] = Function::New(env, SetInt32); + exports["setUint8"] = Function::New(env, SetUint8); + exports["setUint16"] = Function::New(env, SetUint16); + exports["setUint32"] = Function::New(env, SetUint32); + + return exports; +} diff --git a/test/dataview/dataview_read_write.js b/test/dataview/dataview_read_write.js new file mode 100644 index 0000000..3763673 --- /dev/null +++ b/test/dataview/dataview_read_write.js @@ -0,0 +1,86 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + function expected(type, value) { + return eval(`(new ${type}Array([${value}]))[0]`); + } + + function nativeReadDataView(dataview, type, offset, value) { + return eval(`binding.dataview_read_write.get${type}(dataview, offset)`); + } + + function nativeWriteDataView(dataview, type, offset, value) { + eval(`binding.dataview_read_write.set${type}(dataview, offset, value)`); + } + + function jsReadDataView(dataview, type, offset, value) { + return eval(`dataview.get${type}(offset, true)`); + } + + function jsWriteDataView(dataview, type, offset, value) { + eval(`dataview.set${type}(offset, value, true)`); + } + + function testReadData(dataview, type, offset, value) { + jsWriteDataView(dataview, type, offset, 0); + assert.strictEqual(jsReadDataView(dataview, type, offset), 0); + + jsWriteDataView(dataview, type, offset, value); + assert.strictEqual( + nativeReadDataView(dataview, type, offset), expected(type, value)); + } + + function testWriteData(dataview, type, offset, value) { + jsWriteDataView(dataview, type, offset, 0); + assert.strictEqual(jsReadDataView(dataview, type, offset), 0); + + nativeWriteDataView(dataview, type, offset, value); + assert.strictEqual( + jsReadDataView(dataview, type, offset), expected(type, value)); + } + + function testInvalidOffset(dataview, type, offset, value) { + assert.throws(() => { + nativeReadDataView(dataview, type, offset); + }, RangeError); + + assert.throws(() => { + nativeWriteDataView(dataview, type, offset, value); + }, RangeError); + } + + const dataview = new DataView(new ArrayBuffer(22)); + + testReadData(dataview, 'Float32', 0, 10.2); + testReadData(dataview, 'Float64', 4, 20.3); + testReadData(dataview, 'Int8', 5, 120); + testReadData(dataview, 'Int16', 7, 15000); + testReadData(dataview, 'Int32', 11, 200000); + testReadData(dataview, 'Uint8', 12, 128); + testReadData(dataview, 'Uint16', 14, 32768); + testReadData(dataview, 'Uint32', 18, 1000000); + + testWriteData(dataview, 'Float32', 0, 10.2); + testWriteData(dataview, 'Float64', 4, 20.3); + testWriteData(dataview, 'Int8', 5, 120); + testWriteData(dataview, 'Int16', 7, 15000); + testWriteData(dataview, 'Int32', 11, 200000); + testWriteData(dataview, 'Uint8', 12, 128); + testWriteData(dataview, 'Uint16', 14, 32768); + testWriteData(dataview, 'Uint32', 18, 1000000); + + testInvalidOffset(dataview, 'Float32', 22, 10.2); + testInvalidOffset(dataview, 'Float64', 22, 20.3); + testInvalidOffset(dataview, 'Int8', 22, 120); + testInvalidOffset(dataview, 'Int16', 22, 15000); + testInvalidOffset(dataview, 'Int32', 22, 200000); + testInvalidOffset(dataview, 'Uint8', 22, 128); + testInvalidOffset(dataview, 'Uint16', 22, 32768); + testInvalidOffset(dataview, 'Uint32', 22, 1000000); +} diff --git a/test/index.js b/test/index.js index 9482a11..b12b3f2 100644 --- a/test/index.js +++ b/test/index.js @@ -14,6 +14,7 @@ let testModules = [ 'basic_types/value', 'buffer', 'dataview/dataview', + 'dataview/dataview_read_write', 'error', 'external', 'function',