Skip to content

Support ECMAScript stopping in JerryScript. #1753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions docs/02.API-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Possible compile time enabled feature types:
- JERRY_FEATURE_SNAPSHOT_SAVE - saving snapshot files
- JERRY_FEATURE_SNAPSHOT_EXEC - executing snapshot files
- JERRY_FEATURE_DEBUGGER - debugging
- JERRY_FEATURE_VM_EXEC_STOP - stopping ECMAScript execution

## jerry_char_t

Expand Down Expand Up @@ -234,6 +235,28 @@ typedef bool (*jerry_object_property_foreach_t) (const jerry_value_t property_na
void *user_data_p);
```

## jerry_vm_exec_stop_callback_t

**Summary**

Callback which tells whether the ECMAScript execution should be stopped.
If it returns with undefined value the ECMAScript execution continues.
Otherwise the result is thrown by the engine (if the error flag is not
set for the returned value the engine automatically sets it). The
callback function might be called again even if it threw an error.
In this case the function must throw the same error again.

**Prototype**

```c
typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p);
```

**See also**

- [jerry_set_vm_exec_stop_callback](#jerry_set_vm_exec_stop_callback)


# General engine functions

## jerry_init
Expand Down Expand Up @@ -3896,3 +3919,81 @@ jerry_parse_and_save_literals (const jerry_char_t *source_p,
- [jerry_init](#jerry_init)
- [jerry_cleanup](#jerry_cleanup)
- [jerry_register_magic_strings](#jerry_register_magic_strings)


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on jerryscript.h: add a new top level header here: # Miscellaneous functions.

# Miscellaneous functions

## jerry_set_vm_exec_stop_callback

**Summary**

When JERRY_FEATURE_VM_EXEC_STOP is enabled a callback function can be
specified by this function. This callback is periodically called when
JerryScript executes an ECMAScript program.

If the callback returns with undefined value the ECMAScript execution
continues. Otherwise the result is thrown by the engine (if the error
flag is not set for the returned value the engine automatically sets
it). The callback function might be called again even if it threw
an error. In this case the function must throw the same error again.

To reduce the CPU overhead of constantly checking the termination
condition the callback is called when a backward jump is executed
or an exception is caught. Setting the `frequency` to a greater
than `1` value reduces this overhead further. If its value is N
only every Nth event (backward jump, etc.) trigger the next check.


**Prototype**

```c
void
jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we document the signature of jerry_vm_exec_stop_callback_t, too? Right now, it can only be deduced from the example.

void *user_p,
uint32_t frequency);
```

- `stop_cb` - periodically called callback (passing NULL disables this feature)
- `user_p` - user pointer passed to the `stop_cb` function
- `frequency` - frequency of calling the `stop_cb` function

**Example**

```c
static jerry_value_t
vm_exec_stop_callback (void *user_p)
{
static int countdown = 10;

while (countdown > 0)
{
countdown--;
return jerry_create_undefined ();
}

// The error flag is added automatically.
return jerry_create_string ((const jerry_char_t *) "Abort script");
}

{
jerry_init (JERRY_INIT_EMPTY);

jerry_set_vm_exec_stop_callback (vm_exec_stop_callback, &countdown, 16);

// Inifinte loop.
const char *src_p = "while(true) {}";

jerry_value_t src = jerry_parse ((jerry_char_t *) src_p, strlen (src_p), false);
jerry_release_value (jerry_run (src));
jerry_release_value (src);
jerry_cleanup ();
}
```

**See also**

- [jerry_init](#jerry_init)
- [jerry_cleanup](#jerry_cleanup)
- [jerry_parse](#jerry_parse)
- [jerry_run](#jerry_run)
- [jerry_vm_exec_stop_callback_t](#jerry_vm_exec_stop_callback_t)
7 changes: 7 additions & 0 deletions jerry-core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ set(FEATURE_SNAPSHOT_SAVE OFF CACHE BOOL "Enable saving snapshot files?
set(FEATURE_SYSTEM_ALLOCATOR OFF CACHE BOOL "Enable system allocator?")
set(FEATURE_VALGRIND OFF CACHE BOOL "Enable Valgrind support?")
set(FEATURE_VALGRIND_FREYA OFF CACHE BOOL "Enable Valgrind-Freya support?")
set(FEATURE_VM_EXEC_STOP OFF CACHE BOOL "Enable VM execution stopping?")
set(MEM_HEAP_SIZE_KB "512" CACHE STRING "Size of memory heap, in kilobytes")

if(FEATURE_SYSTEM_ALLOCATOR)
Expand All @@ -54,6 +55,7 @@ message(STATUS "FEATURE_SNAPSHOT_SAVE " ${FEATURE_SNAPSHOT_SAVE})
message(STATUS "FEATURE_SYSTEM_ALLOCATOR " ${FEATURE_SYSTEM_ALLOCATOR})
message(STATUS "FEATURE_VALGRIND " ${FEATURE_VALGRIND})
message(STATUS "FEATURE_VALGRIND_FREYA " ${FEATURE_VALGRIND_FREYA})
message(STATUS "FEATURE_VM_EXEC_STOP " ${FEATURE_VM_EXEC_STOP})
message(STATUS "MEM_HEAP_SIZE_KB " ${MEM_HEAP_SIZE_KB})

# Include directories
Expand Down Expand Up @@ -238,6 +240,11 @@ if(FEATURE_VALGRIND_FREYA)
set(INCLUDE_CORE ${INCLUDE_CORE} ${INCLUDE_THIRD_PARTY_VALGRIND})
endif()

# Enable VM execution stopping
if (FEATURE_VM_EXEC_STOP)
set(DEFINES_JERRY ${DEFINES_JERRY} JERRY_VM_EXEC_STOP)
endif()

# Size of heap
math(EXPR MEM_HEAP_AREA_SIZE "${MEM_HEAP_SIZE_KB} * 1024")
set(DEFINES_JERRY ${DEFINES_JERRY} CONFIG_MEM_HEAP_AREA_SIZE=${MEM_HEAP_AREA_SIZE})
Expand Down
8 changes: 8 additions & 0 deletions jerry-core/jcontext/jcontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ typedef struct
uint8_t re_cache_idx; /**< evicted item index when regex cache is full (round-robin) */
#endif /* !CONFIG_DISABLE_REGEXP_BUILTIN */

#ifdef JERRY_VM_EXEC_STOP
uint32_t vm_exec_stop_frequency; /**< reset value for vm_exec_stop_counter */
uint32_t vm_exec_stop_counter; /**< down counter for reducing the calls of vm_exec_stop_cb */
void *vm_exec_stop_user_p; /**< user pointer for vm_exec_stop_cb */
jerry_vm_exec_stop_callback_t vm_exec_stop_cb; /**< user function which returns whether the
* ECMAScript execution should be stopped */
#endif /* JERRY_VM_EXEC_STOP */

#ifdef JERRY_DEBUGGER
uint8_t debugger_send_buffer[JERRY_DEBUGGER_MAX_BUFFER_SIZE]; /**< buffer for sending messages */
uint8_t debugger_receive_buffer[JERRY_DEBUGGER_MAX_BUFFER_SIZE]; /**< buffer for receiving messages */
Expand Down
30 changes: 30 additions & 0 deletions jerry-core/jerry.c
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,9 @@ bool jerry_is_feature_enabled (const jerry_feature_t feature)
#ifdef JERRY_DEBUGGER
|| feature == JERRY_FEATURE_DEBUGGER
#endif /* JERRY_DEBUGGER */
#ifdef JERRY_VM_EXEC_STOP
|| feature == JERRY_FEATURE_VM_EXEC_STOP
#endif /* JERRY_VM_EXEC_STOP */
);
} /* jerry_is_feature_enabled */

Expand Down Expand Up @@ -2145,6 +2148,33 @@ jerry_is_valid_cesu8_string (const jerry_char_t *cesu8_buf_p, /**< CESU-8 string
(lit_utf8_size_t) buf_size);
} /* jerry_is_valid_cesu8_string */

/**
* If JERRY_VM_EXEC_STOP is defined the callback passed to this function is
* periodically called with the user_p argument. If frequency is greater
* than 1, the callback is only called at every frequency ticks.
*/
void
jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, /**< periodically called user function */
void *user_p, /**< pointer passed to the function */
uint32_t frequency) /**< frequency of the function call */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the unit of frequency?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is described in the documentation. There is no real unit. If the callback could be called N times, it is actually called floor(N/frequency) times

{
#ifdef JERRY_VM_EXEC_STOP
if (frequency == 0)
{
frequency = 1;
}

JERRY_CONTEXT (vm_exec_stop_frequency) = frequency;
JERRY_CONTEXT (vm_exec_stop_counter) = frequency;
JERRY_CONTEXT (vm_exec_stop_user_p) = user_p;
JERRY_CONTEXT (vm_exec_stop_cb) = stop_cb;
#else /* !JERRY_VM_EXEC_STOP */
JERRY_UNUSED (stop_cb);
JERRY_UNUSED (user_p);
JERRY_UNUSED (frequency);
#endif /* JERRY_VM_EXEC_STOP */
} /* jerry_set_vm_exec_stop_callback */

/**
* @}
*/
19 changes: 19 additions & 0 deletions jerry-core/jerryscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ typedef enum
JERRY_FEATURE_SNAPSHOT_SAVE, /**< saving snapshot files */
JERRY_FEATURE_SNAPSHOT_EXEC, /**< executing snapshot files */
JERRY_FEATURE_DEBUGGER, /**< debugging */
JERRY_FEATURE_VM_EXEC_STOP, /**< stopping ECMAScript execution */
JERRY_FEATURE__COUNT /**< number of features. NOTE: must be at the end of the list */
} jerry_feature_t;

Expand Down Expand Up @@ -174,6 +175,19 @@ typedef void (*jerry_object_free_callback_t) (const uintptr_t native_p);
*/
typedef void (*jerry_object_native_free_callback_t) (void *native_p);

/**
* Callback which tells whether the ECMAScript execution should be stopped.
*
* As long as the function returns with undefined the execution continues.
* When a non-undefined value is returned the execution stops and the value
* is thrown by the engine (if the error flag is not set for the returned
* value the engine automatically sets it).
*
* Note: if the function returns with a non-undefined value it
* must return with the same value for future calls.
*/
typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p);

/**
* Function type applied for each data property of an object.
*/
Expand Down Expand Up @@ -388,6 +402,11 @@ jerry_value_t jerry_exec_snapshot (const uint32_t *snapshot_p, size_t snapshot_s
size_t jerry_parse_and_save_literals (const jerry_char_t *source_p, size_t source_size, bool is_strict,
uint32_t *buffer_p, size_t buffer_size, bool is_c_format);

/**
* Miscellaneous functions.
*/
void jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, void *user_p, uint32_t frequency);

/**
* @}
*/
Expand Down
50 changes: 50 additions & 0 deletions jerry-core/vm/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,29 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */

if (opcode_data & VM_OC_BACKWARD_BRANCH)
{
#ifdef JERRY_VM_EXEC_STOP
if (JERRY_CONTEXT (vm_exec_stop_cb) != NULL
&& --JERRY_CONTEXT (vm_exec_stop_counter) == 0)
{
result = JERRY_CONTEXT (vm_exec_stop_cb) (JERRY_CONTEXT (vm_exec_stop_user_p));

if (ecma_is_value_undefined (result))
{
JERRY_CONTEXT (vm_exec_stop_counter) = JERRY_CONTEXT (vm_exec_stop_frequency);
}
else
{
JERRY_CONTEXT (vm_exec_stop_counter) = 1;

if (!ECMA_IS_VALUE_ERROR (result))
{
result = ecma_make_error_value (result);
}
goto error;
}
}
#endif /* JERRY_VM_EXEC_STOP */

branch_offset = -branch_offset;
}
}
Expand Down Expand Up @@ -2595,6 +2618,33 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */
JERRY_ASSERT (VM_GET_CONTEXT_TYPE (stack_top_p[-1]) == VM_CONTEXT_FINALLY_THROW);
stack_top_p[-2] = result;
}

#ifdef JERRY_VM_EXEC_STOP
if (JERRY_CONTEXT (vm_exec_stop_cb) != NULL
&& --JERRY_CONTEXT (vm_exec_stop_counter) == 0)
{
result = JERRY_CONTEXT (vm_exec_stop_cb) (JERRY_CONTEXT (vm_exec_stop_user_p));

if (ecma_is_value_undefined (result))
{
JERRY_CONTEXT (vm_exec_stop_counter) = JERRY_CONTEXT (vm_exec_stop_frequency);
}
else
{
JERRY_CONTEXT (vm_exec_stop_counter) = 1;

left_value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED);
right_value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED);

if (!ECMA_IS_VALUE_ERROR (result))
{
result = ecma_make_error_value (result);
}
goto error;
}
}
#endif /* JERRY_VM_EXEC_STOP */

continue;
}
}
Expand Down
59 changes: 59 additions & 0 deletions tests/unit-core/test-api.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,21 @@ handler_construct (const jerry_value_t func_obj_val, /**< function object */
return jerry_create_boolean (true);
} /* handler_construct */

static jerry_value_t
vm_exec_stop_callback (void *user_p)
{
int *int_p = (int *) user_p;

if (*int_p > 0)
{
(*int_p)--;

return jerry_create_undefined ();
}

return jerry_create_string ((const jerry_char_t *) "Abort script");
} /* vm_exec_stop_callback */

/**
* Extended Magic Strings
*/
Expand Down Expand Up @@ -1016,6 +1031,50 @@ main (void)
jerry_cleanup ();
}

/* Test stopping an infinite loop. */
if (jerry_is_feature_enabled (JERRY_FEATURE_VM_EXEC_STOP))
{
jerry_init (JERRY_INIT_EMPTY);

int countdown = 6;
jerry_set_vm_exec_stop_callback (vm_exec_stop_callback, &countdown, 16);

const char *inf_loop_code_src_p = "while(true) {}";
parsed_code_val = jerry_parse ((jerry_char_t *) inf_loop_code_src_p, strlen (inf_loop_code_src_p), false);
TEST_ASSERT (!jerry_value_has_error_flag (parsed_code_val));
res = jerry_run (parsed_code_val);
TEST_ASSERT (countdown == 0);

TEST_ASSERT (jerry_value_has_error_flag (res));

jerry_release_value (res);
jerry_release_value (parsed_code_val);

/* A more complex example. Although the callback error is captured
* by the catch block, it is automatically thrown again. */

/* We keep the callback function, only the countdown is reset. */
countdown = 6;

inf_loop_code_src_p = ("function f() { while (true) ; }\n"
"try { f(); } catch(e) {}");

parsed_code_val = jerry_parse ((jerry_char_t *) inf_loop_code_src_p, strlen (inf_loop_code_src_p), false);

TEST_ASSERT (!jerry_value_has_error_flag (parsed_code_val));
res = jerry_run (parsed_code_val);
TEST_ASSERT (countdown == 0);

/* The result must have an error flag which proves that
* the error is thrown again inside the catch block. */
TEST_ASSERT (jerry_value_has_error_flag (res));

jerry_release_value (res);
jerry_release_value (parsed_code_val);

jerry_cleanup ();
}

/* External Magic String */
jerry_init (JERRY_INIT_SHOW_OPCODES);

Expand Down
3 changes: 3 additions & 0 deletions tools/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ def devhelp(helpstring):
help='build unittests')
parser.add_argument('-v', '--verbose', action='store_const', const='ON', default='OFF',
help='increase verbosity')
parser.add_argument('--vm-exec-stop', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper,
help='enable VM execution stopping (%(choices)s; default: %(default)s)')

devgroup = parser.add_argument_group('developer options')
devgroup.add_argument('--link-map', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper,
Expand Down Expand Up @@ -161,6 +163,7 @@ def generate_build_options(arguments):
build_options.append('-DFEATURE_SYSTEM_ALLOCATOR=%s' % arguments.system_allocator)
build_options.append('-DENABLE_STATIC_LINK=%s' % arguments.static_link)
build_options.append('-DENABLE_STRIP=%s' % arguments.strip)
build_options.append('-DFEATURE_VM_EXEC_STOP=%s' % arguments.vm_exec_stop)

if arguments.toolchain:
build_options.append('-DCMAKE_TOOLCHAIN_FILE=%s' % arguments.toolchain)
Expand Down
Loading