From 56a8069b0e822f64f15f05aad95acf5ed635546d Mon Sep 17 00:00:00 2001 From: legendecas Date: Sun, 10 Feb 2019 14:50:42 +0800 Subject: [PATCH] Implement handle scope API in jerry-ext JerryScript-DCO-1.0-Signed-off-by: legendecas legendecas@gmail.com --- docs/14.EXT-REFERENCE-HANDLE-SCOPE.md | 112 ++++++ jerry-ext/CMakeLists.txt | 1 + .../handle-scope/handle-scope-allocator.c | 224 +++++++++++ .../handle-scope/handle-scope-internal.h | 89 +++++ jerry-ext/handle-scope/handle-scope.c | 361 ++++++++++++++++++ .../include/jerryscript-ext/handle-scope.h | 135 +++++++ tests/unit-ext/test-ext-handle-scope-escape.c | 82 ++++ ...t-ext-handle-scope-handle-prelist-escape.c | 89 +++++ .../test-ext-handle-scope-handle-prelist.c | 89 +++++ tests/unit-ext/test-ext-handle-scope-nested.c | 102 +++++ tests/unit-ext/test-ext-handle-scope-remove.c | 85 +++++ tests/unit-ext/test-ext-handle-scope-root.c | 74 ++++ tests/unit-ext/test-ext-handle-scope.c | 67 ++++ 13 files changed, 1510 insertions(+) create mode 100644 docs/14.EXT-REFERENCE-HANDLE-SCOPE.md create mode 100644 jerry-ext/handle-scope/handle-scope-allocator.c create mode 100644 jerry-ext/handle-scope/handle-scope-internal.h create mode 100644 jerry-ext/handle-scope/handle-scope.c create mode 100644 jerry-ext/include/jerryscript-ext/handle-scope.h create mode 100644 tests/unit-ext/test-ext-handle-scope-escape.c create mode 100644 tests/unit-ext/test-ext-handle-scope-handle-prelist-escape.c create mode 100644 tests/unit-ext/test-ext-handle-scope-handle-prelist.c create mode 100644 tests/unit-ext/test-ext-handle-scope-nested.c create mode 100644 tests/unit-ext/test-ext-handle-scope-remove.c create mode 100644 tests/unit-ext/test-ext-handle-scope-root.c create mode 100644 tests/unit-ext/test-ext-handle-scope.c diff --git a/docs/14.EXT-REFERENCE-HANDLE-SCOPE.md b/docs/14.EXT-REFERENCE-HANDLE-SCOPE.md new file mode 100644 index 0000000000..0af92702c8 --- /dev/null +++ b/docs/14.EXT-REFERENCE-HANDLE-SCOPE.md @@ -0,0 +1,112 @@ +# Handle Scope + +## jerryx_handle_scope + +**Summary** +It is often necessary to make the lifespan of handles shorter than the lifespan of a native method. Even though the native code could only use the most recent handle, all of the associated objects would also be kept alive since they all share the same scope. + +To handle this case, JerryScript HandleScope extension provides the ability to establish a new 'scope' to which newly created handles will be associated. Once those handles are no longer required, the scope can be 'closed' and any handles associated with the scope are invalidated. The methods available to open/close scopes are `jerryx_open_handle_scope` and `jerryx_close_handle_scope`. + +JerryScript only supports a single nested hierarchy of scopes. There is only one active scope at any time, and all new handles will be associated with that scope while it is active. Scopes must be closed in the reverse order from which they are opened. In addition, all scopes created within a native method must be closed before returning from that method. + +**Example** + +[doctest]: # (test="compile") + +```c +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" + +static jerry_value_t +create_object (void) +{ + jerry_value_t obj = jerry_create_object (); + return obj; +} /* create_object */ + +static void +test_handle_scope_val (void) +{ + jerryx_handle_scope scope; + jerryx_open_handle_scope (&scope); + jerry_value_t obj = jerryx_create_handle (create_object ()); + + jerryx_close_handle_scope (scope); + // now obj has been released +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + test_handle_scope_val (); + jerry_gc (JERRY_GC_SEVERITY_LOW); + + jerry_cleanup (); +} /* main */ +``` + +## jerryx_escapable_handle_scope + +**Summary** + +It is necessary in common cases that a handle has to be promote to outer scope and prevent from been garbage collected. To handle this case, a escapable handle scope has been proposed from which one object can be promoted to the outer scope. The method available to escape an object from been release at current scope is `jerryx_escape_handle`. + +**Example** + +[doctest]: # (test="compile") + +```c +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" + +static jerry_value_t +create_object (void) +{ + jerryx_escapable_handle_scope scope; + jerryx_open_escapable_handle_scope (&scope); + jerry_value_t obj = jerryx_create_handle (jerry_create_object ()); + + jerry_value_t escaped_obj; + jerryx_escape_handle(scope, obj, &escaped_obj); + jerryx_close_handle_scope (scope); + // escaped_obj has now been escaped to outer scope, thus not released at this point + + return escaped_obj; +} /* create_object */ + +static void +test_handle_scope_val (void) +{ + jerryx_handle_scope scope; + jerryx_open_handle_scope (&scope); + jerry_value_t obj = create_object (); + + jerryx_close_handle_scope (scope); + // now obj has been released +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + test_handle_scope_val (); + jerry_gc (JERRY_GC_SEVERITY_LOW); + + jerry_cleanup (); +} /* main */ +``` + +**See also** + +- [jerry_value_t](../docs/02.API-REFERENCE.md#jerry_value_t) +- [jerry_acquire_value](../docs/02.API-REFERENCE.md#jerry_acquire_value) +- [jerry_release_value](../docs/02.API-REFERENCE.md#jerry_release_value) + +## Pre-allocated list of handle scopes and handles + +To prevent trapping into system calls frequently, a pre-allocated dedicated list mechanism has been introduced to the implementation of JerryX handle scope. + +To change the size of pre-allocation list, use build definition `JERRYX_HANDLE_PRELIST_SIZE` and `JERRYX_SCOPE_PRELIST_SIZE` to alter the default value of 20. diff --git a/jerry-ext/CMakeLists.txt b/jerry-ext/CMakeLists.txt index bf1d2694f8..00e7b1be58 100644 --- a/jerry-ext/CMakeLists.txt +++ b/jerry-ext/CMakeLists.txt @@ -37,6 +37,7 @@ file(GLOB SOURCE_EXT arg/*.c common/*.c debugger/*.c + handle-scope/*.c handler/*.c module/*.c) diff --git a/jerry-ext/handle-scope/handle-scope-allocator.c b/jerry-ext/handle-scope/handle-scope-allocator.c new file mode 100644 index 0000000000..14f5e82220 --- /dev/null +++ b/jerry-ext/handle-scope/handle-scope-allocator.c @@ -0,0 +1,224 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "handle-scope-internal.h" + +static jerryx_handle_scope_t jerryx_handle_scope_root = +{ + .prelist_handle_count = 0, + .handle_ptr = NULL, +}; +static jerryx_handle_scope_t *jerryx_handle_scope_current = &jerryx_handle_scope_root; +static jerryx_handle_scope_pool_t jerryx_handle_scope_pool = +{ + .count = 0, + .start = NULL, +}; + +#define JERRYX_HANDLE_SCOPE_POOL_PRELIST_LAST \ + jerryx_handle_scope_pool.prelist + JERRYX_SCOPE_PRELIST_SIZE - 1 + +#define JERRYX_HANDLE_SCOPE_PRELIST_IDX(scope) (scope - jerryx_handle_scope_pool.prelist) + +/** + * Get current handle scope top of stack. + */ +inline jerryx_handle_scope_t * +jerryx_handle_scope_get_current (void) +{ + return jerryx_handle_scope_current; +} /* jerryx_handle_scope_get_current */ + +/** + * Get root handle scope. + */ +inline jerryx_handle_scope_t * +jerryx_handle_scope_get_root (void) +{ + return &jerryx_handle_scope_root; +} /* jerryx_handle_scope_get_root */ + +/** + * Determines if given handle scope is located in pre-allocated list. + * + * @param scope - the one to be determined. + */ +static inline bool +jerryx_handle_scope_is_in_prelist (jerryx_handle_scope_t *scope) +{ + return (jerryx_handle_scope_pool.prelist <= scope) + && (scope <= (jerryx_handle_scope_pool.prelist + JERRYX_SCOPE_PRELIST_SIZE - 1)); +} /** jerryx_handle_scope_is_in_prelist */ + +/** + * Get the parent of given handle scope. + * If given handle scope is in prelist, the parent must be in prelist too; + * if given is the first item of heap chain list, the parent must be the last one of prelist; + * the parent must be in chain list otherwise. + * + * @param scope - the one to be permformed on. + * @returns - the parent of the given scope. + */ +jerryx_handle_scope_t * +jerryx_handle_scope_get_parent (jerryx_handle_scope_t *scope) +{ + if (scope == &jerryx_handle_scope_root) + { + return NULL; + } + if (!jerryx_handle_scope_is_in_prelist (scope)) + { + jerryx_handle_scope_dynamic_t *dy_scope = (jerryx_handle_scope_dynamic_t *) scope; + if (dy_scope == jerryx_handle_scope_pool.start) + { + return JERRYX_HANDLE_SCOPE_POOL_PRELIST_LAST; + } + jerryx_handle_scope_dynamic_t *parent = dy_scope->parent; + return (jerryx_handle_scope_t *) parent; + } + if (scope == jerryx_handle_scope_pool.prelist) + { + return &jerryx_handle_scope_root; + } + return jerryx_handle_scope_pool.prelist + JERRYX_HANDLE_SCOPE_PRELIST_IDX (scope) - 1; +} /** jerryx_handle_scope_get_parent */ + +/** + * Get the child of given handle scope. + * If the given handle scope is in heap chain list, its child must be in heap chain list too; + * if the given handle scope is the last one of prelist, its child must be the first item of chain list; + * the children are in prelist otherwise. + * + * @param scope - the one to be permformed on. + * @returns the child of the given scope. + */ +jerryx_handle_scope_t * +jerryx_handle_scope_get_child (jerryx_handle_scope_t *scope) +{ + if (scope == &jerryx_handle_scope_root) + { + if (jerryx_handle_scope_pool.count > 0) + { + return jerryx_handle_scope_pool.prelist; + } + return NULL; + } + if (!jerryx_handle_scope_is_in_prelist (scope)) + { + jerryx_handle_scope_dynamic_t *child = ((jerryx_handle_scope_dynamic_t *) scope)->child; + return (jerryx_handle_scope_t *) child; + } + if (scope == JERRYX_HANDLE_SCOPE_POOL_PRELIST_LAST) + { + return (jerryx_handle_scope_t *) jerryx_handle_scope_pool.start; + } + long idx = JERRYX_HANDLE_SCOPE_PRELIST_IDX (scope); + if (idx < 0) + { + return NULL; + } + if ((unsigned long) idx >= jerryx_handle_scope_pool.count - 1) + { + return NULL; + } + return jerryx_handle_scope_pool.prelist + idx + 1; +} /** jerryx_handle_scope_get_child */ + +/** + * Claims a handle scope either from prelist or allocating a new memory block, + * and increment pool's scope count by 1, and set current scope to the newly claimed one. + * If there are still available spaces in prelist, claims a block in prelist; + * otherwise allocates a new memory block from heap and sets its fields to default values, + * and link it to previously dynamically allocated scope, or link it to pool's start pointer. + * + * @returns the newly claimed handle scope pointer. + */ +jerryx_handle_scope_t * +jerryx_handle_scope_alloc (void) +{ + jerryx_handle_scope_t *scope; + if (jerryx_handle_scope_pool.count < JERRYX_SCOPE_PRELIST_SIZE) + { + scope = jerryx_handle_scope_pool.prelist + jerryx_handle_scope_pool.count; + } + else + { + jerryx_handle_scope_dynamic_t *dy_scope = malloc (sizeof (jerryx_handle_scope_dynamic_t)); + JERRYX_HANDLE_SCOPE_ASSERT (dy_scope != NULL); + dy_scope->child = NULL; + + if (jerryx_handle_scope_pool.count != JERRYX_SCOPE_PRELIST_SIZE) + { + jerryx_handle_scope_dynamic_t *dy_current = (jerryx_handle_scope_dynamic_t *) jerryx_handle_scope_current; + dy_scope->parent = dy_current; + dy_current->child = dy_scope; + } + else + { + jerryx_handle_scope_pool.start = dy_scope; + dy_scope->parent = NULL; + } + + scope = (jerryx_handle_scope_t *) dy_scope; + } + + scope->prelist_handle_count = 0; + scope->escaped = false; + scope->handle_ptr = NULL; + + jerryx_handle_scope_current = scope; + ++jerryx_handle_scope_pool.count; + return (jerryx_handle_scope_t *) scope; +} /** jerryx_handle_scope_alloc */ + +/** + * Deannounce a previously claimed handle scope, return it to pool + * or free the allocated memory block. + * + * @param scope - the one to be freed. + */ +void +jerryx_handle_scope_free (jerryx_handle_scope_t *scope) +{ + if (scope == &jerryx_handle_scope_root) + { + return; + } + + --jerryx_handle_scope_pool.count; + if (scope == jerryx_handle_scope_current) + { + jerryx_handle_scope_current = jerryx_handle_scope_get_parent (scope); + } + + if (!jerryx_handle_scope_is_in_prelist (scope)) + { + jerryx_handle_scope_dynamic_t *dy_scope = (jerryx_handle_scope_dynamic_t *) scope; + if (dy_scope == jerryx_handle_scope_pool.start) + { + jerryx_handle_scope_pool.start = dy_scope->child; + } + else if (dy_scope->parent != NULL) + { + dy_scope->parent->child = dy_scope->child; + } + free (dy_scope); + return; + } + /** + * Nothing to do with scopes in prelist + */ +} /** jerryx_handle_scope_free */ diff --git a/jerry-ext/handle-scope/handle-scope-internal.h b/jerry-ext/handle-scope/handle-scope-internal.h new file mode 100644 index 0000000000..34d80a31fd --- /dev/null +++ b/jerry-ext/handle-scope/handle-scope-internal.h @@ -0,0 +1,89 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JERRYX_HANDLE_SCOPE_INTERNAL_H +#define JERRYX_HANDLE_SCOPE_INTERNAL_H + +#include "jerryscript.h" +#include "jerryscript-port.h" +#include "jerryscript-ext/handle-scope.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#define JERRYX_HANDLE_SCOPE_ASSERT(x) \ + do \ + { \ + if (!(x)) \ + { \ + jerry_port_log (JERRY_LOG_LEVEL_ERROR, \ + "JerryXHandleScope: Assertion '%s' failed at %s(%s):%lu.\n", \ + #x, \ + __FILE__, \ + __func__, \ + (unsigned long) __LINE__); \ + jerry_port_fatal (ERR_FAILED_INTERNAL_ASSERTION); \ + } \ + } while (0) + +/** MARK: - handle-scope-allocator.c */ +typedef struct jerryx_handle_scope_pool_s jerryx_handle_scope_pool_t; +/** + * A linear allocating memory pool for type jerryx_handle_scope_t, + * in which allocated item shall be released in reversed order of allocation + */ +struct jerryx_handle_scope_pool_s +{ + jerryx_handle_scope_t prelist[JERRYX_SCOPE_PRELIST_SIZE]; /**< inlined handle scopes in the pool */ + size_t count; /**< number of handle scopes in the pool */ + jerryx_handle_scope_dynamic_t *start; /**< start address of dynamically allocated handle scope list */ +}; + +jerryx_handle_scope_t * +jerryx_handle_scope_get_parent (jerryx_handle_scope_t *scope); + +jerryx_handle_scope_t * +jerryx_handle_scope_get_child (jerryx_handle_scope_t *scope); + +jerryx_handle_scope_t * +jerryx_handle_scope_alloc (void); + +void +jerryx_handle_scope_free (jerryx_handle_scope_t *scope); +/** MARK: - END handle-scope-allocator.c */ + +/** MARK: - handle-scope.c */ +void +jerryx_handle_scope_release_handles (jerryx_handle_scope scope); + +jerry_value_t +jerryx_hand_scope_escape_handle_from_prelist (jerryx_handle_scope scope, size_t idx); + +jerry_value_t +jerryx_handle_scope_add_handle_to (jerryx_handle_t *handle, jerryx_handle_scope scope); + +jerryx_handle_scope_status +jerryx_escape_handle_internal (jerryx_escapable_handle_scope scope, + jerry_value_t escapee, + jerry_value_t *result, + bool should_promote); +/** MARK: - END handle-scope.c */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !JERRYX_HANDLE_SCOPE_INTERNAL_H */ diff --git a/jerry-ext/handle-scope/handle-scope.c b/jerry-ext/handle-scope/handle-scope.c new file mode 100644 index 0000000000..8c0b3eb1fa --- /dev/null +++ b/jerry-ext/handle-scope/handle-scope.c @@ -0,0 +1,361 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "handle-scope-internal.h" + +/** + * Opens a new handle scope and attach it to current global scope as a child scope. + * + * @param result - [out value] opened scope. + * @return status code, jerryx_handle_scope_ok if success. + */ +jerryx_handle_scope_status +jerryx_open_handle_scope (jerryx_handle_scope *result) +{ + *result = jerryx_handle_scope_alloc (); + return jerryx_handle_scope_ok; +} /** jerryx_open_handle_scope */ + +/** + * Release all jerry values attached to given scope + * + * @param scope - the scope of handles to be released. + */ +void +jerryx_handle_scope_release_handles (jerryx_handle_scope scope) +{ + size_t prelist_handle_count = scope->prelist_handle_count; + if (prelist_handle_count == JERRYX_HANDLE_PRELIST_SIZE && scope->handle_ptr != NULL) + { + jerryx_handle_t *a_handle = scope->handle_ptr; + while (a_handle != NULL) + { + jerry_release_value (a_handle->jval); + jerryx_handle_t *sibling = a_handle->sibling; + free (a_handle); + a_handle = sibling; + } + scope->handle_ptr = NULL; + prelist_handle_count = JERRYX_HANDLE_PRELIST_SIZE; + } + + for (size_t idx = 0; idx < prelist_handle_count; idx++) + { + jerry_release_value (scope->handle_prelist[idx]); + } + scope->prelist_handle_count = 0; +} /** jerryx_handle_scope_release_handles */ + +/** + * Close the scope and its child scopes and release all jerry values that + * resides in the scopes. + * Scopes must be closed in the reverse order from which they were created. + * + * @param scope - the scope closed. + * @return status code, jerryx_handle_scope_ok if success. + */ +jerryx_handle_scope_status +jerryx_close_handle_scope (jerryx_handle_scope scope) +{ + /** + * Release all handles related to given scope and its child scopes + */ + jerryx_handle_scope a_scope = scope; + do + { + jerryx_handle_scope_release_handles (a_scope); + jerryx_handle_scope child = jerryx_handle_scope_get_child (a_scope); + jerryx_handle_scope_free (a_scope); + a_scope = child; + } + while (a_scope != NULL); + + return jerryx_handle_scope_ok; +} /** jerryx_close_handle_scope */ + +/** + * Opens a new handle scope from which one object can be promoted to the outer scope + * and attach it to current global scope as a child scope. + * + * @param result - [out value] opened escapable handle scope. + * @return status code, jerryx_handle_scope_ok if success. + */ +jerryx_handle_scope_status +jerryx_open_escapable_handle_scope (jerryx_handle_scope *result) +{ + return jerryx_open_handle_scope (result); +} /** jerryx_open_escapable_handle_scope */ + +/** + * Close the scope and its child scopes and release all jerry values that + * resides in the scopes. + * Scopes must be closed in the reverse order from which they were created. + * + * @param scope - the one to be closed. + * @return status code, jerryx_handle_scope_ok if success. + */ +jerryx_handle_scope_status +jerryx_close_escapable_handle_scope (jerryx_handle_scope scope) +{ + return jerryx_close_handle_scope (scope); +} /** jerryx_close_escapable_handle_scope */ + +/** + * Internal helper. + * Escape a jerry value from the scope, yet did not promote it to outer scope. + * An assertion of if parent exists shall be made before invoking this function. + * + * @param scope - scope of the handle added to. + * @param idx - expected index of the handle in the scope's prelist. + * @returns escaped jerry value id + */ +jerry_value_t +jerryx_hand_scope_escape_handle_from_prelist (jerryx_handle_scope scope, size_t idx) +{ + jerry_value_t jval = scope->handle_prelist[idx]; + if (scope->prelist_handle_count == JERRYX_HANDLE_PRELIST_SIZE && scope->handle_ptr != NULL) + { + jerryx_handle_t *handle = scope->handle_ptr; + scope->handle_ptr = handle->sibling; + scope->handle_prelist[idx] = handle->jval; + free (handle); + return jval; + } + + if (idx < JERRYX_HANDLE_PRELIST_SIZE - 1) + { + scope->handle_prelist[idx] = scope->handle_prelist[scope->prelist_handle_count - 1]; + } + return jval; +} /** jerryx_hand_scope_escape_handle_from_prelist */ + +/** + * Internal helper. + * Escape a jerry value from the given escapable handle scope. + * + * @param scope - the expected scope to be escaped from. + * @param escapee - the jerry value to be escaped. + * @param result - [out value] escaped jerry value result. + * @param should_promote - true if the escaped value should be added to parent + * scope after escaped from the given handle scope. + * @return status code, jerryx_handle_scope_ok if success. + */ +jerryx_handle_scope_status +jerryx_escape_handle_internal (jerryx_escapable_handle_scope scope, + jerry_value_t escapee, + jerry_value_t *result, + bool should_promote) +{ + if (scope->escaped) + { + return jerryx_escape_called_twice; + } + + jerryx_handle_scope parent = jerryx_handle_scope_get_parent (scope); + if (parent == NULL) + { + return jerryx_handle_scope_mismatch; + } + + bool found = false; + { + size_t found_idx = 0; + size_t prelist_count = scope->prelist_handle_count; + /** + * Search prelist in a reversed order since last added handle + * is possible the one to be escaped + */ + for (size_t idx_plus_1 = prelist_count; idx_plus_1 > 0; --idx_plus_1) + { + if (escapee == scope->handle_prelist[idx_plus_1 - 1]) + { + found = true; + found_idx = idx_plus_1 - 1; + break; + } + } + + if (found) + { + *result = jerryx_hand_scope_escape_handle_from_prelist (scope, found_idx); + + --scope->prelist_handle_count; + if (should_promote) + { + scope->escaped = true; + /** + * Escape handle to parent scope + */ + jerryx_create_handle_in_scope (*result, jerryx_handle_scope_get_parent (scope)); + } + return jerryx_handle_scope_ok; + } + }; + + if (scope->prelist_handle_count <= JERRYX_HANDLE_PRELIST_SIZE && scope->handle_ptr == NULL) + { + return jerryx_handle_scope_mismatch; + } + + /** + * Handle chain list is already in an reversed order, + * search through it as it is + */ + jerryx_handle_t *handle = scope->handle_ptr; + jerryx_handle_t *memo_handle = NULL; + jerryx_handle_t *found_handle = NULL; + while (!found) + { + if (handle == NULL) + { + return jerryx_handle_scope_mismatch; + } + if (handle->jval != escapee) + { + memo_handle = handle; + handle = handle->sibling; + continue; + } + /** + * Remove found handle from current scope's handle chain + */ + found = true; + found_handle = handle; + if (memo_handle == NULL) + { + // found handle is the first handle in heap + scope->handle_ptr = found_handle->sibling; + } + else + { + memo_handle->sibling = found_handle->sibling; + } + } + + if (should_promote) + { + /** + * Escape handle to parent scope + */ + *result = jerryx_handle_scope_add_handle_to (found_handle, parent); + } + + if (should_promote) + { + scope->escaped = true; + } + return jerryx_handle_scope_ok; +} /** jerryx_escape_handle_internal */ + +/** + * Promotes the handle to the JavaScript object so that it is valid for the lifetime of + * the outer scope. It can only be called once per scope. If it is called more than + * once an error will be returned. + * + * @param scope - the expected scope to be escaped from. + * @param escapee - the jerry value to be escaped. + * @param result - [out value] escaped jerry value result. + * @return status code, jerryx_handle_scope_ok if success. + */ +jerryx_handle_scope_status +jerryx_escape_handle (jerryx_escapable_handle_scope scope, + jerry_value_t escapee, + jerry_value_t *result) +{ + return jerryx_escape_handle_internal (scope, escapee, result, true); +} /** jerryx_escape_handle */ + +/** + * Escape a handle from scope yet do not promote it to the outer scope. + * Leave the handle's life time management up to user. + * + * @param scope - the expected scope to be removed from. + * @param escapee - the jerry value to be removed. + * @param result - [out value] removed jerry value result. + * @return status code, jerryx_handle_scope_ok if success. + */ +jerryx_handle_scope_status +jerryx_remove_handle (jerryx_escapable_handle_scope scope, + jerry_value_t escapee, + jerry_value_t *result) +{ + return jerryx_escape_handle_internal (scope, escapee, result, false); +} /** jerryx_remove_handle */ + +/** + * Try to reuse given handle if possible while adding to the scope. + * + * @param handle - the one to be added to the scope. + * @param scope - the scope of handle to be added to. + * @returns the jerry value id wrapped by given handle. + */ +jerry_value_t +jerryx_handle_scope_add_handle_to (jerryx_handle_t *handle, jerryx_handle_scope scope) +{ + size_t prelist_handle_count = scope->prelist_handle_count; + if (prelist_handle_count < JERRYX_HANDLE_PRELIST_SIZE) + { + ++scope->prelist_handle_count; + jerry_value_t jval = handle->jval; + free (handle); + scope->handle_prelist[prelist_handle_count] = jval; + return jval; + } + + handle->sibling = scope->handle_ptr; + scope->handle_ptr = handle; + return handle->jval; +} /** jerryx_handle_scope_add_handle_to */ + +/** + * Add given jerry value to the scope. + * + * @param jval - jerry value to be added to scope. + * @param scope - the scope of the jerry value been expected to be added to. + * @return jerry value that added to scope. + */ +jerry_value_t +jerryx_create_handle_in_scope (jerry_value_t jval, jerryx_handle_scope scope) +{ + size_t prelist_handle_count = scope->prelist_handle_count; + if (prelist_handle_count < JERRYX_HANDLE_PRELIST_SIZE) + { + scope->handle_prelist[prelist_handle_count] = jval; + + ++scope->prelist_handle_count; + return jval; + } + jerryx_handle_t *handle = malloc (sizeof (jerryx_handle_t)); + JERRYX_HANDLE_SCOPE_ASSERT (handle != NULL); + handle->jval = jval; + + handle->sibling = scope->handle_ptr; + scope->handle_ptr = handle; + + return jval; +} /** jerryx_create_handle_in_scope */ + +/** + * Add given jerry value to current top scope. + * + * @param jval - jerry value to be added to scope. + * @return jerry value that added to scope. + */ +jerry_value_t +jerryx_create_handle (jerry_value_t jval) +{ + return jerryx_create_handle_in_scope (jval, jerryx_handle_scope_get_current ()); +} /** jerryx_create_handle */ diff --git a/jerry-ext/include/jerryscript-ext/handle-scope.h b/jerry-ext/include/jerryscript-ext/handle-scope.h new file mode 100644 index 0000000000..bd871fb73a --- /dev/null +++ b/jerry-ext/include/jerryscript-ext/handle-scope.h @@ -0,0 +1,135 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JERRYX_HANDLE_SCOPE_H +#define JERRYX_HANDLE_SCOPE_H + +#include "jerryscript.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#ifndef JERRYX_HANDLE_PRELIST_SIZE +#define JERRYX_HANDLE_PRELIST_SIZE 20 +#endif + +#ifndef JERRYX_SCOPE_PRELIST_SIZE +#define JERRYX_SCOPE_PRELIST_SIZE 20 +#endif + +#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1] + +STATIC_ASSERT (JERRYX_SCOPE_PRELIST_SIZE < 32, JERRYX_SCOPE_PRELIST_SIZE_must_be_less_than_size_of_uint8_t); + +#undef STATIC_ASSERT + +typedef struct jerryx_handle_t jerryx_handle_t; +/** + * Dynamically allocated handle in the scopes. + * Scopes has it's own size-limited linear storage of handles. Still there + * might be not enough space left for new handles, dynamically allocated + * `jerryx_handle_t` could ease the pre-allocated linear memory burden. + */ +struct jerryx_handle_t +{ + jerry_value_t jval; /**< jerry value of the handle bound to */ + jerryx_handle_t *sibling; /**< next sibling the the handle */ +}; + +#define JERRYX_HANDLE_SCOPE_FIELDS \ + jerry_value_t handle_prelist[JERRYX_HANDLE_PRELIST_SIZE]; \ + uint8_t prelist_handle_count; \ + bool escaped; \ + jerryx_handle_t *handle_ptr + +typedef struct jerryx_handle_scope_s jerryx_handle_scope_t; +typedef jerryx_handle_scope_t *jerryx_handle_scope; +typedef jerryx_handle_scope_t *jerryx_escapable_handle_scope; +/** + * Inlined simple handle scope type. + */ +struct jerryx_handle_scope_s +{ + JERRYX_HANDLE_SCOPE_FIELDS; /**< common handle scope fields */ +}; + + +typedef struct jerryx_handle_scope_dynamic_s jerryx_handle_scope_dynamic_t; +/** + * Dynamically allocated handle scope type. + */ +struct jerryx_handle_scope_dynamic_s +{ + JERRYX_HANDLE_SCOPE_FIELDS; /**< common handle scope fields */ + jerryx_handle_scope_dynamic_t *child; /**< child dynamically allocated handle scope */ + jerryx_handle_scope_dynamic_t *parent; /**< parent dynamically allocated handle scope */ +}; + +#undef JERRYX_HANDLE_SCOPE_FIELDS + +typedef enum +{ + jerryx_handle_scope_ok = 0, + + jerryx_escape_called_twice, + jerryx_handle_scope_mismatch, +} jerryx_handle_scope_status; + +jerryx_handle_scope_status +jerryx_open_handle_scope (jerryx_handle_scope *result); + +jerryx_handle_scope_status +jerryx_close_handle_scope (jerryx_handle_scope scope); + +jerryx_handle_scope_status +jerryx_open_escapable_handle_scope (jerryx_handle_scope *result); + +jerryx_handle_scope_status +jerryx_close_escapable_handle_scope (jerryx_handle_scope scope); + +jerryx_handle_scope_status +jerryx_escape_handle (jerryx_escapable_handle_scope scope, + jerry_value_t escapee, + jerry_value_t *result); + +/** + * Completely escape a handle from handle scope, + * leave life time management totally up to user. + */ +jerryx_handle_scope_status +jerryx_remove_handle (jerryx_escapable_handle_scope scope, + jerry_value_t escapee, + jerry_value_t *result); + +jerry_value_t +jerryx_create_handle (jerry_value_t jval); + +jerry_value_t +jerryx_create_handle_in_scope (jerry_value_t jval, jerryx_handle_scope scope); + +/** MARK: - handle-scope-allocator.c */ +jerryx_handle_scope_t * +jerryx_handle_scope_get_current (void); + +jerryx_handle_scope_t * +jerryx_handle_scope_get_root (void); +/** MARK: - END handle-scope-allocator.c */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !JERRYX_HANDLE_SCOPE_H */ diff --git a/tests/unit-ext/test-ext-handle-scope-escape.c b/tests/unit-ext/test-ext-handle-scope-escape.c new file mode 100644 index 0000000000..4d653ff13c --- /dev/null +++ b/tests/unit-ext/test-ext-handle-scope-escape.c @@ -0,0 +1,82 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Unit test for jerry-ext/handle-scope. + */ + +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" +#include "test-common.h" + +static int native_free_cb_call_count; + +static void +native_free_cb (void *native_p) +{ + ++native_free_cb_call_count; + (void) native_p; +} /* native_free_cb */ + +static const jerry_object_native_info_t native_info = +{ + .free_cb = native_free_cb, +}; + +static jerry_value_t +create_object (void) +{ + jerryx_escapable_handle_scope scope; + jerryx_open_escapable_handle_scope (&scope); + jerry_value_t obj = jerryx_create_handle (jerry_create_object ()); + jerry_set_object_native_pointer (obj, NULL, &native_info); + + // If leaves `escaped` uninitialized, there will be a style error on linux thrown by compiler + jerry_value_t escaped = 0; + jerryx_escape_handle (scope, obj, &escaped); + TEST_ASSERT (scope->prelist_handle_count == 0); + TEST_ASSERT (scope->handle_ptr == NULL); + + jerryx_close_handle_scope (scope); + return escaped; +} /* create_object */ + +static void +test_handle_scope_val (void) +{ + jerryx_handle_scope scope; + jerryx_open_handle_scope (&scope); + jerry_value_t obj = create_object (); + (void) obj; + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == 0); + + jerryx_close_handle_scope (scope); +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + native_free_cb_call_count = 0; + test_handle_scope_val (); + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == 1); + + jerry_cleanup (); +} /* main */ diff --git a/tests/unit-ext/test-ext-handle-scope-handle-prelist-escape.c b/tests/unit-ext/test-ext-handle-scope-handle-prelist-escape.c new file mode 100644 index 0000000000..a2c382e1f1 --- /dev/null +++ b/tests/unit-ext/test-ext-handle-scope-handle-prelist-escape.c @@ -0,0 +1,89 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Unit test for jerry-ext/handle-scope-handle-prelist-escape. + * + * Tests escaping jerry value that holds on scope's prelist. + */ + +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" +#include "test-common.h" + +static size_t native_free_cb_call_count; +static const size_t handle_count = JERRYX_HANDLE_PRELIST_SIZE + 1; + +static void +native_free_cb (void *native_p) +{ + ++native_free_cb_call_count; + (void) native_p; +} /* native_free_cb */ + +static const jerry_object_native_info_t native_info = +{ + .free_cb = native_free_cb, +}; + +static jerry_value_t +create_object (void) +{ + jerryx_escapable_handle_scope scope; + jerryx_open_escapable_handle_scope (&scope); + + jerry_value_t obj; + for (size_t idx = 0; idx < handle_count; idx ++) + { + obj = jerryx_create_handle (jerry_create_object ()); + jerry_set_object_native_pointer (obj, NULL, &native_info); + } + + // If leaves `escaped` uninitialized, there will be a style error on linux thrown by compiler + jerry_value_t escaped = 0; + jerryx_escape_handle (scope, obj, &escaped); + TEST_ASSERT (scope->prelist_handle_count == JERRYX_HANDLE_PRELIST_SIZE); + + jerryx_close_handle_scope (scope); + return escaped; +} /* create_object */ + +static void +test_handle_scope_val (void) +{ + jerryx_handle_scope scope; + jerryx_open_handle_scope (&scope); + jerry_value_t obj = create_object (); + (void) obj; + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == (handle_count -1)); + + jerryx_close_handle_scope (scope); +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + native_free_cb_call_count = 0; + test_handle_scope_val (); + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == handle_count); + + jerry_cleanup (); +} /* main */ diff --git a/tests/unit-ext/test-ext-handle-scope-handle-prelist.c b/tests/unit-ext/test-ext-handle-scope-handle-prelist.c new file mode 100644 index 0000000000..995e6f3075 --- /dev/null +++ b/tests/unit-ext/test-ext-handle-scope-handle-prelist.c @@ -0,0 +1,89 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Unit test for jerry-ext/handle-scope-handle-prelist. + * + * Tests escaping jerry value that holds on scope's handle heap. + */ + +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" +#include "test-common.h" + +static size_t native_free_cb_call_count; +static const size_t handle_count = JERRYX_HANDLE_PRELIST_SIZE * 2; + +static void +native_free_cb (void *native_p) +{ + ++native_free_cb_call_count; + (void) native_p; +} /* native_free_cb */ + +static const jerry_object_native_info_t native_info = +{ + .free_cb = native_free_cb, +}; + +static jerry_value_t +create_object (void) +{ + jerryx_escapable_handle_scope scope; + jerryx_open_escapable_handle_scope (&scope); + + jerry_value_t obj; + for (size_t idx = 0; idx < handle_count; idx ++) + { + obj = jerryx_create_handle (jerry_create_object ()); + jerry_set_object_native_pointer (obj, NULL, &native_info); + } + + // If leaves `escaped` uninitialized, there will be a style error on linux thrown by compiler + jerry_value_t escaped = 0; + jerryx_escape_handle (scope, obj, &escaped); + TEST_ASSERT (scope->prelist_handle_count == JERRYX_HANDLE_PRELIST_SIZE); + + jerryx_close_handle_scope (scope); + return escaped; +} /* create_object */ + +static void +test_handle_scope_val (void) +{ + jerryx_handle_scope scope; + jerryx_open_handle_scope (&scope); + jerry_value_t obj = create_object (); + (void) obj; + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == (handle_count -1)); + + jerryx_close_handle_scope (scope); +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + native_free_cb_call_count = 0; + test_handle_scope_val (); + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == handle_count); + + jerry_cleanup (); +} /* main */ diff --git a/tests/unit-ext/test-ext-handle-scope-nested.c b/tests/unit-ext/test-ext-handle-scope-nested.c new file mode 100644 index 0000000000..850a532e4e --- /dev/null +++ b/tests/unit-ext/test-ext-handle-scope-nested.c @@ -0,0 +1,102 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Unit test for jerry-ext/handle-scope. + * + * Tests escaping jerry value that passed from scopes which are created on heap. + * Also reallocates scopes for one times to test if reallocation works. + */ + +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" +#include "test-common.h" + +static int native_free_cb_call_count; + +static void +native_free_cb (void *native_p) +{ + ++native_free_cb_call_count; + (void) native_p; +} /* native_free_cb */ + +static const jerry_object_native_info_t native_info = +{ + .free_cb = native_free_cb, +}; + +static jerry_value_t +create_object_nested (int times) +{ + jerryx_escapable_handle_scope scope; + jerryx_open_escapable_handle_scope (&scope); + + jerry_value_t obj; + if (times == 0) + { + obj = jerryx_create_handle (jerry_create_object ()); + jerry_set_object_native_pointer (obj, NULL, &native_info); + } + else + { + obj = create_object_nested (times - 1); + } + TEST_ASSERT (jerryx_handle_scope_get_current () == scope); + + // If leaves `escaped` uninitialized, there will be a style error on linux thrown by compiler + jerry_value_t escaped = 0; + int status = jerryx_escape_handle (scope, obj, &escaped); + TEST_ASSERT (status == 0); + TEST_ASSERT (scope->prelist_handle_count == 0); + TEST_ASSERT (scope->handle_ptr == NULL); + + jerryx_close_handle_scope (scope); + return escaped; +} /* create_object_nested */ + +static void +test_handle_scope_val (void) +{ + jerryx_handle_scope scope; + jerryx_open_handle_scope (&scope); + + for (int idx = 0; idx < 2; ++idx) + { + jerry_value_t obj = create_object_nested (JERRYX_SCOPE_PRELIST_SIZE * 2); + (void) obj; + } + + TEST_ASSERT (jerryx_handle_scope_get_current () == scope); + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == 0); + + jerryx_close_handle_scope (scope); +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + native_free_cb_call_count = 0; + test_handle_scope_val (); + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == 2); + + jerry_cleanup (); +} /* main */ diff --git a/tests/unit-ext/test-ext-handle-scope-remove.c b/tests/unit-ext/test-ext-handle-scope-remove.c new file mode 100644 index 0000000000..56b65cf9cc --- /dev/null +++ b/tests/unit-ext/test-ext-handle-scope-remove.c @@ -0,0 +1,85 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Unit test for jerry-ext/handle-scope. + */ + +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" +#include "test-common.h" + +static int native_free_cb_call_count; + +static void +native_free_cb (void *native_p) +{ + ++native_free_cb_call_count; + (void) native_p; +} /* native_free_cb */ + +static const jerry_object_native_info_t native_info = +{ + .free_cb = native_free_cb, +}; + +static jerry_value_t +create_object (void) +{ + jerryx_escapable_handle_scope scope; + jerryx_open_escapable_handle_scope (&scope); + jerry_value_t obj = jerryx_create_handle (jerry_create_object ()); + jerry_set_object_native_pointer (obj, NULL, &native_info); + + // If leaves `escaped` uninitialized, there will be a style error on linux thrown by compiler + jerry_value_t escaped = 0; + jerryx_remove_handle (scope, obj, &escaped); + TEST_ASSERT (scope->prelist_handle_count == 0); + TEST_ASSERT (scope->handle_ptr == NULL); + + jerryx_close_handle_scope (scope); + return escaped; +} /* create_object */ + +static void +test_handle_scope_val (void) +{ + jerryx_handle_scope scope; + jerryx_open_handle_scope (&scope); + jerry_value_t obj = create_object (); + (void) obj; + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == 0); + + jerryx_close_handle_scope (scope); + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == 0); + + jerry_release_value (obj); + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == 1); +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + native_free_cb_call_count = 0; + test_handle_scope_val (); + + jerry_cleanup (); +} /* main */ diff --git a/tests/unit-ext/test-ext-handle-scope-root.c b/tests/unit-ext/test-ext-handle-scope-root.c new file mode 100644 index 0000000000..6e3c11b2bb --- /dev/null +++ b/tests/unit-ext/test-ext-handle-scope-root.c @@ -0,0 +1,74 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Unit test for jerry-ext/handle-scope. + */ + +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" +#include "test-common.h" + +static int native_free_cb_call_count; +static int reusing_times = 10; + +static void +native_free_cb (void *native_p) +{ + ++native_free_cb_call_count; + (void) native_p; +} /* native_free_cb */ + +static const jerry_object_native_info_t native_info = +{ + .free_cb = native_free_cb, +}; + +static jerry_value_t +create_object (void) +{ + jerry_value_t obj = jerry_create_object (); + jerry_set_object_native_pointer (obj, NULL, &native_info); + return obj; +} /* create_object */ + +static void +test_handle_scope_val (void) +{ + jerryx_escapable_handle_scope root = jerryx_handle_scope_get_root (); + // root handle scope should always be reusable after close + for (int i = 0; i < reusing_times; ++i) + { + jerry_value_t obj = jerryx_create_handle (create_object ()); + (void) obj; + jerryx_close_handle_scope (root); + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == (i + 1)); + } +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + native_free_cb_call_count = 0; + test_handle_scope_val (); + + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == reusing_times); + + jerry_cleanup (); +} /* main */ diff --git a/tests/unit-ext/test-ext-handle-scope.c b/tests/unit-ext/test-ext-handle-scope.c new file mode 100644 index 0000000000..7fa056b121 --- /dev/null +++ b/tests/unit-ext/test-ext-handle-scope.c @@ -0,0 +1,67 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Unit test for jerry-ext/handle-scope. + */ + +#include "jerryscript.h" +#include "jerryscript-ext/handle-scope.h" +#include "test-common.h" + +static int native_free_cb_call_count; + +static void +native_free_cb (void *native_p) +{ + ++native_free_cb_call_count; + (void) native_p; +} /* native_free_cb */ + +static const jerry_object_native_info_t native_info = +{ + .free_cb = native_free_cb, +}; + +static jerry_value_t +create_object (void) +{ + jerry_value_t obj = jerry_create_object (); + jerry_set_object_native_pointer (obj, NULL, &native_info); + return obj; +} /* create_object */ + +static void +test_handle_scope_val (void) +{ + jerryx_handle_scope scope; + jerryx_open_handle_scope (&scope); + jerry_value_t obj = jerryx_create_handle (create_object ()); + (void) obj; + jerryx_close_handle_scope (scope); +} /* test_handle_scope_val */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + native_free_cb_call_count = 0; + test_handle_scope_val (); + jerry_gc (JERRY_GC_SEVERITY_LOW); + TEST_ASSERT (native_free_cb_call_count == 1); + + jerry_cleanup (); +} /* main */