Revise the API ArrayBuffer related operations (#4284)

- External ArrayBuffer construction with 0 length should be equivalent to `new ArrayBuffer(0)`
- Internally allocated ArrayBuffers should be detachable
- Externally allocated ArrayBuffers free callback should be called when underlying buffer is detached

JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu
This commit is contained in:
Robert Fancsik 2020-10-09 15:12:45 +02:00 committed by GitHub
parent 5b9a6deb93
commit e3481d431b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 93 additions and 59 deletions

View File

@ -9011,7 +9011,9 @@ 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.
- NULL if the `value` is:
- not an ArrayBuffer object
- an external ArrayBuffer has been detached
*New in version 2.0*.
@ -9096,6 +9098,8 @@ jerry_value_t
jerry_detach_arraybuffer (const jerry_value_t value);
```
*Note*: If the ArrayBuffer has been created with `jerry_create_arraybuffer_external` the optional free callback is called on a successful detach operation
- `value` - ArrayBuffer to be detached
- return
- null value if success

View File

@ -4204,7 +4204,7 @@ jerry_create_arraybuffer_external (const jerry_length_t size, /**< size of the b
if (JERRY_UNLIKELY (size == 0 || buffer_p == NULL))
{
arraybuffer = ecma_arraybuffer_new_object_external (0, NULL, (ecma_object_native_free_callback_t) free_cb);
arraybuffer = ecma_arraybuffer_new_object (0);
}
else
{
@ -4353,7 +4353,9 @@ jerry_get_arraybuffer_byte_length (const jerry_value_t value) /**< ArrayBuffer *
* when accessing the pointer elements.
*
* @return pointer to the back-buffer of the ArrayBuffer.
* pointer is NULL if the parameter is not an ArrayBuffer
* pointer is NULL if:
* - the parameter is not an ArrayBuffer
* - an external ArrayBuffer has been detached
*/
uint8_t *
jerry_get_arraybuffer_pointer (const jerry_value_t array_buffer) /**< Array Buffer to use */
@ -4392,18 +4394,19 @@ jerry_is_arraybuffer_detachable (const jerry_value_t value) /**< ArrayBuffer */
if (ecma_is_arraybuffer (value))
{
ecma_object_t *buffer_p = ecma_get_object_from_value (value);
return ecma_arraybuffer_is_detachable (buffer_p) ? ECMA_VALUE_TRUE : ECMA_VALUE_FALSE;
return ecma_arraybuffer_is_detached (buffer_p) ? ECMA_VALUE_FALSE : ECMA_VALUE_TRUE;
}
#else /* !ENABLED (JERRY_BUILTIN_TYPEDARRAY) */
JERRY_UNUSED (value);
#endif /* ENABLED (JERRY_BUILTIN_TYPEDARRAY) */
return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Expects an ArrayBuffer")));
return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Expected an ArrayBuffer")));
} /* jerry_is_arraybuffer_detachable */
/**
* Detach the underlying data block from ArrayBuffer and set its bytelength to 0.
* This operation requires the ArrayBuffer to be external that created by
* `jerry_create_arraybuffer_external`.
*
* Note: If the ArrayBuffer has been created with `jerry_create_arraybuffer_external`
* the optional free callback is called on a successful detach operation
*
* @return null value - if success
* value marked with error flag - otherwise
@ -4417,17 +4420,16 @@ jerry_detach_arraybuffer (const jerry_value_t value) /**< ArrayBuffer */
if (ecma_is_arraybuffer (value))
{
ecma_object_t *buffer_p = ecma_get_object_from_value (value);
bool detached = ecma_arraybuffer_detach (buffer_p);
if (!detached)
if (ecma_arraybuffer_detach (buffer_p))
{
return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Expects a detachable ArrayBuffer.")));
return ECMA_VALUE_NULL;
}
return ECMA_VALUE_NULL;
return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("ArrayBuffer has already been detached.")));
}
#else /* !ENABLED (JERRY_BUILTIN_TYPEDARRAY) */
JERRY_UNUSED (value);
#endif /* ENABLED (JERRY_BUILTIN_TYPEDARRAY) */
return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Expects an ArrayBuffer")));
return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Expected an ArrayBuffer")));
} /* jerry_detach_arraybuffer */
/**

View File

@ -1384,11 +1384,10 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */
/* 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);
array_p->free_cb (array_p->buffer_p);
}
}
else

View File

@ -1882,8 +1882,12 @@ 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_DETACHED = (1u << 1), /* ArrayBuffer has been detached */
} ecma_arraybuffer_extra_flag_t;
/**
* Check whether the ArrayBuffer has external underlying buffer
*/
#define ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY(object_p) \
((((ecma_extended_object_t *) object_p)->u.class_prop.extra_info & ECMA_ARRAYBUFFER_EXTERNAL_MEMORY) != 0)

View File

@ -172,7 +172,7 @@ ecma_arraybuffer_get_length (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 ext_object_p->u.class_prop.u.length;
return ecma_arraybuffer_is_detached (object_p) ? 0 : ext_object_p->u.class_prop.u.length;
} /* ecma_arraybuffer_get_length */
/**
@ -190,12 +190,15 @@ ecma_arraybuffer_get_buffer (ecma_object_t *object_p) /**< pointer to the ArrayB
if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (ext_object_p))
{
ecma_arraybuffer_external_info *array_p = (ecma_arraybuffer_external_info *) ext_object_p;
JERRY_ASSERT (!ecma_arraybuffer_is_detached (object_p) || array_p->buffer_p == NULL);
return (lit_utf8_byte_t *) array_p->buffer_p;
}
else
else if (ext_object_p->u.class_prop.extra_info & ECMA_ARRAYBUFFER_DETACHED)
{
return (lit_utf8_byte_t *) (ext_object_p + 1);
return NULL;
}
return (lit_utf8_byte_t *) (ext_object_p + 1);
} /* ecma_arraybuffer_get_buffer */
/**
@ -209,41 +212,9 @@ ecma_arraybuffer_is_detached (ecma_object_t *object_p) /**< pointer to the Array
{
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;
if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (ext_object_p))
{
ecma_arraybuffer_external_info *array_p = (ecma_arraybuffer_external_info *) ext_object_p;
/* in case the arraybuffer has been detached */
return array_p->buffer_p == NULL;
}
return false;
return (((ecma_extended_object_t *) object_p)->u.class_prop.extra_info & ECMA_ARRAYBUFFER_DETACHED) != 0;
} /* ecma_arraybuffer_is_detached */
/**
* Helper function: check if the target ArrayBuffer is detachable
*
* @return true - if value is an detachable ArrayBuffer object
* false - otherwise
*/
inline bool JERRY_ATTR_PURE JERRY_ATTR_ALWAYS_INLINE
ecma_arraybuffer_is_detachable (ecma_object_t *object_p) /**< pointer to the ArrayBuffer object */
{
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;
if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (ext_object_p))
{
ecma_arraybuffer_external_info *array_p = (ecma_arraybuffer_external_info *) ext_object_p;
/* in case the arraybuffer has been detached */
return array_p->buffer_p != NULL;
}
return false;
} /* ecma_arraybuffer_is_detachable */
/**
* ArrayBuffer object detaching operation
*
@ -257,16 +228,27 @@ ecma_arraybuffer_detach (ecma_object_t *object_p) /**< pointer to the ArrayBuffe
{
JERRY_ASSERT (ecma_object_class_is (object_p, LIT_MAGIC_STRING_ARRAY_BUFFER_UL));
if (!ecma_arraybuffer_is_detachable (object_p))
if (ecma_arraybuffer_is_detached (object_p))
{
return false;
}
ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p;
ext_object_p->u.class_prop.extra_info |= ECMA_ARRAYBUFFER_DETACHED;
ecma_arraybuffer_external_info *array_object_p = (ecma_arraybuffer_external_info *) ext_object_p;
array_object_p->buffer_p = NULL;
array_object_p->extended_object.u.class_prop.u.length = 0;
if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (ext_object_p))
{
ecma_arraybuffer_external_info *array_p = (ecma_arraybuffer_external_info *) ext_object_p;
if (array_p->free_cb != NULL)
{
array_p->free_cb (array_p->buffer_p);
array_p->free_cb = NULL;
}
ext_object_p->u.class_prop.u.length = 0;
array_p->buffer_p = NULL;
}
return true;
} /* ecma_arraybuffer_detach */

View File

@ -45,8 +45,6 @@ uint32_t JERRY_ATTR_PURE
ecma_arraybuffer_get_length (ecma_object_t *obj_p);
bool JERRY_ATTR_PURE
ecma_arraybuffer_is_detached (ecma_object_t *obj_p);
bool JERRY_ATTR_PURE
ecma_arraybuffer_is_detachable (ecma_object_t *obj_p);
bool
ecma_arraybuffer_detach (ecma_object_t *obj_p);
bool

View File

@ -154,6 +154,7 @@ static void test_write_with_offset (uint8_t offset) /**< offset for buffer write
} /* test_write_with_offset */
static bool callback_called = false;
static bool detach_free_callback_called = false;
static void test_free_cb (void *buffer) /**< buffer to free (if needed) */
{
@ -161,6 +162,12 @@ static void test_free_cb (void *buffer) /**< buffer to free (if needed) */
callback_called = true;
} /* test_free_cb */
static void test_detach_free_cb (void *buffer) /**< buffer to free */
{
free (buffer);
detach_free_callback_called = true;
} /* test_detach_free_cb */
int
main (void)
{
@ -256,6 +263,7 @@ main (void)
jerry_value_t arraybuffer = jerry_create_arraybuffer_external (length, NULL, NULL);
TEST_ASSERT (!jerry_value_is_error (arraybuffer));
TEST_ASSERT (jerry_value_is_arraybuffer (arraybuffer));
TEST_ASSERT (jerry_is_arraybuffer_detachable (arraybuffer));
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arraybuffer) == length);
uint8_t data[20];
@ -364,7 +372,7 @@ main (void)
jerry_release_value (buffer);
}
/* Test ArrayBuffer detach */
/* Test internal ArrayBuffer detach */
{
const uint32_t length = 1;
jerry_value_t arraybuffer = jerry_create_arraybuffer (length);
@ -374,10 +382,14 @@ main (void)
jerry_value_t is_detachable = jerry_is_arraybuffer_detachable (arraybuffer);
TEST_ASSERT (!jerry_value_is_error (is_detachable));
TEST_ASSERT (!jerry_get_boolean_value (is_detachable));
TEST_ASSERT (jerry_get_boolean_value (is_detachable));
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arraybuffer) == length);
jerry_release_value (is_detachable);
jerry_value_t res = jerry_detach_arraybuffer (arraybuffer);
TEST_ASSERT (jerry_value_is_error (res));
TEST_ASSERT (!jerry_value_is_error (res));
TEST_ASSERT (jerry_get_arraybuffer_pointer (arraybuffer) == NULL);
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arraybuffer) == 0);
jerry_release_value (res);
jerry_release_value (arraybuffer);
@ -395,10 +407,43 @@ main (void)
jerry_value_t is_detachable = jerry_is_arraybuffer_detachable (arraybuffer);
TEST_ASSERT (!jerry_value_is_error (is_detachable));
TEST_ASSERT (jerry_get_boolean_value (is_detachable));
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arraybuffer) == length);
jerry_release_value (is_detachable);
jerry_value_t res = jerry_detach_arraybuffer (arraybuffer);
TEST_ASSERT (!jerry_value_is_error (res));
TEST_ASSERT (jerry_get_arraybuffer_pointer (arraybuffer) == NULL);
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arraybuffer) == 0);
is_detachable = jerry_is_arraybuffer_detachable (arraybuffer);
TEST_ASSERT (!jerry_value_is_error (is_detachable));
TEST_ASSERT (!jerry_get_boolean_value (is_detachable));
jerry_release_value (is_detachable);
jerry_release_value (res);
jerry_release_value (arraybuffer);
}
/* Test external ArrayBuffer with callback detach */
{
const uint32_t length = 8;
uint8_t *buf = (uint8_t *) malloc (length);
jerry_value_t arraybuffer = jerry_create_arraybuffer_external (length, buf, test_detach_free_cb);
TEST_ASSERT (!jerry_value_is_error (arraybuffer));
TEST_ASSERT (jerry_value_is_arraybuffer (arraybuffer));
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arraybuffer) == length);
jerry_value_t is_detachable = jerry_is_arraybuffer_detachable (arraybuffer);
TEST_ASSERT (!jerry_value_is_error (is_detachable));
TEST_ASSERT (jerry_get_boolean_value (is_detachable));
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arraybuffer) == length);
jerry_release_value (is_detachable);
jerry_value_t res = jerry_detach_arraybuffer (arraybuffer);
TEST_ASSERT (!jerry_value_is_error (res));
TEST_ASSERT (jerry_get_arraybuffer_pointer (arraybuffer) == NULL);
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arraybuffer) == 0);
TEST_ASSERT (detach_free_callback_called);
is_detachable = jerry_is_arraybuffer_detachable (arraybuffer);
TEST_ASSERT (!jerry_value_is_error (is_detachable));