diff --git a/docs/09.EXT-REFERENCE-ARG.md b/docs/09.EXT-REFERENCE-ARG.md index c6065615e8..08bd6d22c2 100644 --- a/docs/09.EXT-REFERENCE-ARG.md +++ b/docs/09.EXT-REFERENCE-ARG.md @@ -61,7 +61,7 @@ typedef struct **Summary** -The structure is used in `jerryx_arg_array`. It provides the array items' corresponding +The structure is used in `jerryx_arg_array`. It provides the array items' corresponding JS-to-C mappings and count. **Prototype** @@ -813,6 +813,32 @@ jerryx_arg_js_iterator_peek (jerryx_arg_js_iterator_t *js_arg_iter_p) - return value - the current `jerry_value_t` argument. - `js_arg_iter_p` - the JS arg iterator from which to peek. +## jerryx_arg_js_iterator_restore + +**Summary** + +Restore the last item popped from the stack. This can be called as +many times as there are arguments on the stack -- if called when the +first element in the array is the current top of the stack, this +function does nothing. + +*Note:* This function relies on the underlying implementation of the +arg stack as an array, as its function is to simply back up the "top +of stack" pointer to point to the previous element of the array. + +*Note:* Like `jerryx_arg_js_iterator_pop ()`, this function will +change the `js_arg_idx` and `js_arg_p` values in the iterator. + +**Prototype** + +```c +jerry_value_t +jerryx_arg_js_iterator_restore (jerryx_arg_js_iterator_t *js_arg_iter_p) +``` + - return value - the the new top of the stack. + - `js_arg_iter_p` - the JS arg iterator to restore. + + ## jerryx_arg_js_iterator_index **Summary** diff --git a/jerry-ext/arg/arg-js-iterator-helper.c b/jerry-ext/arg/arg-js-iterator-helper.c index 7267a681a9..ad8aa8db0a 100644 --- a/jerry-ext/arg/arg-js-iterator-helper.c +++ b/jerry-ext/arg/arg-js-iterator-helper.c @@ -30,6 +30,26 @@ jerryx_arg_js_iterator_pop (jerryx_arg_js_iterator_t *js_arg_iter_p) /**< the JS : jerry_create_undefined ()); } /* jerryx_arg_js_iterator_pop */ +/** + * Restore the previous JS argument from the iterator. + * It will change the index and js_arg_p value in the iterator. + * + * @return the restored (now current) JS argument. + */ +jerry_value_t +jerryx_arg_js_iterator_restore (jerryx_arg_js_iterator_t *js_arg_iter_p) /**< the JS arg iterator */ +{ + if (js_arg_iter_p->js_arg_idx == 0) + { + return jerry_create_undefined (); + } + + --js_arg_iter_p->js_arg_idx; + --js_arg_iter_p->js_arg_p; + + return *js_arg_iter_p->js_arg_p; +} /* jerryx_arg_js_iterator_restore */ + /** * Get the current JS argument from the iterator. * diff --git a/jerry-ext/include/jerryscript-ext/arg.h b/jerry-ext/include/jerryscript-ext/arg.h index c239aab57d..8171bcf3c2 100644 --- a/jerry-ext/include/jerryscript-ext/arg.h +++ b/jerry-ext/include/jerryscript-ext/arg.h @@ -186,6 +186,7 @@ jerryx_arg_transform_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, /* Helper functions for transform functions. */ jerry_value_t jerryx_arg_js_iterator_pop (jerryx_arg_js_iterator_t *js_arg_iter_p); +jerry_value_t jerryx_arg_js_iterator_restore (jerryx_arg_js_iterator_t *js_arg_iter_p); jerry_value_t jerryx_arg_js_iterator_peek (jerryx_arg_js_iterator_t *js_arg_iter_p); jerry_length_t jerryx_arg_js_iterator_index (jerryx_arg_js_iterator_t *js_arg_iter_p); diff --git a/tests/unit-ext/test-ext-arg.c b/tests/unit-ext/test-ext-arg.c index f4f7b8d395..98ee34fb01 100644 --- a/tests/unit-ext/test-ext-arg.c +++ b/tests/unit-ext/test-ext-arg.c @@ -57,6 +57,8 @@ static const jerry_char_t test_source[] = TEST_STRING_LITERAL ( "test_validator_array1(arr);" "test_validator_array1();" "test_validator_array2(arr);" + "test_validator_restore(false, 3.0);" + "test_validator_restore(3.0, false);" ); static const jerry_object_native_info_t thing_a_info = @@ -87,6 +89,7 @@ static int validator2_count = 0; static int validator_int_count = 0; static int validator_prop_count = 0; static int validator_array_count = 0; +static int validator_restore_count = 0; /** * The handler should have following arguments: @@ -561,6 +564,164 @@ test_validator_array2_handler (const jerry_value_t func_obj_val __attribute__((u return jerry_create_undefined (); } /* test_validator_array2_handler */ +/** + * This validator is designed to test the + * jerryx_arg_js_iterator_restore function. We'll introduce a union + * type to hold a bool or double and a transform function that will + * look for this type. Then, we'll call the handler with two + * parameters, one bool and one double and see if we correctly build + * the union types for each parameter. To check that the code protects + * against backing up too far, when the check for the double fails, + * we'll "restore" the stack three times; this shouldn't break + * anything. +*/ +/* + * This enumeration type specifies the kind of thing held in the union. +*/ +typedef enum +{ + DOUBLE_VALUE, + BOOL_VALUE +} union_type_t; + +/* + * This struct holds either a boolean or double in a union and has a + * second field that describes the type held in the union. +*/ +typedef struct +{ + union_type_t type_of_value; + union + { + double double_field; + bool bool_field; + } value; +} double_or_bool_t; + +/** + * This creates a jerryx_arg_t that can be used like any + * of the installed functions, like jerryx_arg_bool(). + */ +#define jerryx_arg_double_or_bool_t(value_ptr, coerce_or_not, optional_or_not, last_parameter) \ + jerryx_arg_custom (value_ptr, \ + (uintptr_t)&((uintptr_t []){(uintptr_t)coerce_or_not, \ + (uintptr_t)optional_or_not, \ + (uintptr_t)last_parameter}), \ + jerry_arg_to_double_or_bool_t) +/* + * This function is the argument validator used in the above macro called + * jerryx_arg_double_or_bool. It calls jerryx_arg_js_iterator_restore() + * more times than it should to ensure that calling that function too + * often doesn't cause an error. +*/ +static jerry_value_t +jerry_arg_to_double_or_bool_t (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p) +{ + /* c_arg_p has two fields: dest, which is a pointer to the data that + * gets filled in, and extra_info, which contains the flags used to + * control coercion and optional-ness, respectively. For this test, + * we added an extra flag that tells us that we're working on the + * last parameter; when we know it's the last parameter, we'll "restore" + * the stack more times than there are actual stack values to ensure + * that the restore function doesn't produce an error. */ + double_or_bool_t *destination = c_arg_p->dest; + uintptr_t *extra_info = (uintptr_t *)(c_arg_p->extra_info); + jerryx_arg_t conversion_function; + jerry_value_t conversion_result; + jerry_value_t restore_result; + bool last_parameter = (extra_info[2] == 1); + + validator_restore_count++; + + conversion_function = jerryx_arg_number ((double *) (&(destination->value.double_field)), + (jerryx_arg_coerce_t) extra_info[0], + JERRYX_ARG_OPTIONAL); + conversion_result = conversion_function.func (js_arg_iter_p, &conversion_function); + if (!jerry_value_is_error (conversion_result)) + { + if (last_parameter) + { + /* The stack is only two parameters high, but we want to ensure that + * excessive calls will not result in aberrant behavior... */ + jerryx_arg_js_iterator_restore (js_arg_iter_p); + jerryx_arg_js_iterator_restore (js_arg_iter_p); + jerryx_arg_js_iterator_restore (js_arg_iter_p); + restore_result = jerryx_arg_js_iterator_restore (js_arg_iter_p); + TEST_ASSERT (jerry_value_is_undefined (restore_result)); + } + + destination->type_of_value = DOUBLE_VALUE; + return conversion_result; + } + + jerryx_arg_js_iterator_restore (js_arg_iter_p); + + conversion_function = jerryx_arg_boolean ((bool *) (&(destination->value.bool_field)), + (jerryx_arg_coerce_t) extra_info[0], + (jerryx_arg_optional_t) extra_info[1]); + + jerry_release_value (conversion_result); + conversion_result = conversion_function.func (js_arg_iter_p, &conversion_function); + if (!jerry_value_is_error (conversion_result)) + { + if (last_parameter) + { + /* The stack is only two parameters high, but we want to ensure that + * excessive calls will not result in aberrant behavior... */ + jerryx_arg_js_iterator_restore (js_arg_iter_p); + jerryx_arg_js_iterator_restore (js_arg_iter_p); + jerryx_arg_js_iterator_restore (js_arg_iter_p); + restore_result = jerryx_arg_js_iterator_restore (js_arg_iter_p); + TEST_ASSERT (jerry_value_is_undefined (restore_result)); + } + + destination->type_of_value = BOOL_VALUE; + return conversion_result; + } + + /* Fall through indicates that whatever they gave us, it wasn't + * one of the types we were expecting... */ + jerry_release_value (conversion_result); + return jerry_create_error (JERRY_ERROR_TYPE, + (const jerry_char_t *) "double_or_bool-type error."); +} /* jerry_arg_to_double_or_bool_t */ + +/** + * This validator expects two parameters, one a bool and one a double -- the + * order doesn't matter (so we'll call it twice with the orders reversed). +*/ +static jerry_value_t +test_validator_restore_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val __attribute__((unused)), /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt __attribute__((unused))) /**< arguments length */ +{ + double_or_bool_t arg1; + double_or_bool_t arg2; + + jerryx_arg_t item_mapping[] = + { + jerryx_arg_double_or_bool_t (&arg1, JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED, 0), + jerryx_arg_double_or_bool_t (&arg2, JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED, 1) + }; + + jerry_value_t is_ok = jerryx_arg_transform_args (args_p, args_cnt, item_mapping, ARRAY_SIZE (item_mapping)); + + TEST_ASSERT (!jerry_value_is_error (is_ok)); + + /* We are going to call this with [false, 3.0] and [3.0, false] parameters... */ + bool arg1_is_false = (arg1.type_of_value == BOOL_VALUE && arg1.value.bool_field == false); + bool arg1_is_three = (arg1.type_of_value == DOUBLE_VALUE && arg1.value.double_field == 3.0); + bool arg2_is_false = (arg2.type_of_value == BOOL_VALUE && arg2.value.bool_field == false); + bool arg2_is_three = (arg2.type_of_value == DOUBLE_VALUE && arg2.value.double_field == 3.0); + TEST_ASSERT ((arg1_is_false && arg2_is_three) || (arg1_is_three && arg2_is_false)); + + jerry_release_value (is_ok); + + return jerry_create_undefined (); +} /* test_validator_restore_handler */ + static void test_utf8_string (void) { @@ -657,6 +818,7 @@ main (void) register_js_function ("test_validator_prop3", test_validator_prop3_handler); register_js_function ("test_validator_array1", test_validator_array1_handler); register_js_function ("test_validator_array2", test_validator_array2_handler); + register_js_function ("test_validator_restore", test_validator_restore_handler); jerry_value_t parsed_code_val = jerry_parse (NULL, 0, @@ -672,6 +834,7 @@ main (void) TEST_ASSERT (validator_prop_count == 4); TEST_ASSERT (validator_int_count == 3); TEST_ASSERT (validator_array_count == 3); + TEST_ASSERT (validator_restore_count == 4); jerry_release_value (res); jerry_release_value (parsed_code_val);