1/* Copyright JS Foundation and other contributors, http://js.foundation
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <stdlib.h>
17#include "handle-scope-internal.h"
18
19/**
20 * Opens a new handle scope and attach it to current global scope as a child scope.
21 *
22 * @param result - [out value] opened scope.
23 * @return status code, jerryx_handle_scope_ok if success.
24 */
25jerryx_handle_scope_status
26jerryx_open_handle_scope (jerryx_handle_scope *result)
27{
28  *result = jerryx_handle_scope_alloc ();
29  return jerryx_handle_scope_ok;
30} /** jerryx_open_handle_scope */
31
32/**
33 * Release all jerry values attached to given scope
34 *
35 * @param scope - the scope of handles to be released.
36 */
37void
38jerryx_handle_scope_release_handles (jerryx_handle_scope scope)
39{
40  size_t prelist_handle_count = scope->prelist_handle_count;
41  if (prelist_handle_count == JERRYX_HANDLE_PRELIST_SIZE && scope->handle_ptr != NULL)
42  {
43    jerryx_handle_t *a_handle = scope->handle_ptr;
44    while (a_handle != NULL)
45    {
46      jerry_release_value (a_handle->jval);
47      jerryx_handle_t *sibling = a_handle->sibling;
48      free (a_handle);
49      a_handle = sibling;
50    }
51    scope->handle_ptr = NULL;
52    prelist_handle_count = JERRYX_HANDLE_PRELIST_SIZE;
53  }
54
55  for (size_t idx = 0; idx < prelist_handle_count; idx++)
56  {
57    jerry_release_value (scope->handle_prelist[idx]);
58  }
59  scope->prelist_handle_count = 0;
60} /** jerryx_handle_scope_release_handles */
61
62/**
63 * Close the scope and its child scopes and release all jerry values that
64 * resides in the scopes.
65 * Scopes must be closed in the reverse order from which they were created.
66 *
67 * @param scope - the scope closed.
68 * @return status code, jerryx_handle_scope_ok if success.
69 */
70jerryx_handle_scope_status
71jerryx_close_handle_scope (jerryx_handle_scope scope)
72{
73  /**
74   * Release all handles related to given scope and its child scopes
75   */
76  jerryx_handle_scope a_scope = scope;
77  do
78  {
79    jerryx_handle_scope_release_handles (a_scope);
80    jerryx_handle_scope child = jerryx_handle_scope_get_child (a_scope);
81    jerryx_handle_scope_free (a_scope);
82    a_scope = child;
83  }
84  while (a_scope != NULL);
85
86  return jerryx_handle_scope_ok;
87} /** jerryx_close_handle_scope */
88
89/**
90 * Opens a new handle scope from which one object can be promoted to the outer scope
91 * and attach it to current global scope as a child scope.
92 *
93 * @param result - [out value] opened escapable handle scope.
94 * @return status code, jerryx_handle_scope_ok if success.
95 */
96jerryx_handle_scope_status
97jerryx_open_escapable_handle_scope (jerryx_handle_scope *result)
98{
99  return jerryx_open_handle_scope (result);
100} /** jerryx_open_escapable_handle_scope */
101
102/**
103 * Close the scope and its child scopes and release all jerry values that
104 * resides in the scopes.
105 * Scopes must be closed in the reverse order from which they were created.
106 *
107 * @param scope - the one to be closed.
108 * @return status code, jerryx_handle_scope_ok if success.
109 */
110jerryx_handle_scope_status
111jerryx_close_escapable_handle_scope (jerryx_handle_scope scope)
112{
113  return jerryx_close_handle_scope (scope);
114} /** jerryx_close_escapable_handle_scope */
115
116/**
117 * Internal helper.
118 * Escape a jerry value from the scope, yet did not promote it to outer scope.
119 * An assertion of if parent exists shall be made before invoking this function.
120 *
121 * @param scope - scope of the handle added to.
122 * @param idx - expected index of the handle in the scope's prelist.
123 * @returns escaped jerry value id
124 */
125jerry_value_t
126jerryx_hand_scope_escape_handle_from_prelist (jerryx_handle_scope scope, size_t idx)
127{
128  jerry_value_t jval = scope->handle_prelist[idx];
129  if (scope->prelist_handle_count == JERRYX_HANDLE_PRELIST_SIZE && scope->handle_ptr != NULL)
130  {
131    jerryx_handle_t *handle = scope->handle_ptr;
132    scope->handle_ptr = handle->sibling;
133    scope->handle_prelist[idx] = handle->jval;
134    free (handle);
135    return jval;
136  }
137
138  if (idx < JERRYX_HANDLE_PRELIST_SIZE - 1)
139  {
140    scope->handle_prelist[idx] = scope->handle_prelist[scope->prelist_handle_count - 1];
141  }
142  return jval;
143} /** jerryx_hand_scope_escape_handle_from_prelist */
144
145/**
146 * Internal helper.
147 * Escape a jerry value from the given escapable handle scope.
148 *
149 * @param scope - the expected scope to be escaped from.
150 * @param escapee - the jerry value to be escaped.
151 * @param result - [out value] escaped jerry value result.
152 * @param should_promote - true if the escaped value should be added to parent
153 *                         scope after escaped from the given handle scope.
154 * @return status code, jerryx_handle_scope_ok if success.
155 */
156jerryx_handle_scope_status
157jerryx_escape_handle_internal (jerryx_escapable_handle_scope scope,
158                               jerry_value_t escapee,
159                               jerry_value_t *result,
160                               bool should_promote)
161{
162  if (scope->escaped)
163  {
164    return jerryx_escape_called_twice;
165  }
166
167  jerryx_handle_scope parent = jerryx_handle_scope_get_parent (scope);
168  if (parent == NULL)
169  {
170    return jerryx_handle_scope_mismatch;
171  }
172
173  bool found = false;
174  {
175    size_t found_idx = 0;
176    size_t prelist_count = scope->prelist_handle_count;
177    /**
178     * Search prelist in a reversed order since last added handle
179     * is possible the one to be escaped
180     */
181    for (size_t idx_plus_1 = prelist_count; idx_plus_1 > 0; --idx_plus_1)
182    {
183      if (escapee == scope->handle_prelist[idx_plus_1 - 1])
184      {
185        found = true;
186        found_idx = idx_plus_1 - 1;
187        break;
188      }
189    }
190
191    if (found)
192    {
193      *result = jerryx_hand_scope_escape_handle_from_prelist (scope, found_idx);
194
195      --scope->prelist_handle_count;
196      if (should_promote)
197      {
198        scope->escaped = true;
199        /**
200         * Escape handle to parent scope
201         */
202        jerryx_create_handle_in_scope (*result, jerryx_handle_scope_get_parent (scope));
203      }
204      return jerryx_handle_scope_ok;
205    }
206  };
207
208  if (scope->prelist_handle_count <= JERRYX_HANDLE_PRELIST_SIZE && scope->handle_ptr == NULL)
209  {
210    return jerryx_handle_scope_mismatch;
211  }
212
213  /**
214   * Handle chain list is already in an reversed order,
215   * search through it as it is
216   */
217  jerryx_handle_t *handle = scope->handle_ptr;
218  jerryx_handle_t *memo_handle = NULL;
219  jerryx_handle_t *found_handle = NULL;
220  while (!found)
221  {
222    if (handle == NULL)
223    {
224      return jerryx_handle_scope_mismatch;
225    }
226    if (handle->jval != escapee)
227    {
228      memo_handle = handle;
229      handle = handle->sibling;
230      continue;
231    }
232    /**
233     * Remove found handle from current scope's handle chain
234     */
235    found = true;
236    found_handle = handle;
237    if (memo_handle == NULL)
238    {
239      // found handle is the first handle in heap
240      scope->handle_ptr = found_handle->sibling;
241    }
242    else
243    {
244      memo_handle->sibling = found_handle->sibling;
245    }
246  }
247
248  if (should_promote)
249  {
250    /**
251     * Escape handle to parent scope
252     */
253    *result = jerryx_handle_scope_add_handle_to (found_handle, parent);
254  }
255
256  if (should_promote)
257  {
258    scope->escaped = true;
259  }
260  return jerryx_handle_scope_ok;
261} /** jerryx_escape_handle_internal */
262
263/**
264 * Promotes the handle to the JavaScript object so that it is valid for the lifetime of
265 * the outer scope. It can only be called once per scope. If it is called more than
266 * once an error will be returned.
267 *
268 * @param scope - the expected scope to be escaped from.
269 * @param escapee - the jerry value to be escaped.
270 * @param result - [out value] escaped jerry value result.
271 * @return status code, jerryx_handle_scope_ok if success.
272 */
273jerryx_handle_scope_status
274jerryx_escape_handle (jerryx_escapable_handle_scope scope,
275                      jerry_value_t escapee,
276                      jerry_value_t *result)
277{
278  return jerryx_escape_handle_internal (scope, escapee, result, true);
279} /** jerryx_escape_handle */
280
281/**
282 * Escape a handle from scope yet do not promote it to the outer scope.
283 * Leave the handle's life time management up to user.
284 *
285 * @param scope - the expected scope to be removed from.
286 * @param escapee - the jerry value to be removed.
287 * @param result - [out value] removed jerry value result.
288 * @return status code, jerryx_handle_scope_ok if success.
289 */
290jerryx_handle_scope_status
291jerryx_remove_handle (jerryx_escapable_handle_scope scope,
292                      jerry_value_t escapee,
293                      jerry_value_t *result)
294{
295  return jerryx_escape_handle_internal (scope, escapee, result, false);
296} /** jerryx_remove_handle */
297
298/**
299 * Try to reuse given handle if possible while adding to the scope.
300 *
301 * @param handle - the one to be added to the scope.
302 * @param scope - the scope of handle to be added to.
303 * @returns the jerry value id wrapped by given handle.
304 */
305jerry_value_t
306jerryx_handle_scope_add_handle_to (jerryx_handle_t *handle, jerryx_handle_scope scope)
307{
308  size_t prelist_handle_count = scope->prelist_handle_count;
309  if (prelist_handle_count < JERRYX_HANDLE_PRELIST_SIZE)
310  {
311    ++scope->prelist_handle_count;
312    jerry_value_t jval = handle->jval;
313    free (handle);
314    scope->handle_prelist[prelist_handle_count] = jval;
315    return jval;
316  }
317
318  handle->sibling = scope->handle_ptr;
319  scope->handle_ptr = handle;
320  return handle->jval;
321} /** jerryx_handle_scope_add_handle_to */
322
323/**
324 * Add given jerry value to the scope.
325 *
326 * @param jval - jerry value to be added to scope.
327 * @param scope - the scope of the jerry value been expected to be added to.
328 * @return jerry value that added to scope.
329 */
330jerry_value_t
331jerryx_create_handle_in_scope (jerry_value_t jval, jerryx_handle_scope scope)
332{
333  size_t prelist_handle_count = scope->prelist_handle_count;
334  if (prelist_handle_count < JERRYX_HANDLE_PRELIST_SIZE)
335  {
336    scope->handle_prelist[prelist_handle_count] = jval;
337
338    ++scope->prelist_handle_count;
339    return jval;
340  }
341  jerryx_handle_t *handle = malloc (sizeof (jerryx_handle_t));
342  JERRYX_HANDLE_SCOPE_ASSERT (handle != NULL);
343  handle->jval = jval;
344
345  handle->sibling = scope->handle_ptr;
346  scope->handle_ptr = handle;
347
348  return jval;
349} /** jerryx_create_handle_in_scope */
350
351/**
352 * Add given jerry value to current top scope.
353 *
354 * @param jval - jerry value to be added to scope.
355 * @return jerry value that added to scope.
356 */
357jerry_value_t
358jerryx_create_handle (jerry_value_t jval)
359{
360  return jerryx_create_handle_in_scope (jval, jerryx_handle_scope_get_current ());
361} /** jerryx_create_handle */
362