From 3b8f2185ac7a53cae0d896b435547eb043bbc837 Mon Sep 17 00:00:00 2001 From: Peter Gal Date: Mon, 8 Jan 2018 15:35:18 +0100 Subject: [PATCH] Add ArrayBuffer with user specified buffer New API functions: - jerry_create_arraybuffer_external - jerry_get_arraybuffer_pointer JerryScript-DCO-1.0-Signed-off-by: Peter Gal pgal.u-szeged@partner.samsung.com --- docs/02.API-REFERENCE.md | 109 ++++++++++++++ jerry-core/api/jerry.c | 75 ++++++++++ jerry-core/ecma/base/ecma-gc.c | 22 ++- jerry-core/ecma/base/ecma-globals.h | 29 ++++ .../ecma/operations/ecma-arraybuffer-object.c | 44 +++++- .../ecma/operations/ecma-arraybuffer-object.h | 4 + jerry-core/include/jerryscript-core.h | 4 + tests/unit-core/test-arraybuffer.c | 134 +++++++++++++++++- 8 files changed, 416 insertions(+), 5 deletions(-) diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index 3550ffb495..87f59bb0f4 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -1030,6 +1030,7 @@ jerry_value_is_arraybuffer (const jerry_value_t value) **See also** - [jerry_create_arraybuffer](#jerry_create_arraybuffer) +- [jerry_create_arraybuffer_external](#jerry_create_arraybuffer_external) ## jerry_value_is_boolean @@ -2465,6 +2466,56 @@ jerry_create_arraybuffer (jerry_length_t size); - [jerry_release_value](#jerry_release_value) +## jerry_create_arraybuffer_external + +**Summary** + +Creates a jerry_value_t representing an ArrayBuffer object with +user specified back-buffer. + +User must pass a buffer pointer which is at least `size` big. +After the object is not needed the GC will call the `free_cb` +so the user can release the buffer which was provided. + +**Prototype** + +```c +jerry_value_t +jerry_create_arraybuffer_external (const jerry_length_t size + uint8_t *buffer_p, + jerry_object_native_free_callback_t free_cb); +``` + +- `size` - size of the buffer to use **in bytes** (should not be 0) +- `buffer_p` - the buffer used for the Array Buffer object (should not be a null pointer) +- `free_cb` - the callback function called when the object is released +- return value + - the new ArrayBuffer as a `jerry_value_t` + - if the `size` is zero or `buffer_p` is a null pointer will return RangeError + +**Example** + +```c +{ + uint8_t buffer_p[15]; + jerry_value_t buffer_value = jerry_create_arraybuffer_external (15, buffer_p, NULL); + + ... // use the array buffer + + jerry_release_value (buffer_value); +} +``` + +**See also** + +- [jerry_get_arraybuffer_pointer](#jerry_get_arraybuffer_pointer) +- [jerry_arraybuffer_read](#jerry_arraybuffer_read) +- [jerry_arraybuffer_write](#jerry_arraybuffer_write) +- [jerry_value_is_arraybuffer](#jerry_value_is_arraybuffer) +- [jerry_release_value](#jerry_release_value) +- [jerry_object_native_free_callback_t](#jerry_object_native_free_callback_t) + + ## jerry_create_boolean **Summary** @@ -4921,3 +4972,61 @@ jerry_arraybuffer_write (const jerry_value_t value, - [jerry_create_arraybuffer](#jerry_create_arraybuffer) - [jerry_arraybuffer_write](#jerry_arraybuffer_write) - [jerry_get_arraybuffer_byte_length](#jerry_get_arraybuffer_byte_length) + + +## jerry_get_arraybuffer_pointer + +**Summary** + +The function allows access to the contents of the Array Buffer directly. +Only allowed for Array Buffers which were created with +[jerry_create_arraybuffer_external](#jerry_create_arraybuffer_external) +function calls. In any other case this function will return `NULL`. + +After using the pointer the [jerry_release_value](#jerry_release_value) +function must be called. + +**WARNING!** This operation is for expert use only! The programmer must +ensure that the returned memory area is used correctly. That is +there is no out of bounds reads or writes. + +**Prototype** + +```c +uint8_t * +jerry_get_arraybuffer_pointer (const jerry_value_t value); +``` + +- `value` - Array Buffer object. +- return value + - pointer to the Array Buffer's data area. + - NULL if the `value` is not an Array Buffer object with external memory. + +**Example** + +```c +{ + jerry_value_t buffer; + + // acquire buffer somewhere which was created by a jerry_create_array_buffer_external call. + + uint8_t *const data = jerry_get_arraybuffer_pointer (buffer); + + for (int i = 0; i < 22; i++) + { + data[i] = (uint8_t) (i + 4); + } + + // required after jerry_get_arraybuffer_pointer call. + jerry_release_value (buffer); + + // use the Array Buffer + + // release buffer as it is not needed after this point + jerry_release_value (buffer); +} +``` + +**See also** + +- [jerry_create_arraybuffer_external](#jerry_create_arraybuffer_external) diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 9ffbe622a5..16a2f47ece 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -2670,6 +2670,42 @@ jerry_create_arraybuffer (const jerry_length_t size) /**< size of the ArrayBuffe #endif /* !CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */ } /* jerry_create_arraybuffer */ +/** + * Creates an ArrayBuffer object with user specified buffer. + * + * Notes: + * * the size is specified in bytes. + * * the buffer passed should be at least the specified bytes big. + * * if the typed arrays are disabled this will return a TypeError. + * * if the size is zero or the buffer_p is a null pointer this will return a RangeError. + * + * @return value of the construced ArrayBuffer object + */ +jerry_value_t +jerry_create_arraybuffer_external (const jerry_length_t size, /**< size of the buffer to used */ + uint8_t *buffer_p, /**< buffer to use as the ArrayBuffer's backing */ + jerry_object_native_free_callback_t free_cb) /**< buffer free callback */ +{ + jerry_assert_api_available (); + +#ifndef CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN + if (size == 0 || buffer_p == NULL) + { + return jerry_throw (ecma_raise_range_error (ECMA_ERR_MSG ("invalid buffer size or storage reference"))); + } + + ecma_object_t *arraybuffer = ecma_arraybuffer_new_object_external (size, + buffer_p, + (ecma_object_native_free_callback_t) free_cb); + return jerry_return (ecma_make_object_value (arraybuffer)); +#else /* CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */ + JERRY_UNUSED (size); + JERRY_UNUSED (buffer_p); + JERRY_UNUSED (free_cb); + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("ArrayBuffer not supported."))); +#endif /* !CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */ +} /* jerry_create_arraybuffer_external */ + /** * Copy bytes into the ArrayBuffer from a buffer. * @@ -2798,6 +2834,45 @@ jerry_get_arraybuffer_byte_length (const jerry_value_t value) /**< ArrayBuffer * return 0; } /* jerry_get_arraybuffer_byte_length */ +/** + * Get a pointer for the start of the ArrayBuffer. + * + * Note: + * * Only valid for ArrayBuffers created with jerry_create_arraybuffer_external. + * * This is a high-risk operation as the bounds are not checked + * when accessing the pointer elements. + * * jerry_release_value must be called on the ArrayBuffer when the pointer is no longer needed. + * + * @return pointer to the back-buffer of the ArrayBuffer. + * pointer is NULL if the parameter is not an ArrayBuffer with external memory + or it is not an ArrayBuffer at all. + */ +uint8_t * +jerry_get_arraybuffer_pointer (const jerry_value_t value) /**< Array Buffer to use */ +{ + jerry_assert_api_available (); +#ifndef CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN + jerry_value_t buffer = jerry_get_arg_value (value); + + if (!ecma_is_arraybuffer (buffer)) + { + return NULL; + } + + ecma_object_t *buffer_p = ecma_get_object_from_value (buffer); + if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (buffer_p)) + { + jerry_acquire_value (value); + lit_utf8_byte_t *mem_buffer_p = ecma_arraybuffer_get_buffer (buffer_p); + return (uint8_t *const) mem_buffer_p; + } +#else /* CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */ + JERRY_UNUSED (value); +#endif /* !CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */ + + return NULL; +} /* jerry_get_arraybuffer_pointer */ + /** * @} */ diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index 9797694052..3353bea065 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -570,11 +570,29 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ case LIT_MAGIC_STRING_ARRAY_BUFFER_UL: { ecma_length_t arraybuffer_length = ext_object_p->u.class_prop.u.length; - size_t size = sizeof (ecma_extended_object_t) + arraybuffer_length; + size_t size; + + if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (ext_object_p)) + { + size = sizeof (ecma_arraybuffer_external_info); + + /* Call external free callback if any. */ + ecma_arraybuffer_external_info *array_p = (ecma_arraybuffer_external_info *) ext_object_p; + JERRY_ASSERT (array_p != NULL); + + if (array_p->free_cb != NULL) + { + (array_p->free_cb) (array_p->buffer_p); + } + } + else + { + size = sizeof (ecma_extended_object_t) + arraybuffer_length; + } + ecma_dealloc_extended_object (object_p, size); return; } - #endif /* !CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */ #ifndef CONFIG_DISABLE_ES2015_PROMISE_BUILTIN case LIT_MAGIC_STRING_PROMISE_UL: diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 22659271cb..686c429c2e 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -712,6 +712,8 @@ typedef struct struct { uint16_t class_id; /**< class id of the object */ + uint16_t extra_info; /**< extra information for the object + e.g. array buffer type info (external/internal) */ /* * Description of extra fields. These extra fields depends on the class_id. @@ -1243,6 +1245,33 @@ typedef struct #ifndef CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN +/** + * Extra information for ArrayBuffers. + */ +typedef enum +{ + ECMA_ARRAYBUFFER_INTERNAL_MEMORY = 0u, /* ArrayBuffer memory is handled internally. */ + ECMA_ARRAYBUFFER_EXTERNAL_MEMORY = (1u << 0), /* ArrayBuffer created via jerry_create_arraybuffer_external. */ +} ecma_arraybuffer_extra_flag_t; + +#define ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY(object_p) \ + ((((ecma_extended_object_t *) object_p)->u.class_prop.extra_info & ECMA_ARRAYBUFFER_EXTERNAL_MEMORY) != 0) + +/** + * Struct to store information for ArrayBuffers with external memory. + * + * The following elements are stored in Jerry memory. + * + * buffer_p - pointer to the external memory. + * free_cb - pointer to a callback function which is called when the ArrayBuffer is freed. + */ +typedef struct +{ + ecma_extended_object_t extended_object; /**< extended object part */ + void *buffer_p; /**< external buffer pointer */ + ecma_object_native_free_callback_t free_cb; /**< the free callback for the above buffer pointer */ +} ecma_arraybuffer_external_info; + /** * Some internal properties of TypedArray object. * It is only used when the offset is not 0, and diff --git a/jerry-core/ecma/operations/ecma-arraybuffer-object.c b/jerry-core/ecma/operations/ecma-arraybuffer-object.c index b5e68c750c..1840791f09 100644 --- a/jerry-core/ecma/operations/ecma-arraybuffer-object.c +++ b/jerry-core/ecma/operations/ecma-arraybuffer-object.c @@ -51,6 +51,7 @@ ecma_arraybuffer_new_object (ecma_length_t length) /**< length of the arraybuffe ECMA_OBJECT_TYPE_CLASS); ecma_deref_object (prototype_obj_p); ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p; + ext_object_p->u.class_prop.extra_info = ECMA_ARRAYBUFFER_INTERNAL_MEMORY; ext_object_p->u.class_prop.class_id = LIT_MAGIC_STRING_ARRAY_BUFFER_UL; ext_object_p->u.class_prop.u.length = length; @@ -60,6 +61,38 @@ ecma_arraybuffer_new_object (ecma_length_t length) /**< length of the arraybuffe return object_p; } /* ecma_arraybuffer_new_object */ +/** + * Helper function: create arraybuffer object with external buffer backing. + * + * The struct of external arraybuffer object: + * ecma_object_t + * extend_part + * arraybuffer external info part + * + * @return ecma_object_t *, pointer to the created ArrayBuffer object + */ +ecma_object_t * +ecma_arraybuffer_new_object_external (ecma_length_t length, /**< length of the buffer_p to use */ + void *buffer_p, /**< pointer for ArrayBuffer's buffer backing */ + ecma_object_native_free_callback_t free_cb) /**< buffer free callback */ +{ + ecma_object_t *prototype_obj_p = ecma_builtin_get (ECMA_BUILTIN_ID_ARRAYBUFFER_PROTOTYPE); + ecma_object_t *object_p = ecma_create_object (prototype_obj_p, + sizeof (ecma_arraybuffer_external_info), + ECMA_OBJECT_TYPE_CLASS); + ecma_deref_object (prototype_obj_p); + ecma_arraybuffer_external_info *array_object_p = (ecma_arraybuffer_external_info *) object_p; + array_object_p->extended_object.u.class_prop.extra_info = ECMA_ARRAYBUFFER_EXTERNAL_MEMORY; + array_object_p->extended_object.u.class_prop.class_id = LIT_MAGIC_STRING_ARRAY_BUFFER_UL; + array_object_p->extended_object.u.class_prop.u.length = length; + + array_object_p->buffer_p = buffer_p; + array_object_p->free_cb = free_cb; + + return object_p; +} /* ecma_arraybuffer_new_object_external */ + + /** * ArrayBuffer object creation operation. * @@ -158,7 +191,16 @@ ecma_arraybuffer_get_buffer (ecma_object_t *object_p) /**< pointer to the ArrayB JERRY_ASSERT (ecma_object_class_is (object_p, LIT_MAGIC_STRING_ARRAY_BUFFER_UL)); ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p; - return (lit_utf8_byte_t *) (ext_object_p + 1); + + if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (ext_object_p)) + { + ecma_arraybuffer_external_info *array_p = (ecma_arraybuffer_external_info *) ext_object_p; + return (lit_utf8_byte_t *) array_p->buffer_p; + } + else + { + return (lit_utf8_byte_t *) (ext_object_p + 1); + } } /* ecma_arraybuffer_get_buffer */ /** diff --git a/jerry-core/ecma/operations/ecma-arraybuffer-object.h b/jerry-core/ecma/operations/ecma-arraybuffer-object.h index f363f4c38a..0fe3f5c9df 100644 --- a/jerry-core/ecma/operations/ecma-arraybuffer-object.h +++ b/jerry-core/ecma/operations/ecma-arraybuffer-object.h @@ -34,6 +34,10 @@ ecma_op_create_arraybuffer_object (const ecma_value_t *, ecma_length_t); */ ecma_object_t * ecma_arraybuffer_new_object (ecma_length_t lengh); +ecma_object_t * +ecma_arraybuffer_new_object_external (ecma_length_t length, + void *buffer_p, + ecma_object_native_free_callback_t free_cb); lit_utf8_byte_t * ecma_arraybuffer_get_buffer (ecma_object_t *obj_p) __attr_pure___; ecma_length_t diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 73723340d6..055db6a3f5 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -446,6 +446,9 @@ void jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, voi */ bool jerry_value_is_arraybuffer (const jerry_value_t value); jerry_value_t jerry_create_arraybuffer (const jerry_length_t size); +jerry_value_t jerry_create_arraybuffer_external (const jerry_length_t size, + uint8_t *buffer_p, + jerry_object_native_free_callback_t free_cb); jerry_length_t jerry_arraybuffer_write (const jerry_value_t value, jerry_length_t offset, const uint8_t *buf_p, @@ -455,6 +458,7 @@ jerry_length_t jerry_arraybuffer_read (const jerry_value_t value, uint8_t *buf_p, jerry_length_t buf_size); jerry_length_t jerry_get_arraybuffer_byte_length (const jerry_value_t value); +uint8_t *jerry_get_arraybuffer_pointer (const jerry_value_t value); /** * @} diff --git a/tests/unit-core/test-arraybuffer.c b/tests/unit-core/test-arraybuffer.c index 95eebba829..12a363acb5 100644 --- a/tests/unit-core/test-arraybuffer.c +++ b/tests/unit-core/test-arraybuffer.c @@ -129,7 +129,6 @@ static void test_write_with_offset (uint8_t offset) /**< offset for buffer write jerry_value_t offset_val = jerry_create_number (offset); register_js_value ("offset", offset_val); jerry_release_value (offset_val); - } const char *eval_arraybuffer_src_p = "var array = new Uint8Array (15); array.buffer"; @@ -145,7 +144,7 @@ static void test_write_with_offset (uint8_t offset) /**< offset for buffer write for (uint8_t i = 0; i < 20; i++) { - buffer[i] = (uint8_t)(i * 3); + buffer[i] = (uint8_t) (i * 3); } /* Intentionally copy more than the allowed space. */ @@ -170,6 +169,14 @@ static void test_write_with_offset (uint8_t offset) /**< offset for buffer write jerry_release_value (arraybuffer); } /* test_write_with_offset */ +static bool callback_called = false; + +static void test_free_cb (void *buffer) /**< buffer to free (if needed) */ +{ + (void) buffer; + callback_called = true; +} /* test_free_cb */ + int main (void) { @@ -259,5 +266,128 @@ main (void) jerry_release_value (arraybuffer); } + /* Test ArrayBuffer with buffer allocated externally */ + { + const uint32_t buffer_size = 15; + const uint8_t base_value = 51; + + uint8_t buffer_p[buffer_size]; + memset (buffer_p, base_value, buffer_size); + + jerry_value_t arrayb = jerry_create_arraybuffer_external (buffer_size, buffer_p, test_free_cb); + uint8_t new_value = 123; + jerry_length_t copied = jerry_arraybuffer_write (arrayb, 0, &new_value, 1); + TEST_ASSERT (copied == 1); + TEST_ASSERT (buffer_p[0] == new_value); + TEST_ASSERT (jerry_get_arraybuffer_byte_length (arrayb) == buffer_size); + + for (uint32_t i = 1; i < buffer_size; i++) + { + TEST_ASSERT (buffer_p[i] == base_value); + } + + uint8_t test_buffer[buffer_size]; + jerry_length_t read = jerry_arraybuffer_read (arrayb, 0, test_buffer, buffer_size); + TEST_ASSERT (read == buffer_size); + TEST_ASSERT (test_buffer[0] == new_value); + + for (uint32_t i = 1; i < buffer_size; i++) + { + TEST_ASSERT (test_buffer[i] == base_value); + } + + TEST_ASSERT (jerry_value_is_arraybuffer (arrayb)); + jerry_release_value (arrayb); + } + + /* Test ArrayBuffer external memory map/unmap */ + { + const uint32_t buffer_size = 20; + uint8_t buffer_p[buffer_size]; + { + jerry_value_t input_buffer = jerry_create_arraybuffer_external (buffer_size, buffer_p, NULL); + register_js_value ("input_buffer", input_buffer); + jerry_release_value (input_buffer); + } + + const char *eval_arraybuffer_src_p = ( + "var array = new Uint8Array(input_buffer);" + "for (var i = 0; i < array.length; i++)" + "{" + " array[i] = i * 2;" + "};" + "array.buffer"); + jerry_value_t buffer = jerry_eval ((jerry_char_t *) eval_arraybuffer_src_p, + strlen (eval_arraybuffer_src_p), + true); + + TEST_ASSERT (!jerry_value_has_error_flag (buffer)); + TEST_ASSERT (jerry_value_is_arraybuffer (buffer)); + TEST_ASSERT (jerry_get_arraybuffer_byte_length (buffer) == 20); + + uint8_t *const data = jerry_get_arraybuffer_pointer (buffer); + + /* test memory read */ + for (int i = 0; i < 20; i++) + { + TEST_ASSERT (data[i] == (uint8_t) (i * 2)); + } + + /* "upload" new data */ + double sum = 0; + for (int i = 0; i < 20; i++) + { + data[i] = (uint8_t)(i * 3); + sum += data[i]; + } + + jerry_release_value (buffer); + + const char *eval_test_arraybuffer_p = ( + "var sum = 0;" + "for (var i = 0; i < array.length; i++)" + "{" + " var expected = i * 3;" + " assert(array[i] == expected, 'Array at index ' + i + ' was: ' + array[i] + ' should be: ' + expected);" + " sum += array[i]" + "};" + "sum"); + jerry_value_t res = jerry_eval ((jerry_char_t *) eval_test_arraybuffer_p, + strlen (eval_test_arraybuffer_p), + true); + TEST_ASSERT (jerry_value_is_number (res)); + TEST_ASSERT (jerry_get_number_value (res) == sum); + jerry_release_value (res); + + jerry_release_value (buffer); + } + + /* Test ArrayBuffer external with invalid arguments */ + { + jerry_value_t input_buffer = jerry_create_arraybuffer_external (0, NULL, NULL); + TEST_ASSERT (jerry_value_has_error_flag (input_buffer)); + jerry_value_clear_error_flag (&input_buffer); + + if (jerry_is_feature_enabled (JERRY_FEATURE_ERROR_MESSAGES)) + { + jerry_char_t error_str[15]; + jerry_char_t expected_error_str[15] = "RangeError"; + + jerry_char_t *name_str_p = (jerry_char_t *) "name"; + jerry_value_t name_key = jerry_create_string (name_str_p); + jerry_value_t name_value = jerry_get_property (input_buffer, name_key); + + jerry_size_t name_size = jerry_string_to_char_buffer (name_value, error_str, sizeof (error_str)); + TEST_ASSERT (name_size == strlen ((char *) expected_error_str)); + TEST_ASSERT (strncmp ((char *) error_str, (char *) expected_error_str, name_size) == 0); + + jerry_release_value (name_value); + jerry_release_value (name_key); + } + jerry_release_value (input_buffer); + } + jerry_cleanup (); + + TEST_ASSERT (callback_called == true); } /* main */