From d4178ae3861fe32c8b350470facd41c8e04171d0 Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Sat, 24 Jul 2021 09:26:46 +0200 Subject: [PATCH] Support dynamic import calls (#4652) JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- docs/02.API-REFERENCE.md | 168 +++++++++++ jerry-core/api/jerry.c | 19 ++ jerry-core/ecma/base/ecma-module.c | 76 +++++ jerry-core/ecma/base/ecma-module.h | 1 + jerry-core/include/jerryscript-core.h | 1 + jerry-core/include/jerryscript-snapshot.h | 2 +- jerry-core/include/jerryscript-types.h | 7 + jerry-core/jcontext/jcontext.h | 2 + jerry-core/parser/js/byte-code.c | 2 +- jerry-core/parser/js/byte-code.h | 2 + jerry-core/parser/js/js-parser-expr.c | 32 +++ jerry-core/parser/js/js-parser-statm.c | 12 + jerry-core/parser/js/js-parser-util.c | 5 +- jerry-core/parser/js/js-parser.h | 1 + jerry-core/parser/js/js-scanner.c | 23 +- jerry-core/vm/vm.c | 34 +++ jerry-core/vm/vm.h | 7 + tests/jerry/es.next/module-dynamic-import.js | 28 ++ tests/test262-esnext-excludelist.xml | 110 -------- tests/unit-core/CMakeLists.txt | 1 + tests/unit-core/test-module-dynamic.c | 279 +++++++++++++++++++ 21 files changed, 698 insertions(+), 114 deletions(-) create mode 100644 tests/jerry/es.next/module-dynamic-import.js create mode 100644 tests/unit-core/test-module-dynamic.c diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index eccb74b8e..4a014e096 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -830,6 +830,48 @@ typedef jerry_value_t (*jerry_module_resolve_callback_t) (const jerry_value_t sp - [jerry_module_link](#jerry_module_link) - [jerry_get_global_object](#jerry_get_global_object) +## jerry_module_import_callback_t + +**Summary** + +Callback which is called when an import is resolved dynamically to get the referenced module. + +*Note*: + - If the function returns with a promise, the import call returns with this promise. The + application should try to resolve the requested module later. If the module is evaluated + successfully, the returned promise should be resolved with the namespace object of the + module. Otherwise, the returned promise should be rejected with an error. + - If the function returns with a resolved module, a promise is created and resolved with the + namespace object of the module. The import call returns with the resolved promise. + - If the function returns with an error, a promise is created and rejected with the + return error. The import call returns with the rejected promise. + - All other return values are considered invalid. In this case the import call returns + with a rejected promise. The rejected promise has a fixed error message, it does not + specify the reason of the fail. + - If realms are enabled, the returned module should be created in the current realm + (see: [jerry_get_global_object](#jerry_get_global_object)) + +**Prototype** + +```c +typedef jerry_value_t (*jerry_module_import_callback_t) (const jerry_value_t specifier, + const jerry_value_t user_value, + void *user_p); +``` + +- `specifier` - a module specifier string (usually used as a path to the module) +- `user_value` - the user value assigned to the script (see [jerry_parse_options_t](#jerry_parse_options_t)) +- `user_p` - pointer passed to [jerry_module_set_import_callback](#jerry_module_set_import_callback). +- return value + - promise or resolved module - if the operation is successful + - an error - otherwise + +*New in version [[NEXT_RELEASE]]*. + +**See also** +- [jerry_module_set_import_callback](#jerry_module_set_import_callback) +- [jerry_get_global_object](#jerry_get_global_object) + ## jerry_module_state_changed_callback_t **Summary** @@ -5018,6 +5060,132 @@ main (void) - [jerry_module_link](#jerry_module_link) - [jerry_module_evaluate](#jerry_module_evaluate) +## jerry_module_set_import_callback + +Sets the callback which is called when dynamic imports are resolved. The resolver +receives the `user_value` assigned to the currently executed script, which should +provide all the information that is necessary for the resolve. + +*Notes*: +- This API depends on a build option (`JERRY_MODULE_SYSTEM`) and can be checked + in runtime with the `JERRY_FEATURE_MODULE` feature enum value, + see: [jerry_is_feature_enabled](#jerry_is_feature_enabled). +- The possible return values of the callback is explained + in [jerry_module_import_callback_t](#jerry_module_import_callback_t) + +**Prototype** + +```c +void +jerry_module_set_import_callback (jerry_module_import_callback_t callback_p, + void *user_p) +``` + +- `callback_p` - a [jerry_module_import_callback_t](#jerry_module_import_callback_t) callback which handles `import()` calls +- `user_p` - user pointer passed to the callback + +*New in version [[NEXT_RELEASE]]*. + +**Example** + +[doctest]: # (test="compile") + +```c +#include +#include + +typedef struct { + jerry_value_t specifier; + jerry_value_t user_value; + jerry_value_t promise; +} resolve_module_task_t; + +static jerry_value_t +resolve_dynamic (const jerry_value_t specifier, /**< module specifier */ + const jerry_value_t user_value, /**< user value assigned to the script */ + void *user_p) /**< user data */ +{ + /* If the specified module has already been evaluated, this callback can + * return with it and the promise creation is automatically done by the engine. + * Otherwise the application usually adds a resolve task to a command queue. */ + + /* This very simple command queue supports only one task. */ + resolve_module_task_t *task_p = (resolve_module_task_t *) user_p; + task_p->specifier = jerry_acquire_value (specifier); + task_p->user_value = jerry_acquire_value (user_value); + + /* This Promise should be evaluated later. */ + jerry_value_t promise = jerry_create_promise (); + task_p->promise = jerry_acquire_value (promise); + return promise; +} + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + resolve_module_task_t task; + jerry_module_set_import_callback (resolve_dynamic, &task); + + const jerry_char_t script[] = "import('modules/my_module.mjs').then(\n" + " function (namespace) { /* use namespace */},\n" + " function (error) { /* handle error */}\n" + ")"; + const jerry_char_t resource[] = "dir/my_script.js"; + + jerry_parse_options_t parse_options; + parse_options.options = JERRY_PARSE_HAS_RESOURCE | JERRY_PARSE_HAS_USER_VALUE; + + /* Resource is usually used for debugging purposes, e.g. for generating backtrace. */ + parse_options.resource_name_p = resource; + parse_options.resource_name_length = sizeof (resource) - 1; + + /* User value should provide information for resolving dynamic imports. + * In this case it contains the full path excluding the filename. */ + parse_options.user_value = jerry_create_string ((const jerry_char_t *) "/home/user/dir"); + + jerry_value_t script_value = jerry_parse (script, sizeof (script) - 1, &parse_options); + jerry_release_value (parse_options.user_value); + jerry_release_value (jerry_run (script_value)); + jerry_release_value (script_value); + + /* The application resolves both the module and the promise using the specifier + * and the user_value. In this example the specifier is modules/my_module.mjs. */ + const jerry_char_t module_script[] = "export var a = 5"; + const jerry_char_t module_resource[] = "modules/my_module.mjs"; + + parse_options.options = JERRY_PARSE_MODULE | JERRY_PARSE_HAS_RESOURCE | JERRY_PARSE_HAS_USER_VALUE; + parse_options.resource_name_p = module_resource; + parse_options.resource_name_length = sizeof (module_resource) - 1; + parse_options.user_value = jerry_create_string ((const jerry_char_t *) "/home/user/dir/modules"); + + jerry_value_t module_value = jerry_parse (module_script, sizeof (module_script) - 1, &parse_options); + jerry_release_value (parse_options.user_value); + jerry_release_value (jerry_module_link (module_value, NULL, NULL)); + jerry_release_value (jerry_module_evaluate (module_value)); + + /* The promise must be resolved with the namespace object, not the module. */ + jerry_value_t namespace_value = jerry_module_get_namespace (module_value); + jerry_release_value (jerry_resolve_or_reject_promise (task.promise, namespace_value, true)); + + jerry_release_value (namespace_value); + jerry_release_value (module_value); + jerry_release_value (task.specifier); + jerry_release_value (task.user_value); + jerry_release_value (task.promise); + + /* Process promise handlers. */ + jerry_release_value (jerry_run_all_enqueued_jobs ()); + + jerry_cleanup (); + return 0; +} +``` + +**See also** +- [jerry_module_import_callback_t](#jerry_module_import_callback_t) + ## jerry_native_module_create Creates a native module with a list of exports. The initial state of the module is linked. diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 2422267a6..bbf85df4e 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -869,6 +869,25 @@ jerry_module_get_namespace (const jerry_value_t module_val) /**< module */ #endif /* JERRY_MODULE_SYSTEM */ } /* jerry_module_get_namespace */ +/** + * Sets the callback which is called when dynamic imports are resolved + */ +void +jerry_module_set_import_callback (jerry_module_import_callback_t callback_p, /**< callback which handles + * dynamic import calls */ + void *user_p) /**< user pointer passed to the callback */ +{ + jerry_assert_api_available (); + +#if JERRY_MODULE_SYSTEM + JERRY_CONTEXT (module_import_callback_p) = callback_p; + JERRY_CONTEXT (module_import_callback_user_p) = user_p; +#else /* !JERRY_MODULE_SYSTEM */ + JERRY_UNUSED (callback_p); + JERRY_UNUSED (user_p); +#endif /* JERRY_MODULE_SYSTEM */ +} /* jerry_module_set_import_callback */ + /** * Creates a native module with a list of exports. The initial state of the module is linked. * diff --git a/jerry-core/ecma/base/ecma-module.c b/jerry-core/ecma/base/ecma-module.c index 03e5db429..a3cf0ed7e 100644 --- a/jerry-core/ecma/base/ecma-module.c +++ b/jerry-core/ecma/base/ecma-module.c @@ -1371,6 +1371,82 @@ error: return ECMA_VALUE_ERROR; } /* ecma_module_link */ +/** + * Compute the result of 'import()' calls + * + * @return promise object representing the result of the operation + */ +ecma_value_t +ecma_module_import (ecma_value_t specifier, /**< module specifier */ + ecma_value_t user_value) /**< user value assigned to the script */ +{ + ecma_string_t *specifier_p = ecma_op_to_string (specifier); + + if (JERRY_UNLIKELY (specifier_p == NULL)) + { + goto error; + } + + if (JERRY_CONTEXT (module_import_callback_p) == NULL) + { + ecma_deref_ecma_string (specifier_p); + goto error_module_instantiate; + } + + jerry_value_t result; + result = JERRY_CONTEXT (module_import_callback_p) (ecma_make_string_value (specifier_p), + user_value, + JERRY_CONTEXT (module_import_callback_user_p)); + ecma_deref_ecma_string (specifier_p); + + if (JERRY_UNLIKELY (ecma_is_value_error_reference (result))) + { + ecma_raise_error_from_error_reference (result); + goto error; + } + + if (ecma_is_value_object (result) + && ecma_is_promise (ecma_get_object_from_value (result))) + { + return result; + } + + ecma_module_t *module_p = ecma_module_get_resolved_module (result); + + if (module_p == NULL) + { + ecma_free_value (result); + goto error_module_instantiate; + } + + if (module_p->header.u.cls.u1.module_state != JERRY_MODULE_STATE_EVALUATED) + { + ecma_deref_object (&module_p->header.object); + goto error_module_instantiate; + } + + result = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_VALUE_UNDEFINED, NULL); + ecma_fulfill_promise (result, ecma_make_object_value (module_p->namespace_object_p)); + ecma_deref_object (&module_p->header.object); + return result; + +error_module_instantiate: + ecma_raise_range_error (ECMA_ERR_MSG ("Module cannot be instantiated")); + +error: + if (jcontext_has_pending_abort ()) + { + return ECMA_VALUE_ERROR; + } + + ecma_value_t exception = jcontext_take_exception (); + + ecma_value_t promise = ecma_op_create_promise_object (ECMA_VALUE_EMPTY, ECMA_VALUE_UNDEFINED, NULL); + ecma_reject_promise (promise, exception); + ecma_free_value (exception); + return promise; +} /* ecma_module_import */ + /** * Cleans up and releases a module structure including all referenced modules. */ diff --git a/jerry-core/ecma/base/ecma-module.h b/jerry-core/ecma/base/ecma-module.h index 5390dfd4d..1a590e291 100644 --- a/jerry-core/ecma/base/ecma-module.h +++ b/jerry-core/ecma/base/ecma-module.h @@ -120,6 +120,7 @@ ecma_value_t ecma_module_link (ecma_module_t *module_p, jerry_module_resolve_callback_t callback_p, void *user_p); ecma_value_t ecma_module_evaluate (ecma_module_t *module_p); +ecma_value_t ecma_module_import (ecma_value_t specifier, ecma_value_t user_value); ecma_module_t *ecma_module_create (void); void ecma_module_cleanup_context (void); diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index f59663004..386dd5a00 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -279,6 +279,7 @@ void jerry_module_set_state_changed_callback (jerry_module_state_changed_callbac size_t jerry_module_get_number_of_requests (const jerry_value_t module_val); jerry_value_t jerry_module_get_request (const jerry_value_t module_val, size_t request_index); jerry_value_t jerry_module_get_namespace (const jerry_value_t module_val); +void jerry_module_set_import_callback (jerry_module_import_callback_t callback_p, void *user_p); jerry_value_t jerry_native_module_create (jerry_native_module_evaluate_callback_t callback, const jerry_value_t * const exports_p, size_t number_of_exports); diff --git a/jerry-core/include/jerryscript-snapshot.h b/jerry-core/include/jerryscript-snapshot.h index 9546c9457..eb395627a 100644 --- a/jerry-core/include/jerryscript-snapshot.h +++ b/jerry-core/include/jerryscript-snapshot.h @@ -30,7 +30,7 @@ extern "C" /** * Jerry snapshot format version. */ -#define JERRY_SNAPSHOT_VERSION (66u) +#define JERRY_SNAPSHOT_VERSION (67u) /** * Flags for jerry_generate_snapshot and jerry_generate_function_snapshot. diff --git a/jerry-core/include/jerryscript-types.h b/jerry-core/include/jerryscript-types.h index 4f153e61a..df070a966 100644 --- a/jerry-core/include/jerryscript-types.h +++ b/jerry-core/include/jerryscript-types.h @@ -555,6 +555,13 @@ typedef jerry_value_t (*jerry_module_resolve_callback_t) (const jerry_value_t sp const jerry_value_t referrer, void *user_p); +/** + * Callback which is called when an import is resolved dynamically to get the referenced module. + */ +typedef jerry_value_t (*jerry_module_import_callback_t) (const jerry_value_t specifier, + const jerry_value_t user_value, + void *user_p); + /** * Callback which is called after the module enters into linked, evaluated or error state. */ diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index de702ca44..3eaf64971 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -152,6 +152,8 @@ struct jerry_context_t jerry_module_state_changed_callback_t module_state_changed_callback_p; /**< callback which is called after the * state of a module is changed */ void *module_state_changed_callback_user_p; /**< user pointer for module_state_changed_callback_p */ + jerry_module_import_callback_t module_import_callback_p; /**< callback for dynamic module import */ + void *module_import_callback_user_p; /**< user pointer for module_import_callback_p */ #endif /* JERRY_MODULE_SYSTEM */ vm_frame_ctx_t *vm_top_context_p; /**< top (current) interpreter context */ diff --git a/jerry-core/parser/js/byte-code.c b/jerry-core/parser/js/byte-code.c index a8d969ca1..ecd943665 100644 --- a/jerry-core/parser/js/byte-code.c +++ b/jerry-core/parser/js/byte-code.c @@ -31,7 +31,7 @@ JERRY_STATIC_ASSERT (offsetof (cbc_uint8_arguments_t, script_value) == offsetof */ JERRY_STATIC_ASSERT (CBC_END == 238, number_of_cbc_opcodes_changed); -JERRY_STATIC_ASSERT (CBC_EXT_END == 146, +JERRY_STATIC_ASSERT (CBC_EXT_END == 147, number_of_cbc_ext_opcodes_changed); #if JERRY_PARSER || JERRY_PARSER_DUMP_BYTE_CODE diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index ce7b5080b..7f3e7ffdc 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -598,6 +598,8 @@ VM_OC_COPY_FROM_ARG) \ CBC_OPCODE (CBC_EXT_PUSH_REST_OBJECT, CBC_NO_FLAG, 1, \ VM_OC_PUSH_REST_OBJECT) \ + CBC_OPCODE (CBC_EXT_MODULE_IMPORT, CBC_NO_FLAG, 0, \ + VM_OC_MODULE_IMPORT) \ CBC_OPCODE (CBC_EXT_STRING_CONCAT, CBC_NO_FLAG, -1, \ VM_OC_STRING_CONCAT | VM_OC_GET_STACK_STACK | VM_OC_PUT_STACK) \ CBC_OPCODE (CBC_EXT_STRING_CONCAT_RIGHT_LITERAL, CBC_HAS_LITERAL_ARG, 0, \ diff --git a/jerry-core/parser/js/js-parser-expr.c b/jerry-core/parser/js/js-parser-expr.c index 81e53a8a9..e6570b835 100644 --- a/jerry-core/parser/js/js-parser-expr.c +++ b/jerry-core/parser/js/js-parser-expr.c @@ -1929,6 +1929,16 @@ parser_parse_unary_expression (parser_context_t *context_p, /**< context */ break; } #endif /* JERRY_ESNEXT */ +#if JERRY_MODULE_SYSTEM + case LEXER_KEYW_IMPORT: + { + if (new_was_seen) + { + parser_raise_error (context_p, PARSER_ERR_IMPORT_AFTER_NEW); + } + break; + } +#endif /* JERRY_MODULE_SYSTEM */ } /* Bracketed expressions are primary expressions. At this @@ -2303,6 +2313,28 @@ parser_parse_unary_expression (parser_context_t *context_p, /**< context */ && context_p->token.type != LEXER_COMMA); } #endif /* JERRY_ESNEXT */ +#if JERRY_MODULE_SYSTEM + case LEXER_KEYW_IMPORT: + { + lexer_next_token (context_p); + + if (context_p->token.type != LEXER_LEFT_PAREN) + { + parser_raise_error (context_p, PARSER_ERR_LEFT_PAREN_EXPECTED); + } + + lexer_next_token (context_p); + parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA); + + if (context_p->token.type != LEXER_RIGHT_PAREN) + { + parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED); + } + + parser_emit_cbc_ext (context_p, CBC_EXT_MODULE_IMPORT); + break; + } +#endif /* JERRY_MODULE_SYSTEM */ default: { bool is_left_hand_side = (*grouping_level_p == PARSE_EXPR_LEFT_HAND_SIDE); diff --git a/jerry-core/parser/js/js-parser-statm.c b/jerry-core/parser/js/js-parser-statm.c index f99081cd9..7e14dbe8d 100644 --- a/jerry-core/parser/js/js-parser-statm.c +++ b/jerry-core/parser/js/js-parser-statm.c @@ -2424,6 +2424,18 @@ parser_parse_import_statement (parser_context_t *context_p) /**< parser context JERRY_ASSERT (context_p->token.type == LEXER_KEYW_IMPORT); JERRY_ASSERT (context_p->module_names_p == NULL); + if (lexer_check_next_character (context_p, LIT_CHAR_LEFT_PAREN)) + { + if (context_p->status_flags & PARSER_IS_FUNCTION) + { + parser_parse_expression_statement (context_p, PARSE_EXPR); + return; + } + + parser_parse_block_expression (context_p, PARSE_EXPR); + return; + } + parser_module_check_request_place (context_p); lexer_next_token (context_p); diff --git a/jerry-core/parser/js/js-parser-util.c b/jerry-core/parser/js/js-parser-util.c index 0a353599f..ce0819cd4 100644 --- a/jerry-core/parser/js/js-parser-util.c +++ b/jerry-core/parser/js/js-parser-util.c @@ -1372,8 +1372,11 @@ parser_error_to_string (parser_error_t error) /**< error code */ { return "Export not defined in module"; } + case PARSER_ERR_IMPORT_AFTER_NEW: + { + return "Module import call is not allowed after new"; + } #endif /* JERRY_MODULE_SYSTEM */ - default: { JERRY_ASSERT (error == PARSER_ERR_NO_ERROR); diff --git a/jerry-core/parser/js/js-parser.h b/jerry-core/parser/js/js-parser.h index ff78834bf..96a030c7a 100644 --- a/jerry-core/parser/js/js-parser.h +++ b/jerry-core/parser/js/js-parser.h @@ -181,6 +181,7 @@ typedef enum PARSER_ERR_DUPLICATED_EXPORT_IDENTIFIER, /**< duplicated export identifier name */ PARSER_ERR_DUPLICATED_IMPORT_BINDING, /**< duplicated import binding name */ PARSER_ERR_EXPORT_NOT_DEFINED, /**< export is not defined in module */ + PARSER_ERR_IMPORT_AFTER_NEW, /**< module import call is not allowed after new */ #endif /* JERRY_MODULE_SYSTEM */ PARSER_ERR_NON_STRICT_ARG_DEFINITION /**< non-strict argument definition */ diff --git a/jerry-core/parser/js/js-scanner.c b/jerry-core/parser/js/js-scanner.c index 9350bcfdb..a7b39912c 100644 --- a/jerry-core/parser/js/js-scanner.c +++ b/jerry-core/parser/js/js-scanner.c @@ -311,6 +311,20 @@ scanner_scan_primary_expression (parser_context_t *context_p, /**< context */ return SCAN_KEEP_TOKEN; } #endif /* JERRY_ESNEXT */ +#if JERRY_MODULE_SYSTEM + case LEXER_KEYW_IMPORT: + { + lexer_next_token (context_p); + + if (context_p->token.type != LEXER_LEFT_PAREN) + { + scanner_raise_error (context_p); + } + + scanner_context_p->mode = SCAN_MODE_POST_PRIMARY_EXPRESSION; + return SCAN_KEEP_TOKEN; + } +#endif /* JERRY_MODULE_SYSTEM */ case LEXER_RIGHT_PAREN: { if (stack_top == SCAN_STACK_PAREN_EXPRESSION) @@ -1681,13 +1695,20 @@ scanner_scan_statement (parser_context_t *context_p, /**< context */ #if JERRY_MODULE_SYSTEM case LEXER_KEYW_IMPORT: { + lexer_next_token (context_p); + + if (context_p->token.type == LEXER_LEFT_PAREN) + { + scanner_context_p->mode = SCAN_MODE_POST_PRIMARY_EXPRESSION; + return SCAN_KEEP_TOKEN; + } + if (stack_top != SCAN_STACK_SCRIPT) { scanner_raise_error (context_p); } scanner_context_p->mode = SCAN_MODE_STATEMENT_END; - lexer_next_token (context_p); if (context_p->token.type == LEXER_LITERAL && context_p->token.lit_location.type == LEXER_STRING_LITERAL) diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index fe0735685..2422c4eb1 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -4554,6 +4554,40 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ JERRY_ASSERT (VM_GET_REGISTERS (frame_ctx_p) + register_end + frame_ctx_p->context_depth == stack_top_p); continue; } +#if JERRY_MODULE_SYSTEM + case VM_OC_MODULE_IMPORT: + { + left_value = *(--stack_top_p); + + ecma_value_t user_value = ECMA_VALUE_UNDEFINED; + ecma_value_t script_value = ((cbc_uint8_arguments_t *) bytecode_header_p)->script_value; + +#if JERRY_SNAPSHOT_EXEC + if (JERRY_UNLIKELY (!(bytecode_header_p->status_flags & CBC_CODE_FLAGS_STATIC_FUNCTION))) + { +#endif /* JERRY_SNAPSHOT_EXEC */ + cbc_script_t *script_p = ECMA_GET_INTERNAL_VALUE_POINTER (cbc_script_t, script_value); + + if (CBC_SCRIPT_GET_TYPE (script_p) != CBC_SCRIPT_GENERIC) + { + user_value = ((cbc_script_user_t *) script_p)->user_value; + } +#if JERRY_SNAPSHOT_EXEC + } +#endif /* JERRY_SNAPSHOT_EXEC */ + + result = ecma_module_import (left_value, user_value); + ecma_free_value (left_value); + + if (ECMA_IS_VALUE_ERROR (result)) + { + goto error; + } + + *stack_top_p++ = result; + continue; + } +#endif /* JERRY_MODULE_SYSTEM */ #if JERRY_DEBUGGER case VM_OC_BREAKPOINT_ENABLED: { diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 72dd7480e..cc239ead9 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -300,6 +300,10 @@ typedef enum VM_OC_PUSH_STATIC_FIELD_FUNC, /**< push static field initializer function */ VM_OC_ADD_COMPUTED_FIELD, /**< add computed field name */ #endif /* JERRY_ESNEXT */ +#if JERRY_MODULE_SYSTEM + VM_OC_MODULE_IMPORT, /**< module dynamic import */ +#endif /* JERRY_MODULE_SYSTEM */ + VM_OC_NONE, /**< a special opcode for unsupported byte codes */ } vm_oc_types; @@ -384,6 +388,9 @@ typedef enum VM_OC_PUSH_STATIC_FIELD_FUNC = VM_OC_NONE, /**< push static field initializer function */ VM_OC_ADD_COMPUTED_FIELD = VM_OC_NONE, /**< add computed field name */ #endif /* !JERRY_ESNEXT */ +#if !JERRY_MODULE_SYSTEM + VM_OC_MODULE_IMPORT = VM_OC_NONE, /**< module dynamic import */ +#endif /* JERRY_MODULE_SYSTEM */ VM_OC_UNUSED = VM_OC_NONE /**< placeholder if the list is empty */ } vm_oc_unused_types; diff --git a/tests/jerry/es.next/module-dynamic-import.js b/tests/jerry/es.next/module-dynamic-import.js new file mode 100644 index 000000000..a19516dbf --- /dev/null +++ b/tests/jerry/es.next/module-dynamic-import.js @@ -0,0 +1,28 @@ +/* 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. + */ + +function check_syntax_error(code) +{ + try { + eval(code); + assert(false); + } catch (e) { + assert(e instanceof SyntaxError); + } +} + +check_syntax_error("import('a.mjs',4)"); +check_syntax_error("(import('a.mjs',4))"); +check_syntax_error("(import 'a.mjs')"); diff --git a/tests/test262-esnext-excludelist.xml b/tests/test262-esnext-excludelist.xml index 065e7e2d3..7b2f7cf4b 100644 --- a/tests/test262-esnext-excludelist.xml +++ b/tests/test262-esnext-excludelist.xml @@ -6318,8 +6318,6 @@ features: [dynamic-import] https://github.com/tc39/proposal-dynamic-import --> - - @@ -6343,124 +6341,90 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -6548,76 +6512,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -7310,22 +7207,15 @@ - - - - - - - diff --git a/tests/unit-core/CMakeLists.txt b/tests/unit-core/CMakeLists.txt index 9511a2914..8699b79d5 100644 --- a/tests/unit-core/CMakeLists.txt +++ b/tests/unit-core/CMakeLists.txt @@ -58,6 +58,7 @@ set(SOURCE_UNIT_TEST_MAIN_MODULES test-lit-char-helpers.c test-literal-storage.c test-mem-stats.c + test-module-dynamic.c test-module.c test-native-callback-nested.c test-native-instanceof.c diff --git a/tests/unit-core/test-module-dynamic.c b/tests/unit-core/test-module-dynamic.c new file mode 100644 index 000000000..a3bd9aa2d --- /dev/null +++ b/tests/unit-core/test-module-dynamic.c @@ -0,0 +1,279 @@ +/* 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 "jerryscript.h" +#include "jerryscript-port.h" +#include "jerryscript-port-default.h" +#include "test-common.h" + +static int mode = 0; +static jerry_value_t global_user_value; + +static jerry_value_t +global_assert (const jerry_call_info_t *call_info_p, /**< call information */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt) /**< arguments length */ +{ + JERRY_UNUSED (call_info_p); + + TEST_ASSERT (args_cnt == 1 && jerry_value_is_true (args_p[0])); + return jerry_create_boolean (true); +} /* global_assert */ + +static void +register_assert (void) +{ + jerry_value_t global_object_value = jerry_get_global_object (); + + jerry_value_t function_value = jerry_create_external_function (global_assert); + jerry_value_t function_name_value = jerry_create_string ((const jerry_char_t *) "assert"); + jerry_value_t result_value = jerry_set_property (global_object_value, function_name_value, function_value); + + jerry_release_value (function_name_value); + jerry_release_value (function_value); + jerry_release_value (global_object_value); + + TEST_ASSERT (jerry_value_is_true (result_value)); + jerry_release_value (result_value); +} /* register_assert */ + +static void +compare_specifier (jerry_value_t specifier, /* string value */ + int id) /* module id */ +{ + jerry_char_t string[] = "XX_module.mjs"; + + TEST_ASSERT (id >= 1 && id <= 99 && string[0] == 'X' && string[1] == 'X'); + + string[0] = (jerry_char_t) ((id / 10) + '0'); + string[1] = (jerry_char_t) ((id % 10) + '0'); + + jerry_size_t length = (jerry_size_t) (sizeof (string) - 1); + jerry_char_t buffer[sizeof (string) - 1]; + + TEST_ASSERT (jerry_value_is_string (specifier)); + TEST_ASSERT (jerry_get_string_size (specifier) == length); + + TEST_ASSERT (jerry_string_to_char_buffer (specifier, buffer, length) == length); + TEST_ASSERT (memcmp (buffer, string, length) == 0); +} /* compare_specifier */ + +static jerry_value_t +module_import_callback (const jerry_value_t specifier, /* string value */ + const jerry_value_t user_value, /* user value assigned to the script */ + void *user_p) /* user pointer */ +{ + TEST_ASSERT (user_p == (void *) &mode); + + jerry_value_t compare_value = jerry_binary_operation (JERRY_BIN_OP_STRICT_EQUAL, + user_value, + global_user_value); + + TEST_ASSERT (jerry_value_is_true (compare_value)); + jerry_release_value (compare_value); + + switch (mode) + { + case 0: + { + compare_specifier (specifier, 1); + return jerry_create_error (JERRY_ERROR_RANGE, (const jerry_char_t *) "Err01"); + } + case 1: + { + compare_specifier (specifier, 2); + return jerry_create_null (); + } + case 2: + { + compare_specifier (specifier, 3); + + jerry_value_t promise_value = jerry_create_promise (); + /* Normally this should be a namespace object. */ + jerry_value_t object_value = jerry_create_object (); + jerry_resolve_or_reject_promise (promise_value, object_value, true); + jerry_release_value (object_value); + return promise_value; + } + } + + TEST_ASSERT (mode == 3 || mode == 4); + + jerry_parse_options_t parse_options; + parse_options.options = JERRY_PARSE_MODULE; + + jerry_value_t parse_result_value = jerry_parse ((const jerry_char_t *) "", 0, &parse_options); + TEST_ASSERT (!jerry_value_is_error (parse_result_value)); + + jerry_value_t result_value = jerry_module_link (parse_result_value, NULL, NULL); + TEST_ASSERT (!jerry_value_is_error (result_value)); + jerry_release_value (result_value); + + if (mode == 3) + { + result_value = jerry_module_evaluate (parse_result_value); + TEST_ASSERT (!jerry_value_is_error (result_value)); + jerry_release_value (result_value); + } + + return parse_result_value; +} /* module_import_callback */ + +static void +run_script (const char *source_p, /* source code */ + jerry_parse_options_t *parse_options_p) /* parse options */ +{ + jerry_value_t parse_result_value; + + parse_result_value = jerry_parse ((const jerry_char_t *) source_p, strlen (source_p), parse_options_p); + TEST_ASSERT (!jerry_value_is_error (parse_result_value)); + + jerry_value_t result_value; + if (parse_options_p->options & JERRY_PARSE_MODULE) + { + result_value = jerry_module_link (parse_result_value, NULL, NULL); + TEST_ASSERT (!jerry_value_is_error (result_value)); + jerry_release_value (result_value); + + result_value = jerry_module_evaluate (parse_result_value); + } + else + { + result_value = jerry_run (parse_result_value); + } + + jerry_release_value (parse_result_value); + + TEST_ASSERT (!jerry_value_is_error (result_value)); + jerry_release_value (result_value); + + result_value = jerry_run_all_enqueued_jobs (); + TEST_ASSERT (!jerry_value_is_error (result_value)); + jerry_release_value (result_value); +} /* run_script */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + if (!jerry_is_feature_enabled (JERRY_FEATURE_MODULE)) + { + jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Module is disabled!\n"); + jerry_cleanup (); + return 0; + } + + register_assert (); + jerry_module_set_import_callback (module_import_callback, (void *) &mode); + + jerry_parse_options_t parse_options; + parse_options.options = JERRY_PARSE_NO_OPTS; + + if (jerry_is_feature_enabled (JERRY_FEATURE_ERROR_MESSAGES)) + { + run_script ("var expected_message = 'Module cannot be instantiated'", &parse_options); + } + else + { + run_script ("var expected_message = ''", &parse_options); + } + + global_user_value = jerry_create_object (); + const char *source_p = TEST_STRING_LITERAL ("import('01_module.mjs').then(\n" + " function(resolve) { assert(false) },\n" + " function(reject) {\n" + " assert(reject instanceof RangeError\n" + " && reject.message === 'Err01')\n" + " }\n" + ")"); + + mode = 0; + parse_options.options = JERRY_PARSE_HAS_USER_VALUE; + parse_options.user_value = global_user_value; + run_script (source_p, &parse_options); + jerry_release_value (global_user_value); + + global_user_value = jerry_create_null (); + source_p = TEST_STRING_LITERAL ("var src = \"import('02_module.mjs').then(\\\n" + " function(resolve) { assert(false) },\\\n" + " function(reject) {\\\n" + " assert(reject instanceof RangeError\\\n" + " && reject.message === expected_message)\\\n" + " }\\\n" + ")\"\n" + "eval('eval(src)')"); + + mode = 1; + parse_options.options = JERRY_PARSE_HAS_USER_VALUE; + parse_options.user_value = global_user_value; + run_script (source_p, &parse_options); + jerry_release_value (global_user_value); + + global_user_value = jerry_create_number (5.6); + source_p = TEST_STRING_LITERAL ("function f() {\n" + " return function () {\n" + " return import('03_module.mjs')\n" + " }\n" + "}\n" + "export var a = f()().then(\n" + " function(resolve) { assert(typeof resolve == 'object') },\n" + " function(reject) { assert(false) }\n" + ")"); + + mode = 2; + parse_options.options = JERRY_PARSE_HAS_USER_VALUE | JERRY_PARSE_MODULE; + parse_options.user_value = global_user_value; + run_script (source_p, &parse_options); + jerry_release_value (global_user_value); + + global_user_value = jerry_create_string ((const jerry_char_t *) "Any string..."); + source_p = TEST_STRING_LITERAL ("var src = \"import('02_module.mjs').then(\\\n" + " function(resolve) { assert(typeof resolve == 'object') },\\\n" + " function(reject) { assert(false) }\\\n" + ")\"\n" + "function f() {\n" + " eval('(function() { return eval(src) })()')\n" + "}\n" + "f()\n"); + + mode = 3; + parse_options.options = JERRY_PARSE_HAS_USER_VALUE; + parse_options.user_value = global_user_value; + run_script (source_p, &parse_options); + jerry_release_value (global_user_value); + + global_user_value = jerry_create_external_function (global_assert); + source_p = TEST_STRING_LITERAL ("var src = \"import('02_module.mjs').then(\\\n" + " function(resolve) { assert(false) },\\\n" + " function(reject) {\\\n" + " assert(reject instanceof RangeError\\\n" + " && reject.message === expected_message)\\\n" + " }\\\n" + ")\"\n" + "export function f() {\n" + " eval('(function() { return eval(src) })()')\n" + "}\n" + "f()\n"); + + mode = 4; + parse_options.options = JERRY_PARSE_HAS_USER_VALUE | JERRY_PARSE_MODULE; + parse_options.user_value = global_user_value; + run_script (source_p, &parse_options); + jerry_release_value (global_user_value); + + jerry_cleanup (); + return 0; +} /* main */