Skip to content

Commit cf07c50

Browse files
committed
Support ECMAScript stopping in JerryScript.
JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg [email protected]
1 parent c9dac6a commit cf07c50

File tree

9 files changed

+279
-2
lines changed

9 files changed

+279
-2
lines changed

docs/02.API-REFERENCE.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Possible compile time enabled feature types:
3636
- JERRY_FEATURE_SNAPSHOT_SAVE - saving snapshot files
3737
- JERRY_FEATURE_SNAPSHOT_EXEC - executing snapshot files
3838
- JERRY_FEATURE_DEBUGGER - debugging
39+
- JERRY_FEATURE_VM_EXEC_STOP - stopping ECMAScript execution
3940

4041
## jerry_char_t
4142

@@ -234,6 +235,28 @@ typedef bool (*jerry_object_property_foreach_t) (const jerry_value_t property_na
234235
void *user_data_p);
235236
```
236237

238+
## jerry_vm_exec_stop_callback_t
239+
240+
**Summary**
241+
242+
Callback which tells whether the ECMAScript execution should be stopped.
243+
If it returns with undefined value the ECMAScript execution continues.
244+
Otherwise the result is thrown by the engine (if the error flag is not
245+
set for the returned value the engine automatically sets it). The
246+
callback function might be called again even if it threw an error.
247+
In this case the function must throw the same error again.
248+
249+
**Prototype**
250+
251+
```c
252+
typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p);
253+
```
254+
255+
**See also**
256+
257+
- [jerry_set_vm_exec_stop_callback](#jerry_set_vm_exec_stop_callback)
258+
259+
237260
# General engine functions
238261

239262
## jerry_init
@@ -3896,3 +3919,81 @@ jerry_parse_and_save_literals (const jerry_char_t *source_p,
38963919
- [jerry_init](#jerry_init)
38973920
- [jerry_cleanup](#jerry_cleanup)
38983921
- [jerry_register_magic_strings](#jerry_register_magic_strings)
3922+
3923+
3924+
# Miscellaneous functions
3925+
3926+
## jerry_set_vm_exec_stop_callback
3927+
3928+
**Summary**
3929+
3930+
When JERRY_FEATURE_VM_EXEC_STOP is enabled a callback function can be
3931+
specified by this function. This callback is periodically called when
3932+
JerryScript executes an ECMAScript program.
3933+
3934+
If the callback returns with undefined value the ECMAScript execution
3935+
continues. Otherwise the result is thrown by the engine (if the error
3936+
flag is not set for the returned value the engine automatically sets
3937+
it). The callback function might be called again even if it threw
3938+
an error. In this case the function must throw the same error again.
3939+
3940+
To reduce the CPU overhead of constantly checking the termination
3941+
condition the callback is called when a backward jump is executed
3942+
or an exception is caught. Setting the `frequency` to a greater
3943+
than `1` value reduces this overhead further. If its value is N
3944+
only every Nth event (backward jump, etc.) trigger the next check.
3945+
3946+
3947+
**Prototype**
3948+
3949+
```c
3950+
void
3951+
jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb,
3952+
void *user_p,
3953+
uint32_t frequency);
3954+
```
3955+
3956+
- `stop_cb` - periodically called callback (passing NULL disables this feature)
3957+
- `user_p` - user pointer passed to the `stop_cb` function
3958+
- `frequency` - frequency of calling the `stop_cb` function
3959+
3960+
**Example**
3961+
3962+
```c
3963+
static jerry_value_t
3964+
vm_exec_stop_callback (void *user_p)
3965+
{
3966+
static int countdown = 10;
3967+
3968+
while (countdown > 0)
3969+
{
3970+
countdown--;
3971+
return jerry_create_undefined ();
3972+
}
3973+
3974+
// The error flag is added automatically.
3975+
return jerry_create_string ((const jerry_char_t *) "Abort script");
3976+
}
3977+
3978+
{
3979+
jerry_init (JERRY_INIT_EMPTY);
3980+
3981+
jerry_set_vm_exec_stop_callback (vm_exec_stop_callback, &countdown, 16);
3982+
3983+
// Inifinte loop.
3984+
const char *src_p = "while(true) {}";
3985+
3986+
jerry_value_t src = jerry_parse ((jerry_char_t *) src_p, strlen (src_p), false);
3987+
jerry_release_value (jerry_run (src));
3988+
jerry_release_value (src);
3989+
jerry_cleanup ();
3990+
}
3991+
```
3992+
3993+
**See also**
3994+
3995+
- [jerry_init](#jerry_init)
3996+
- [jerry_cleanup](#jerry_cleanup)
3997+
- [jerry_parse](#jerry_parse)
3998+
- [jerry_run](#jerry_run)
3999+
- [jerry_vm_exec_stop_callback_t](#jerry_vm_exec_stop_callback_t)

jerry-core/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ set(FEATURE_SNAPSHOT_SAVE OFF CACHE BOOL "Enable saving snapshot files?
3232
set(FEATURE_SYSTEM_ALLOCATOR OFF CACHE BOOL "Enable system allocator?")
3333
set(FEATURE_VALGRIND OFF CACHE BOOL "Enable Valgrind support?")
3434
set(FEATURE_VALGRIND_FREYA OFF CACHE BOOL "Enable Valgrind-Freya support?")
35+
set(FEATURE_VM_EXEC_STOP OFF CACHE BOOL "Enable VM execution stopping?")
3536
set(MEM_HEAP_SIZE_KB "512" CACHE STRING "Size of memory heap, in kilobytes")
3637

3738
if(FEATURE_SYSTEM_ALLOCATOR)
@@ -54,6 +55,7 @@ message(STATUS "FEATURE_SNAPSHOT_SAVE " ${FEATURE_SNAPSHOT_SAVE})
5455
message(STATUS "FEATURE_SYSTEM_ALLOCATOR " ${FEATURE_SYSTEM_ALLOCATOR})
5556
message(STATUS "FEATURE_VALGRIND " ${FEATURE_VALGRIND})
5657
message(STATUS "FEATURE_VALGRIND_FREYA " ${FEATURE_VALGRIND_FREYA})
58+
message(STATUS "FEATURE_VM_EXEC_STOP " ${FEATURE_VM_EXEC_STOP})
5759
message(STATUS "MEM_HEAP_SIZE_KB " ${MEM_HEAP_SIZE_KB})
5860

5961
# Include directories
@@ -238,6 +240,11 @@ if(FEATURE_VALGRIND_FREYA)
238240
set(INCLUDE_CORE ${INCLUDE_CORE} ${INCLUDE_THIRD_PARTY_VALGRIND})
239241
endif()
240242

243+
# Enable VM execution stopping
244+
if (FEATURE_VM_EXEC_STOP)
245+
set(DEFINES_JERRY ${DEFINES_JERRY} JERRY_VM_EXEC_STOP)
246+
endif()
247+
241248
# Size of heap
242249
math(EXPR MEM_HEAP_AREA_SIZE "${MEM_HEAP_SIZE_KB} * 1024")
243250
set(DEFINES_JERRY ${DEFINES_JERRY} CONFIG_MEM_HEAP_AREA_SIZE=${MEM_HEAP_AREA_SIZE})

jerry-core/jcontext/jcontext.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ typedef struct
8484
uint8_t re_cache_idx; /**< evicted item index when regex cache is full (round-robin) */
8585
#endif /* !CONFIG_DISABLE_REGEXP_BUILTIN */
8686

87+
#ifdef JERRY_VM_EXEC_STOP
88+
uint32_t vm_exec_stop_frequency; /**< reset value for vm_exec_stop_counter */
89+
uint32_t vm_exec_stop_counter; /**< down counter for reducing the calls of vm_exec_stop_cb */
90+
void *vm_exec_stop_user_p; /**< user pointer for vm_exec_stop_cb */
91+
jerry_vm_exec_stop_callback_t vm_exec_stop_cb; /**< user function which returns whether the
92+
* ECMAScript execution should be stopped */
93+
#endif /* JERRY_VM_EXEC_STOP */
94+
8795
#ifdef JERRY_DEBUGGER
8896
uint8_t debugger_send_buffer[JERRY_DEBUGGER_MAX_BUFFER_SIZE]; /**< buffer for sending messages */
8997
uint8_t debugger_receive_buffer[JERRY_DEBUGGER_MAX_BUFFER_SIZE]; /**< buffer for receiving messages */

jerry-core/jerry.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,9 @@ bool jerry_is_feature_enabled (const jerry_feature_t feature)
618618
#ifdef JERRY_DEBUGGER
619619
|| feature == JERRY_FEATURE_DEBUGGER
620620
#endif /* JERRY_DEBUGGER */
621+
#ifdef JERRY_VM_EXEC_STOP
622+
|| feature == JERRY_FEATURE_VM_EXEC_STOP
623+
#endif /* JERRY_VM_EXEC_STOP */
621624
);
622625
} /* jerry_is_feature_enabled */
623626

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

2151+
/**
2152+
* If JERRY_VM_EXEC_STOP is defined the callback passed to this function is
2153+
* periodically called with the user_p argument. If frequency is greater
2154+
* than 1, the callback is only called at every frequency ticks.
2155+
*/
2156+
void
2157+
jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, /**< periodically called user function */
2158+
void *user_p, /**< pointer passed to the function */
2159+
uint32_t frequency) /**< frequency of the function call */
2160+
{
2161+
#ifdef JERRY_VM_EXEC_STOP
2162+
if (frequency == 0)
2163+
{
2164+
frequency = 1;
2165+
}
2166+
2167+
JERRY_CONTEXT (vm_exec_stop_frequency) = frequency;
2168+
JERRY_CONTEXT (vm_exec_stop_counter) = frequency;
2169+
JERRY_CONTEXT (vm_exec_stop_user_p) = user_p;
2170+
JERRY_CONTEXT (vm_exec_stop_cb) = stop_cb;
2171+
#else /* !JERRY_VM_EXEC_STOP */
2172+
JERRY_UNUSED (stop_cb);
2173+
JERRY_UNUSED (user_p);
2174+
JERRY_UNUSED (frequency);
2175+
#endif /* JERRY_VM_EXEC_STOP */
2176+
} /* jerry_set_vm_exec_stop_callback */
2177+
21482178
/**
21492179
* @}
21502180
*/

jerry-core/jerryscript.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ typedef enum
8686
JERRY_FEATURE_SNAPSHOT_SAVE, /**< saving snapshot files */
8787
JERRY_FEATURE_SNAPSHOT_EXEC, /**< executing snapshot files */
8888
JERRY_FEATURE_DEBUGGER, /**< debugging */
89+
JERRY_FEATURE_VM_EXEC_STOP, /**< stopping ECMAScript execution */
8990
JERRY_FEATURE__COUNT /**< number of features. NOTE: must be at the end of the list */
9091
} jerry_feature_t;
9192

@@ -174,6 +175,19 @@ typedef void (*jerry_object_free_callback_t) (const uintptr_t native_p);
174175
*/
175176
typedef void (*jerry_object_native_free_callback_t) (void *native_p);
176177

178+
/**
179+
* Callback which tells whether the ECMAScript execution should be stopped.
180+
*
181+
* As long as the function returns with undefined the execution continues.
182+
* When a non-undefined value is returned the execution stops and the value
183+
* is thrown by the engine (if the error flag is not set for the returned
184+
* value the engine automatically sets it).
185+
*
186+
* Note: if the function returns with a non-undefined value it
187+
* must return with the same value for future calls.
188+
*/
189+
typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p);
190+
177191
/**
178192
* Function type applied for each data property of an object.
179193
*/
@@ -388,6 +402,11 @@ jerry_value_t jerry_exec_snapshot (const uint32_t *snapshot_p, size_t snapshot_s
388402
size_t jerry_parse_and_save_literals (const jerry_char_t *source_p, size_t source_size, bool is_strict,
389403
uint32_t *buffer_p, size_t buffer_size, bool is_c_format);
390404

405+
/**
406+
* Miscellaneous functions.
407+
*/
408+
void jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, void *user_p, uint32_t frequency);
409+
391410
/**
392411
* @}
393412
*/

jerry-core/vm/vm.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,29 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */
847847

848848
if (opcode_data & VM_OC_BACKWARD_BRANCH)
849849
{
850+
#ifdef JERRY_VM_EXEC_STOP
851+
if (JERRY_CONTEXT (vm_exec_stop_cb) != NULL
852+
&& --JERRY_CONTEXT (vm_exec_stop_counter) == 0)
853+
{
854+
result = JERRY_CONTEXT (vm_exec_stop_cb) (JERRY_CONTEXT (vm_exec_stop_user_p));
855+
856+
if (ecma_is_value_undefined (result))
857+
{
858+
JERRY_CONTEXT (vm_exec_stop_counter) = JERRY_CONTEXT (vm_exec_stop_frequency);
859+
}
860+
else
861+
{
862+
JERRY_CONTEXT (vm_exec_stop_counter) = 1;
863+
864+
if (!ECMA_IS_VALUE_ERROR (result))
865+
{
866+
result = ecma_make_error_value (result);
867+
}
868+
goto error;
869+
}
870+
}
871+
#endif /* JERRY_VM_EXEC_STOP */
872+
850873
branch_offset = -branch_offset;
851874
}
852875
}
@@ -2595,6 +2618,33 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */
25952618
JERRY_ASSERT (VM_GET_CONTEXT_TYPE (stack_top_p[-1]) == VM_CONTEXT_FINALLY_THROW);
25962619
stack_top_p[-2] = result;
25972620
}
2621+
2622+
#ifdef JERRY_VM_EXEC_STOP
2623+
if (JERRY_CONTEXT (vm_exec_stop_cb) != NULL
2624+
&& --JERRY_CONTEXT (vm_exec_stop_counter) == 0)
2625+
{
2626+
result = JERRY_CONTEXT (vm_exec_stop_cb) (JERRY_CONTEXT (vm_exec_stop_user_p));
2627+
2628+
if (ecma_is_value_undefined (result))
2629+
{
2630+
JERRY_CONTEXT (vm_exec_stop_counter) = JERRY_CONTEXT (vm_exec_stop_frequency);
2631+
}
2632+
else
2633+
{
2634+
JERRY_CONTEXT (vm_exec_stop_counter) = 1;
2635+
2636+
left_value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED);
2637+
right_value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED);
2638+
2639+
if (!ECMA_IS_VALUE_ERROR (result))
2640+
{
2641+
result = ecma_make_error_value (result);
2642+
}
2643+
goto error;
2644+
}
2645+
}
2646+
#endif /* JERRY_VM_EXEC_STOP */
2647+
25982648
continue;
25992649
}
26002650
}

tests/unit-core/test-api.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,21 @@ handler_construct (const jerry_value_t func_obj_val, /**< function object */
170170
return jerry_create_boolean (true);
171171
} /* handler_construct */
172172

173+
static jerry_value_t
174+
vm_exec_stop_callback (void *user_p)
175+
{
176+
int *int_p = (int *) user_p;
177+
178+
if (*int_p > 0)
179+
{
180+
(*int_p)--;
181+
182+
return jerry_create_undefined ();
183+
}
184+
185+
return jerry_create_string ((const jerry_char_t *) "Abort script");
186+
} /* vm_exec_stop_callback */
187+
173188
/**
174189
* Extended Magic Strings
175190
*/
@@ -1007,6 +1022,50 @@ main (void)
10071022
jerry_cleanup ();
10081023
}
10091024

1025+
/* Test stopping an infinite loop. */
1026+
if (jerry_is_feature_enabled (JERRY_FEATURE_VM_EXEC_STOP))
1027+
{
1028+
jerry_init (JERRY_INIT_EMPTY);
1029+
1030+
int countdown = 6;
1031+
jerry_set_vm_exec_stop_callback (vm_exec_stop_callback, &countdown, 16);
1032+
1033+
const char *inf_loop_code_src_p = "while(true) {}";
1034+
parsed_code_val = jerry_parse ((jerry_char_t *) inf_loop_code_src_p, strlen (inf_loop_code_src_p), false);
1035+
TEST_ASSERT (!jerry_value_has_error_flag (parsed_code_val));
1036+
res = jerry_run (parsed_code_val);
1037+
TEST_ASSERT (countdown == 0);
1038+
1039+
TEST_ASSERT (jerry_value_has_error_flag (res));
1040+
1041+
jerry_release_value (res);
1042+
jerry_release_value (parsed_code_val);
1043+
1044+
/* A more complex example. Although the callback error is captured
1045+
* by the catch block, it is automatically thrown again. */
1046+
1047+
/* We keep the callback function, only the countdown is reset. */
1048+
countdown = 6;
1049+
1050+
inf_loop_code_src_p = ("function f() { while (true) ; }\n"
1051+
"try { f(); } catch(e) {}");
1052+
1053+
parsed_code_val = jerry_parse ((jerry_char_t *) inf_loop_code_src_p, strlen (inf_loop_code_src_p), false);
1054+
1055+
TEST_ASSERT (!jerry_value_has_error_flag (parsed_code_val));
1056+
res = jerry_run (parsed_code_val);
1057+
TEST_ASSERT (countdown == 0);
1058+
1059+
/* The result must have an error flag which proves that
1060+
* the error is thrown again inside the catch block. */
1061+
TEST_ASSERT (jerry_value_has_error_flag (res));
1062+
1063+
jerry_release_value (res);
1064+
jerry_release_value (parsed_code_val);
1065+
1066+
jerry_cleanup ();
1067+
}
1068+
10101069
/* External Magic String */
10111070
jerry_init (JERRY_INIT_SHOW_OPCODES);
10121071

tools/build.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def devhelp(helpstring):
108108
help='build unittests')
109109
parser.add_argument('-v', '--verbose', action='store_const', const='ON', default='OFF',
110110
help='increase verbosity')
111+
parser.add_argument('--vm-exec-stop', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper,
112+
help='enable VM execution stopping (%(choices)s; default: %(default)s)')
111113

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

165168
if arguments.toolchain:
166169
build_options.append('-DCMAKE_TOOLCHAIN_FILE=%s' % arguments.toolchain)

0 commit comments

Comments
 (0)