From 6c484f3529c01a45b244f4d4c454435faca8253b Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Tue, 30 Mar 2021 15:40:09 +0200 Subject: [PATCH] Rework module linking (#4632) The module linking process from jerry_parse is moved out into a new jerry_module_link function, and jerry_parse is limited to create unlinked modules. JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com --- docs/02.API-REFERENCE.md | 121 ++++ docs/05.PORT-API.md | 68 ++- jerry-core/api/jerry.c | 92 ++- jerry-core/ecma/base/ecma-gc.c | 25 +- jerry-core/ecma/base/ecma-globals.h | 3 + jerry-core/ecma/base/ecma-module.c | 660 ++++++++++------------ jerry-core/ecma/base/ecma-module.h | 69 +-- jerry-core/include/jerryscript-core.h | 7 + jerry-core/include/jerryscript-port.h | 38 +- jerry-core/include/jerryscript-types.h | 8 + jerry-core/jcontext/jcontext.h | 1 - jerry-core/parser/js/js-parser-internal.h | 8 +- jerry-core/parser/js/js-parser-module.c | 302 +++++----- jerry-core/parser/js/js-parser-statm.c | 24 +- jerry-core/parser/js/js-parser.c | 6 +- jerry-main/main-jerry.c | 46 +- jerry-port/default/default-module.c | 359 +++++++++--- targets/nuttx-stm32f4/Makefile | 2 +- targets/nuttx-stm32f4/jerry_module.c | 302 ++++++++++ targets/nuttx-stm32f4/jerry_port.c | 36 -- tests/unit-core/CMakeLists.txt | 1 + tests/unit-core/test-module.c | 185 ++++++ 22 files changed, 1574 insertions(+), 789 deletions(-) create mode 100644 targets/nuttx-stm32f4/jerry_module.c create mode 100644 tests/unit-core/test-module.c diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index 6fcaeefab..dff99caeb 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -747,6 +747,37 @@ typedef void (*jerry_error_object_created_callback_t) (const jerry_value_t error - [jerry_set_error_object_created_callback](#jerry_set_error_object_created_callback) +## jerry_module_resolve_callback_t + +**Summary** + +Callback which is called by [jerry_module_link](#jerry_module_link) to get the referenced module. + +*Note*: + - 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_resolve_callback_t) (const jerry_value_t specifier, + const jerry_value_t referrer, + void *user_p); +``` + +- `specifier` - a module specifier string (usually used as a path to the module) +- `referrer` - a module object which contains the `specifier` in its source code +- `user_p` - pointer passed to [jerry_module_link](#jerry_module_link). +- return value + - a module object - if it can be resolved successfully + - an error - otherwise + +*New in version [[NEXT_RELEASE]]*. + +**See also** +- [jerry_module_link](#jerry_module_link) +- [jerry_get_global_object](#jerry_get_global_object) + ## jerry_backtrace_callback_t **Summary** @@ -4244,6 +4275,96 @@ jerry_value_as_uint32 (const jerry_value_t value); } ``` +# Functions for module objects + +These APIs all depend on module support. + +## jerry_module_link + +**Summary** + +Link modules to their dependencies. The dependencies are resolved by a user callback. + +*Notes*: +- Returned value must be freed with [jerry_release_value](#jerry_release_value) when it + is no longer needed. +- 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). + +**Prototype** + +```c +jerry_value_t jerry_module_link (const jerry_value_t module_val, + jerry_module_resolve_callback_t callback, void *user_p) +``` + +- `module_val` - module object in unlinked state +- `callback` - user callback which is called to resolve dependencies, + uses `jerry_port_module_resolve` when NULL is passed +- `user_p` - user pointer passed to the callback +- return + - true - if linking is successful + - error - otherwise + +*New in version [[NEXT_RELEASE]]*. + +**Example** + +[doctest]: # (test="compile") + +```c +#include + +static jerry_value_t +module_resolve_callback (const jerry_value_t specifier, + const jerry_value_t referrer, + void *user_data_p) +{ + /* In this case, the specifier contains 'b.mjs', and the referrer is the module + * created in the main() function below. Normally the specifier string should be + * extended to a full file system path, and it should be checked whether a module + * corresponding to this path has been loaded already. For simplicity, this function + * returns with a new module. */ + + const jerry_char_t script[] = "export var a = 5"; + const jerry_char_t file[] = "b.mjs"; + + jerry_parse_options_t parse_options; + parse_options.options = JERRY_PARSE_MODULE | JERRY_PARSE_HAS_RESOURCE; + parse_options.resource_name_p = file; + parse_options.resource_name_length = sizeof (file) - 1; + + return jerry_parse (script, sizeof (script) - 1, &parse_options); +} /* module_resolve_callback */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + const jerry_char_t script[] = "import a from 'b.mjs'"; + const jerry_char_t file[] = "a.mjs"; + + jerry_parse_options_t parse_options; + parse_options.options = JERRY_PARSE_MODULE | JERRY_PARSE_HAS_RESOURCE; + parse_options.resource_name_p = file; + parse_options.resource_name_length = sizeof (file) - 1; + + jerry_value_t ret_value = jerry_parse (script, sizeof (script) - 1, &parse_options); + + jerry_module_link (ret_value, module_resolve_callback, NULL); + + jerry_release_value (ret_value); + + jerry_cleanup (); + return 0; +} +``` + +**See also** +- [jerry_module_resolve_callback_t](#jerry_module_resolve_callback_t) + # Functions for promise objects These APIs all depend on the es.next profile (or on some build options). diff --git a/docs/05.PORT-API.md b/docs/05.PORT-API.md index 1f96482ee..976832cbb 100644 --- a/docs/05.PORT-API.md +++ b/docs/05.PORT-API.md @@ -85,12 +85,10 @@ void jerry_port_print_char (char c); ### Jerry Module system -The port API provides functions that can be used by the module system to open -and close source files, and normalize file paths. -The `jerry_port_get_native_module` port function can be used to provide native -modules to the engine. This function will be called when an import/export -statement is encountered with an unknown module specifier, which embedders can -use to supply native module objects based on the module name argument. +The port API provides optional functions that can be used by the +user application to resolve modules. If no callback is provided +to `jerry_module_link`, the `jerry_port_module_resolve` function +is used for resolving modules. ```c /** @@ -115,40 +113,38 @@ jerry_port_release_source (uint8_t *buffer_p) /**< buffer to free */ } /* jerry_port_release_source */ /** - * Normalize a file path + * Default module resolver. * - * @return length of the path written to the output buffer - */ -size_t -jerry_port_normalize_path (const char *in_path_p, /**< input file path */ - char *out_buf_p, /**< output buffer */ - size_t out_buf_size, /**< size of output buffer */ - char *base_file_p) /**< base file path */ -{ - // normalize in_path_p by expanding relative paths etc. - // if base_file_p is not NULL, in_path_p is relative to that file - // write to out_buf_p the normalized path - // return length of written path -} /* jerry_port_normalize_path */ - -/** - * Get the module object of a native module. - * - * Note: - * This port function is called by jerry-core when JERRY_MODULE_SYSTEM - * is enabled. - * - * @param name String value of the module specifier. - * - * @return Undefined, if 'name' is not a native module - * jerry_value_t containing the module object, otherwise + * @return a module object if resolving is successful, an error otherwise */ jerry_value_t -jerry_port_get_native_module (jerry_value_t name) /**< module specifier */ +jerry_port_module_resolve (const jerry_value_t specifier, /**< module specifier string */ + const jerry_value_t referrer, /**< parent module */ + void *user_p) /**< user data */ { - (void) name; - return jerry_create_undefined (); -} + // Resolves a module using the specifier string. If a referrer is a module, + // and specifier is a relative path, the base path should be the directory + // part extracted from the path of the referrer module. + + // The callback function of jerry_module_link may call this function + // if it cannot resolve a module. Furthermore if the callback is NULL, + // this function is used for resolving modules. + + // The default implementation only resolves ECMAScript modules, and does + // not (currently) use the user data. +} /* jerry_port_module_resolve */ + +/** + * Release known modules. + */ +void +jerry_port_module_release (const jerry_value_t realm) /**< if this argument is object, release only those modules, + * which realm value is equal to this argument. */ +{ + // This function releases the known modules, forcing their reload + // when resolved again later. The released modules can be filtered + // by realms. This function is only called by user applications. +} /* jerry_port_module_release */ ``` ## Date diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index ea67ce6a5..9900a1973 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -160,6 +160,18 @@ static const char * const error_json_not_supported_p = "JSON support is disabled static const char * const error_symbol_not_supported_p = "Symbol support is disabled"; #endif /* !JERRY_ESNEXT */ +#if JERRY_MODULE_SYSTEM +/** + * Error message, if argument is not a module + */ +static const char * const error_not_module_p = "Argument is not a module"; +#else /* !JERRY_MODULE_SYSTEM */ +/** + * Error message, if Module support is disabled + */ +static const char * const error_module_not_supported_p = "Module support is disabled"; +#endif /* JERRY_MODULE_SYSTEM */ + #if !JERRY_BUILTIN_PROMISE /** * Error message, if Promise support is disabled @@ -528,9 +540,9 @@ jerry_parse (const jerry_char_t *source_p, /**< script source */ if ((parse_opts & JERRY_PARSE_MODULE) != 0) { #if JERRY_MODULE_SYSTEM - ecma_module_initialize_context ((const ecma_parse_options_t *) options_p); + ecma_module_initialize_context (); #else /* !JERRY_MODULE_SYSTEM */ - return jerry_throw (ecma_raise_syntax_error (ECMA_ERR_MSG ("Module system has been disabled"))); + return jerry_throw (ecma_raise_syntax_error (ECMA_ERR_MSG (error_module_not_supported_p))); #endif /* JERRY_MODULE_SYSTEM */ } @@ -556,27 +568,12 @@ jerry_parse (const jerry_char_t *source_p, /**< script source */ #if JERRY_MODULE_SYSTEM if (JERRY_UNLIKELY (parse_opts & JERRY_PARSE_MODULE)) { - if (ECMA_IS_VALUE_ERROR (ecma_module_parse_referenced_modules ())) - { - ecma_bytecode_deref (bytecode_data_p); - ecma_module_cleanup_context (); - - return ecma_create_error_reference_from_context (); - } - - ecma_module_t *root_module_p = JERRY_CONTEXT (module_current_p); - root_module_p->compiled_code_p = bytecode_data_p; - - ecma_object_t *obj_p = ecma_create_object (NULL, sizeof (ecma_extended_object_t), ECMA_OBJECT_TYPE_CLASS); - - ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) obj_p; - ext_object_p->u.class_prop.class_id = LIT_MAGIC_STRING_MODULE_UL; - ECMA_SET_INTERNAL_VALUE_POINTER (ext_object_p->u.class_prop.u.value, root_module_p); + ecma_module_t *module_p = JERRY_CONTEXT (module_current_p); + module_p->compiled_code_p = bytecode_data_p; JERRY_CONTEXT (module_current_p) = NULL; - JERRY_CONTEXT (module_list_p) = NULL; - return ecma_make_object_value (obj_p); + return ecma_make_object_value ((ecma_object_t *) module_p); } #endif /* JERRY_MODULE_SYSTEM */ @@ -713,20 +710,16 @@ jerry_run (const jerry_value_t func_val) /**< function to run */ ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p; #if JERRY_MODULE_SYSTEM - if (ext_object_p->u.class_prop.class_id == LIT_MAGIC_STRING_MODULE_UL) + if (JERRY_UNLIKELY (ext_object_p->u.class_prop.class_id == LIT_MAGIC_STRING_MODULE_UL)) { - ecma_module_t *root_module_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_module_t, ext_object_p->u.class_prop.u.value); + ecma_module_t *root_module_p = (ecma_module_t *) ext_object_p; -#if JERRY_BUILTIN_REALMS - ecma_object_t *global_object_p = (ecma_object_t *) ecma_op_function_get_realm (root_module_p->compiled_code_p); -#else /* !JERRY_BUILTIN_REALMS */ - ecma_object_t *global_object_p = ecma_builtin_get_global (); -#endif /* JERRY_BUILTIN_REALMS */ + if (root_module_p->header.u.class_prop.extra_info != ECMA_MODULE_STATE_LINKED) + { + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Module must be in linked state"))); + } - ecma_create_global_lexical_block (global_object_p); - root_module_p->scope_p = ecma_get_global_scope (global_object_p); - - return jerry_return (vm_run_module (root_module_p)); + return jerry_return (ecma_module_evaluate (root_module_p)); } #endif /* JERRY_MODULE_SYSTEM */ @@ -770,6 +763,43 @@ jerry_eval (const jerry_char_t *source_p, /**< source code */ parse_opts)); } /* jerry_eval */ +/** + * Link modules to their dependencies. The dependencies are resolved by a user callback. + * + * Note: + * returned value must be freed with jerry_release_value, when it is no longer needed. + * + * @return true - if linking is successful, error - otherwise + */ +jerry_value_t +jerry_module_link (const jerry_value_t module_val, /**< root module */ + jerry_module_resolve_callback_t callback, /**< resolve module callback, uses + * jerry_port_module_resolve when NULL is passed */ + void *user_p) /**< pointer passed to the resolve callback */ +{ +#if JERRY_MODULE_SYSTEM + if (callback == NULL) + { + callback = jerry_port_module_resolve; + } + + ecma_module_t *module_p = ecma_module_get_resolved_module (module_val); + + if (module_p == NULL) + { + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (error_not_module_p))); + } + + return jerry_return (ecma_module_link (module_p, callback, user_p)); +#else /* !JERRY_MODULE_SYSTEM */ + JERRY_UNUSED (module_val); + JERRY_UNUSED (callback); + JERRY_UNUSED (user_p); + + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (error_module_not_supported_p))); +#endif /* JERRY_MODULE_SYSTEM */ +} /* jerry_module_link */ + /** * Run enqueued Promise jobs until the first thrown error or until all get executed. * diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index 5478a3db1..cdf14051a 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -721,6 +721,24 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */ switch (ext_object_p->u.class_prop.class_id) { +#if JERRY_MODULE_SYSTEM + case LIT_MAGIC_STRING_MODULE_UL: + { + ecma_module_node_t *node_p = ((ecma_module_t *) ext_object_p)->imports_p; + + while (node_p != NULL) + { + if (ecma_is_value_object (node_p->u.path_or_module)) + { + ecma_gc_set_object_visited (ecma_get_object_from_value (node_p->u.path_or_module)); + } + + node_p = node_p->next_p; + } + break; + } +#endif /* JERRY_MODULE_SYSTEM */ + #if JERRY_BUILTIN_PROMISE case LIT_MAGIC_STRING_PROMISE_UL: { @@ -1705,11 +1723,8 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ #if JERRY_MODULE_SYSTEM case LIT_MAGIC_STRING_MODULE_UL: { - ecma_module_t *root_module_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_module_t, - ext_object_p->u.class_prop.u.value); - - ecma_bytecode_deref (root_module_p->compiled_code_p); - ecma_module_cleanup (root_module_p); + ecma_module_release_module ((ecma_module_t *) ext_object_p); + ext_object_size = sizeof (ecma_module_t); break; } #endif /* JERRY_MODULE_SYSTEM */ diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 267a6e58d..01fef9367 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -987,6 +987,9 @@ typedef struct ecma_value_t date; /**< Date object [[DateValue]] internal property */ int32_t tza; /**< TimeZone adjustment for date objects */ uint32_t length; /**< length related property (e.g. length of ArrayBuffer) */ +#if JERRY_MODULE_SYSTEM + uint32_t dfs_ancestor_index; /**< module dfs ancestor index (ES2020 15.2.1.16) */ +#endif /* JERRY_MODULE_SYSTEM */ ecma_value_t target; /**< [[ProxyTarget]] or [[WeakRefTarget]] internal property */ ecma_value_t head; /**< points to the async generator task queue head item */ ecma_value_t promise; /**< PromiseCapability[[Promise]] internal slot */ diff --git a/jerry-core/ecma/base/ecma-module.c b/jerry-core/ecma/base/ecma-module.c index 44801d789..a21356397 100644 --- a/jerry-core/ecma/base/ecma-module.c +++ b/jerry-core/ecma/base/ecma-module.c @@ -28,208 +28,31 @@ #if JERRY_MODULE_SYSTEM -/** - * Takes a ModuleSpecifier and applies path normalization to it. - * It's not checked if the ModuleSpecifier is a valid path or not. - * Note: See 15.2.1.17 - * - * @return pointer to ecma_string_t containing the normalized and zero terminated path - */ -ecma_string_t * -ecma_module_create_normalized_path (const lit_utf8_byte_t *char_p, /**< module path specifier */ - lit_utf8_size_t size, /**< size of module specifier */ - ecma_string_t *const base_path_p) /**< base path for the module specifier */ -{ - JERRY_ASSERT (size > 0); - ecma_string_t *ret_p = NULL; - - /* The module specifier is cesu8 encoded, we need to convert is to utf8, and zero terminate it, - * so that OS level functions can handle it. */ - lit_utf8_byte_t *path_p = (lit_utf8_byte_t *) jmem_heap_alloc_block (size + 1u); - - lit_utf8_size_t utf8_size; - utf8_size = lit_convert_cesu8_string_to_utf8_string (char_p, - size, - path_p, - size); - path_p[utf8_size] = LIT_CHAR_NULL; - - lit_utf8_byte_t *module_path_p = NULL; - lit_utf8_size_t module_path_size = 0; - - if (base_path_p != NULL) - { - module_path_size = ecma_string_get_size (base_path_p); - module_path_p = (lit_utf8_byte_t *) jmem_heap_alloc_block (module_path_size + 1); - - lit_utf8_size_t module_utf8_size; - module_utf8_size = ecma_string_copy_to_utf8_buffer (base_path_p, - module_path_p, - module_path_size); - - module_path_p[module_utf8_size] = LIT_CHAR_NULL; - } - - lit_utf8_byte_t *normalized_out_p = (lit_utf8_byte_t *) jmem_heap_alloc_block (ECMA_MODULE_MAX_PATH); - size_t normalized_size = jerry_port_normalize_path ((const char *) path_p, - (char *) normalized_out_p, - ECMA_MODULE_MAX_PATH, - (char *) module_path_p); - - if (normalized_size > 0) - { - /* Convert the normalized path to cesu8. */ - ret_p = ecma_new_ecma_string_from_utf8_converted_to_cesu8 (normalized_out_p, (lit_utf8_size_t) (normalized_size)); - } - - jmem_heap_free_block (path_p, size + 1u); - jmem_heap_free_block (normalized_out_p, ECMA_MODULE_MAX_PATH); - if (module_path_p != NULL) - { - jmem_heap_free_block (module_path_p, module_path_size + 1); - } - - return ret_p; -} /* ecma_module_create_normalized_path */ - -/** - * Push a new module into the module list. New modules are inserted after the head module, this way in the end the - * root module remains the first in the list. - */ -static void -ecma_module_list_push (ecma_module_t *module_p) -{ - ecma_module_t *head_p = JERRY_CONTEXT (module_list_p); - module_p->next_p = head_p->next_p; - head_p->next_p = module_p; -} /* ecma_module_list_push */ - -/** - * Lookup a module with a specific identifier. - * - * @return pointer to ecma_module_t, if found - * NULL, otherwise - */ -static ecma_module_t * -ecma_module_list_lookup (ecma_string_t *const path_p) /**< module identifier */ -{ - ecma_module_t *current_p = JERRY_CONTEXT (module_list_p); - while (current_p != NULL) - { - if (ecma_compare_ecma_strings (path_p, current_p->path_p)) - { - return current_p; - } - - current_p = current_p->next_p; - } - - return NULL; -} /* ecma_module_list_lookup */ - -/** - * Create a new module - * - * @return pointer to created module - */ -static ecma_module_t * -ecma_module_create_module (ecma_string_t *const path_p) /**< module identifier */ -{ - ecma_module_t *module_p = (ecma_module_t *) jmem_heap_alloc_block (sizeof (ecma_module_t)); - memset (module_p, 0, sizeof (ecma_module_t)); - - module_p->path_p = path_p; - ecma_module_list_push (module_p); - return module_p; -} /* ecma_module_create_module */ - -/** - * Checks if we already have a module request in the module list. - * - * @return pointer to found or newly created module structure - */ -ecma_module_t * -ecma_module_find_module (ecma_string_t *const path_p) /**< module path */ -{ - ecma_module_t *module_p = ecma_module_list_lookup (path_p); - - if (module_p) - { - ecma_deref_ecma_string (path_p); - return module_p; - } - - return ecma_module_create_module (path_p); -} /* ecma_module_find_module */ - -/** - * Create a new native module - * - * @return pointer to created module - */ -ecma_module_t * -ecma_module_find_native_module (ecma_string_t *const path_p) -{ - ecma_module_t *module_p = ecma_module_list_lookup (path_p); - - if (module_p != NULL) - { - return module_p; - } - - ecma_value_t native = jerry_port_get_native_module (ecma_make_string_value (path_p)); - - if (!ecma_is_value_undefined (native)) - { - JERRY_ASSERT (ecma_is_value_object (native)); - - module_p = ecma_module_create_module (path_p); - module_p->state = ECMA_MODULE_STATE_NATIVE; - module_p->namespace_object_p = ecma_get_object_from_value (native); - - return module_p; - } - - return NULL; -} /* ecma_module_find_native_module */ - /** * Initialize context variables for the root module. */ void -ecma_module_initialize_context (const ecma_parse_options_t *options_p) /**< configuration options */ +ecma_module_initialize_context (void) { JERRY_ASSERT (JERRY_CONTEXT (module_current_p) == NULL); - JERRY_ASSERT (JERRY_CONTEXT (module_list_p) == NULL); - ecma_string_t *path_p = ecma_get_magic_string (LIT_MAGIC_STRING_RESOURCE_ANON); + ecma_object_t *obj_p = ecma_create_object (NULL, sizeof (ecma_module_t), ECMA_OBJECT_TYPE_CLASS); - if (options_p != NULL - && (options_p->options & JERRY_PARSE_HAS_RESOURCE) - && options_p->resource_name_length > 0) - { - const lit_utf8_byte_t *path_str_chars_p = options_p->resource_name_p; - lit_utf8_size_t path_str_size = (lit_utf8_size_t) options_p->resource_name_length; + ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) obj_p; + ext_object_p->u.class_prop.class_id = LIT_MAGIC_STRING_MODULE_UL; + ext_object_p->u.class_prop.extra_info = ECMA_MODULE_STATE_UNLINKED; - path_p = ecma_module_create_normalized_path (path_str_chars_p, - path_str_size, - NULL); - if (path_p == NULL) - { - path_p = ecma_new_ecma_string_from_utf8_converted_to_cesu8 (path_str_chars_p, path_str_size); - } - } + ecma_module_t *module_p = (ecma_module_t *) obj_p; - ecma_module_t *module_p = (ecma_module_t *) jmem_heap_alloc_block (sizeof (ecma_module_t)); - memset (module_p, 0, sizeof (ecma_module_t)); - - module_p->path_p = path_p; - /* Root modules are handled differently then the rest of the referenced modules, as the scope and compiled code - * are handled separately. */ - module_p->state = ECMA_MODULE_STATE_ROOT; + module_p->imports_p = NULL; + module_p->local_exports_p = NULL; + module_p->indirect_exports_p = NULL; + module_p->star_exports_p = NULL; + module_p->compiled_code_p = NULL; + module_p->scope_p = NULL; + module_p->namespace_object_p = NULL; JERRY_CONTEXT (module_current_p) = module_p; - JERRY_CONTEXT (module_list_p) = module_p; } /* ecma_module_initialize_context */ /** @@ -238,13 +61,30 @@ ecma_module_initialize_context (const ecma_parse_options_t *options_p) /**< conf void ecma_module_cleanup_context (void) { - ecma_module_cleanup (JERRY_CONTEXT (module_current_p)); + ecma_deref_object ((ecma_object_t *) JERRY_CONTEXT (module_current_p)); #ifndef JERRY_NDEBUG JERRY_CONTEXT (module_current_p) = NULL; - JERRY_CONTEXT (module_list_p) = NULL; #endif /* JERRY_NDEBUG */ } /* ecma_module_cleanup_context */ +/** + * Gets the internal module pointer of a module + * + * @return module pointer + */ +static inline ecma_module_t * JERRY_ATTR_ALWAYS_INLINE +ecma_module_get_from_object (ecma_value_t module_val) /**< module */ +{ + JERRY_ASSERT (ecma_is_value_object (module_val)); + + ecma_object_t *object_p = ecma_get_object_from_value (module_val); + + JERRY_ASSERT (ecma_get_object_type (object_p) == ECMA_OBJECT_TYPE_CLASS); + JERRY_ASSERT (((ecma_extended_object_t *) object_p)->u.class_prop.class_id == LIT_MAGIC_STRING_MODULE_UL); + + return (ecma_module_t *) object_p; +} /* ecma_module_get_from_object */ + /** * Inserts a {module, export_name} record into a resolve set. * Note: See 15.2.1.16.3 - resolveSet and exportStarSet @@ -252,7 +92,7 @@ ecma_module_cleanup_context (void) * @return true - if the set already contains the record * false - otherwise */ -bool +static bool ecma_module_resolve_set_insert (ecma_module_resolve_set_t **set_p, /**< [in, out] resolve set */ ecma_module_t *const module_p, /**< module */ ecma_string_t *const export_name_p) /**< export name */ @@ -286,7 +126,7 @@ ecma_module_resolve_set_insert (ecma_module_resolve_set_t **set_p, /**< [in, out /** * Cleans up contents of a resolve set. */ -void +static void ecma_module_resolve_set_cleanup (ecma_module_resolve_set_t *set_p) /**< resolve set */ { while (set_p != NULL) @@ -302,7 +142,7 @@ ecma_module_resolve_set_cleanup (ecma_module_resolve_set_t *set_p) /**< resolve * Pushes a new resolve frame on top of a resolve stack and initializes it * to begin resolving the specified exported name in the base module. */ -void +static void ecma_module_resolve_stack_push (ecma_module_resolve_stack_t **stack_p, /**< [in, out] resolve stack */ ecma_module_t * const module_p, /**< base module */ ecma_string_t * const export_name_p) /**< exported name */ @@ -323,7 +163,7 @@ ecma_module_resolve_stack_push (ecma_module_resolve_stack_t **stack_p, /**< [in, /** * Pops the topmost frame from a resolve stack. */ -void +static void ecma_module_resolve_stack_pop (ecma_module_resolve_stack_t **stack_p) /**< [in, out] resolve stack */ { JERRY_ASSERT (stack_p != NULL); @@ -365,7 +205,6 @@ ecma_module_resolve_export (ecma_module_t *const module_p, /**< base module */ ecma_module_resolve_stack_t *current_frame_p = stack_p; ecma_module_t *current_module_p = current_frame_p->module_p; - JERRY_ASSERT (current_module_p->state >= ECMA_MODULE_STATE_PARSED); ecma_string_t *current_export_name_p = current_frame_p->export_name_p; if (!current_frame_p->resolving) @@ -380,7 +219,7 @@ ecma_module_resolve_export (ecma_module_t *const module_p, /**< base module */ continue; } - if (current_module_p->state == ECMA_MODULE_STATE_NATIVE) + if (current_module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_NATIVE) { ecma_object_t *object_p = current_module_p->namespace_object_p; ecma_value_t prop_value = ecma_op_object_find_own (ecma_make_object_value (object_p), @@ -407,8 +246,7 @@ ecma_module_resolve_export (ecma_module_t *const module_p, /**< base module */ if (current_module_p->local_exports_p != NULL) { /* 15.2.1.16.3 / 4 */ - JERRY_ASSERT (current_module_p->local_exports_p->next_p == NULL); - ecma_module_names_t *export_names_p = current_module_p->local_exports_p->module_names_p; + ecma_module_names_t *export_names_p = current_module_p->local_exports_p; while (export_names_p != NULL) { if (ecma_compare_ecma_strings (current_export_name_p, export_names_p->imex_name_p)) @@ -450,7 +288,7 @@ ecma_module_resolve_export (ecma_module_t *const module_p, /**< base module */ { /* 5.2.1.16.3 / 5.a.iv */ ecma_module_resolve_stack_push (&stack_p, - indirect_export_p->module_request_p, + ecma_module_get_from_object (*indirect_export_p->u.module_object_p), export_names_p->local_name_p); break; } @@ -502,7 +340,9 @@ ecma_module_resolve_export (ecma_module_t *const module_p, /**< base module */ JERRY_ASSERT (star_export_p->module_names_p == NULL); /* 15.2.1.16.3 / 10.c */ - ecma_module_resolve_stack_push (&stack_p, star_export_p->module_request_p, export_name_p); + ecma_module_resolve_stack_push (&stack_p, + ecma_module_get_from_object (*star_export_p->u.module_object_p), + export_name_p); star_export_p = star_export_p->next_p; } @@ -539,39 +379,44 @@ ecma_module_resolve_export (ecma_module_t *const module_p, /**< base module */ * @return ECMA_VALUE_ERROR - if an error occured * ECMA_VALUE_EMPTY - otherwise */ -static ecma_value_t +ecma_value_t ecma_module_evaluate (ecma_module_t *module_p) /**< module */ { - JERRY_ASSERT (module_p->state >= ECMA_MODULE_STATE_PARSED); + if (module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_ERROR) + { + return ecma_raise_range_error (ECMA_ERR_MSG ("Module is in error state")); + } - if (module_p->state >= ECMA_MODULE_STATE_EVALUATING) + if (module_p->header.u.class_prop.extra_info >= ECMA_MODULE_STATE_EVALUATING) { return ECMA_VALUE_EMPTY; } + JERRY_ASSERT (module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_LINKED); + #if JERRY_BUILTIN_REALMS ecma_object_t *global_object_p = (ecma_object_t *) ecma_op_function_get_realm (module_p->compiled_code_p); #else /* !JERRY_BUILTIN_REALMS */ ecma_object_t *global_object_p = ecma_builtin_get_global (); #endif /* JERRY_BUILTIN_REALMS */ - module_p->state = ECMA_MODULE_STATE_EVALUATING; + module_p->header.u.class_prop.extra_info = ECMA_MODULE_STATE_EVALUATING; module_p->scope_p = ecma_create_decl_lex_env (ecma_get_global_environment (global_object_p)); ecma_value_t ret_value; ret_value = vm_run_module (module_p); + module_p->header.u.class_prop.extra_info = ECMA_MODULE_STATE_ERROR; + if (!ECMA_IS_VALUE_ERROR (ret_value)) { ecma_free_value (ret_value); + module_p->header.u.class_prop.extra_info = ECMA_MODULE_STATE_EVALUATED; ret_value = ECMA_VALUE_EMPTY; } - module_p->state = ECMA_MODULE_STATE_EVALUATED; ecma_bytecode_deref (module_p->compiled_code_p); -#ifndef JERRY_NDEBUG module_p->compiled_code_p = NULL; -#endif /* JERRY_NDEBUG */ return ret_value; } /* ecma_module_evaluate */ @@ -646,7 +491,7 @@ ecma_module_create_namespace_object (ecma_module_t *module_p) /**< module */ return result; } - JERRY_ASSERT (module_p->state == ECMA_MODULE_STATE_EVALUATED); + JERRY_ASSERT (module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_EVALUATED); ecma_module_resolve_set_t *resolve_set_p = NULL; ecma_module_resolve_stack_t *stack_p = NULL; @@ -678,30 +523,36 @@ ecma_module_create_namespace_object (ecma_module_t *module_p) /**< module */ break; } - if (current_module_p->local_exports_p != NULL) + ecma_module_names_t *export_names_p = current_module_p->local_exports_p; + + if (export_names_p != NULL) { /* 15.2.1.16.2 / 5 */ - JERRY_ASSERT (current_module_p->local_exports_p->next_p == NULL); - ecma_module_names_t *export_names_p = current_module_p->local_exports_p->module_names_p; - while (export_names_p != NULL && ecma_is_value_empty (result)) + do { result = ecma_module_namespace_object_add_export_if_needed (module_p, export_names_p->imex_name_p); export_names_p = export_names_p->next_p; } + while (export_names_p != NULL && ecma_is_value_empty (result)); } /* 15.2.1.16.2 / 6 */ ecma_module_node_t *indirect_export_p = current_module_p->indirect_exports_p; while (indirect_export_p != NULL && ecma_is_value_empty (result)) { - ecma_module_names_t *export_names_p = indirect_export_p->module_names_p; - while (export_names_p != NULL && ecma_is_value_empty (result)) + export_names_p = indirect_export_p->module_names_p; + + JERRY_ASSERT (export_names_p != NULL); + + do { result = ecma_module_namespace_object_add_export_if_needed (module_p, export_names_p->imex_name_p); export_names_p = export_names_p->next_p; } + while (export_names_p != NULL && ecma_is_value_empty (result)); + indirect_export_p = indirect_export_p->next_p; } @@ -713,7 +564,7 @@ ecma_module_create_namespace_object (ecma_module_t *module_p) /**< module */ /* 15.2.1.16.3/10.c */ ecma_module_resolve_stack_push (&stack_p, - star_export_p->module_request_p, + ecma_module_get_from_object (*star_export_p->u.module_object_p), ecma_get_magic_string (LIT_MAGIC_STRING_ASTERIX_CHAR)); star_export_p = star_export_p->next_p; @@ -792,13 +643,17 @@ ecma_module_connect_imports (ecma_module_t *module_p) /* Resolve imports and create local bindings. */ while (import_node_p != NULL) { - ecma_value_t result = ecma_module_evaluate (import_node_p->module_request_p); + ecma_module_names_t *import_names_p = import_node_p->module_names_p; + ecma_module_t *imported_module_p = ecma_module_get_from_object (import_node_p->u.path_or_module); + + /* Module is evaluated even if it is used only in export-from statements. */ + ecma_value_t result = ecma_module_evaluate (imported_module_p); + if (ECMA_IS_VALUE_ERROR (result)) { return result; } - ecma_module_names_t *import_names_p = import_node_p->module_names_p; while (import_names_p != NULL) { const bool is_namespace_import = ecma_compare_ecma_string_to_magic_id (import_names_p->imex_name_p, @@ -808,19 +663,20 @@ ecma_module_connect_imports (ecma_module_t *module_p) if (is_namespace_import) { - result = ecma_module_create_namespace_object (import_node_p->module_request_p); + result = ecma_module_create_namespace_object (imported_module_p); + if (ECMA_IS_VALUE_ERROR (result)) { return result; } - ecma_ref_object (import_node_p->module_request_p->namespace_object_p); - prop_value = ecma_make_object_value (import_node_p->module_request_p->namespace_object_p); + ecma_ref_object (imported_module_p->namespace_object_p); + prop_value = ecma_make_object_value (imported_module_p->namespace_object_p); } else /* !is_namespace_import */ { ecma_module_record_t record; - result = ecma_module_resolve_export (import_node_p->module_request_p, import_names_p->imex_name_p, &record); + result = ecma_module_resolve_export (imported_module_p, import_names_p->imex_name_p, &record); if (ECMA_IS_VALUE_ERROR (result)) { @@ -832,7 +688,7 @@ ecma_module_connect_imports (ecma_module_t *module_p) return ecma_raise_syntax_error (ECMA_ERR_MSG ("Ambiguous import request")); } - if (record.module_p->state == ECMA_MODULE_STATE_NATIVE) + if (record.module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_NATIVE) { ecma_object_t *object_p = record.module_p->namespace_object_p; prop_value = ecma_op_object_find_own (ecma_make_object_value (object_p), object_p, record.name_p); @@ -901,9 +757,11 @@ ecma_module_check_indirect_exports (ecma_module_t *module_p) while (name_p != NULL) { ecma_module_record_t record; - ecma_value_t result = ecma_module_resolve_export (indirect_export_p->module_request_p, - name_p->local_name_p, - &record); + ecma_value_t result; + + result = ecma_module_resolve_export (ecma_module_get_from_object (*indirect_export_p->u.module_object_p), + name_p->local_name_p, + &record); if (ECMA_IS_VALUE_ERROR (result)) { @@ -946,116 +804,234 @@ ecma_module_initialize (ecma_module_t *module_p) /**< module */ return ret_value; } /* ecma_module_initialize */ -static ecma_value_t ecma_module_parse (ecma_module_t *module_p); +/** + * Gets the internal module pointer of a module + * + * @return module pointer - if module_val is a valid module, + * NULL - otherwise + */ +ecma_module_t * +ecma_module_get_resolved_module (ecma_value_t module_val) /**< module */ +{ + if (!ecma_is_value_object (module_val)) + { + return NULL; + } + + ecma_object_t *object_p = ecma_get_object_from_value (module_val); + + if (ecma_get_object_type (object_p) != ECMA_OBJECT_TYPE_CLASS) + { + return NULL; + } + + ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p; + + if (ext_object_p->u.class_prop.class_id != LIT_MAGIC_STRING_MODULE_UL) + { + return NULL; + } + + return (ecma_module_t *) object_p; +} /* ecma_module_get_resolved_module */ /** - * Parses all referenced modules. + * A module stack for depth-first search + */ +typedef struct ecma_module_stack_item_t +{ + struct ecma_module_stack_item_t *prev_p; /**< prev in the stack */ + struct ecma_module_stack_item_t *parent_p; /**< parent item in the stack */ + ecma_module_t *module_p; /**< currently processed module */ + ecma_module_node_t *node_p; /**< currently processed node */ + uint32_t dfs_index; /**< dfs index (ES2020 15.2.1.16) */ +} ecma_module_stack_item_t; + +/** + * Link module dependencies * * @return ECMA_VALUE_ERROR - if an error occured - * ECMA_VALUE_EMPTY - otherwise + * ECMA_VALUE_UNDEFINED - otherwise */ ecma_value_t -ecma_module_parse_referenced_modules (void) +ecma_module_link (ecma_module_t *module_p, /**< root module */ + jerry_module_resolve_callback_t callback, /**< resolve module callback */ + void *user_p) /**< pointer passed to the resolve callback */ { - ecma_module_t *current_p = JERRY_CONTEXT (module_list_p); - while (current_p != NULL) + if (module_p->header.u.class_prop.extra_info != ECMA_MODULE_STATE_UNLINKED) { - if (ECMA_IS_VALUE_ERROR (ecma_module_parse (current_p))) + return ecma_raise_type_error (ECMA_ERR_MSG ("Module must be in unlinked state")); + } + + module_p->header.u.class_prop.extra_info = ECMA_MODULE_STATE_LINKING; + + uint32_t dfs_index = 0; + ecma_module_stack_item_t *last_p; + ecma_module_node_t *node_p; + + last_p = (ecma_module_stack_item_t *) jmem_heap_alloc_block (sizeof (ecma_module_stack_item_t)); + last_p->prev_p = NULL; + last_p->parent_p = NULL; + last_p->module_p = module_p; + last_p->node_p = module_p->imports_p; + last_p->dfs_index = dfs_index; + + module_p->header.u.class_prop.u.dfs_ancestor_index = dfs_index; + + ecma_value_t module_val = ecma_make_object_value (&module_p->header.object); + ecma_module_stack_item_t *current_p = last_p; + +restart: + /* Entering into processing new node phase. Resolve dependencies first. */ + node_p = current_p->node_p; + + JERRY_ASSERT (ecma_module_get_from_object (module_val)->imports_p == node_p); + + while (node_p != NULL) + { + if (ecma_is_value_object (node_p->u.path_or_module)) { - return ECMA_VALUE_ERROR; + /* Already linked. */ + node_p = node_p->next_p; + continue; } - current_p = current_p->next_p; + JERRY_ASSERT (ecma_is_value_string (node_p->u.path_or_module)); + + ecma_value_t resolve_result = callback (node_p->u.path_or_module, module_val, user_p); + + if (JERRY_UNLIKELY (ecma_is_value_error_reference (resolve_result))) + { + ecma_raise_error_from_error_reference (resolve_result); + goto error; + } + + ecma_module_t *resolved_module_p = ecma_module_get_resolved_module (resolve_result); + + if (resolved_module_p == NULL) + { + ecma_free_value (resolve_result); + ecma_raise_type_error (ECMA_ERR_MSG ("Callback result must be a module")); + goto error; + } + + ecma_deref_ecma_string (ecma_get_string_from_value (node_p->u.path_or_module)); + node_p->u.path_or_module = resolve_result; + ecma_deref_object (ecma_get_object_from_value (resolve_result)); + + if (resolved_module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_ERROR) + { + ecma_raise_type_error (ECMA_ERR_MSG ("Cannot link to a module which is in error state")); + goto error; + } + + node_p = node_p->next_p; } - return ECMA_VALUE_EMPTY; -} /* ecma_module_parse_referenced_modules */ - -/** - * Parses an EcmaScript module. - * - * @return ECMA_VALUE_ERROR - if an error occured - * ECMA_VALUE_EMPTY - otherwise - */ -static ecma_value_t -ecma_module_parse (ecma_module_t *module_p) /**< module */ -{ - if (module_p->state >= ECMA_MODULE_STATE_PARSING) + /* Find next unlinked node, or return to parent */ + while (true) { - return ECMA_VALUE_EMPTY; + ecma_module_t *current_module_p = current_p->module_p; + node_p = current_p->node_p; + + while (node_p != NULL) + { + module_p = ecma_module_get_from_object (node_p->u.path_or_module); + + if (module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_UNLINKED) + { + current_p->node_p = node_p->next_p; + module_p->header.u.class_prop.extra_info = ECMA_MODULE_STATE_LINKING; + + ecma_module_stack_item_t *item_p; + item_p = (ecma_module_stack_item_t *) jmem_heap_alloc_block (sizeof (ecma_module_stack_item_t)); + + dfs_index++; + + item_p->prev_p = last_p; + item_p->parent_p = current_p; + item_p->module_p = module_p; + item_p->node_p = module_p->imports_p; + item_p->dfs_index = dfs_index; + + module_p->header.u.class_prop.u.dfs_ancestor_index = dfs_index; + + last_p = item_p; + current_p = item_p; + module_val = node_p->u.path_or_module; + goto restart; + } + + if (module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_LINKING) + { + uint32_t dfs_ancestor_index = module_p->header.u.class_prop.u.dfs_ancestor_index; + + if (dfs_ancestor_index < current_module_p->header.u.class_prop.u.dfs_ancestor_index) + { + current_module_p->header.u.class_prop.u.dfs_ancestor_index = dfs_ancestor_index; + } + } + + node_p = node_p->next_p; + } + + if (current_module_p->header.u.class_prop.u.dfs_ancestor_index != current_p->dfs_index) + { + current_p = current_p->parent_p; + JERRY_ASSERT (current_p != NULL); + + uint32_t dfs_ancestor_index = current_module_p->header.u.class_prop.u.dfs_ancestor_index; + + if (dfs_ancestor_index < current_p->module_p->header.u.class_prop.u.dfs_ancestor_index) + { + current_p->module_p->header.u.class_prop.u.dfs_ancestor_index = dfs_ancestor_index; + } + continue; + } + + ecma_module_stack_item_t *end_p = current_p->prev_p; + current_p = current_p->parent_p; + + do + { + ecma_module_stack_item_t *prev_p = last_p->prev_p; + + JERRY_ASSERT (last_p->module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_LINKING); + last_p->module_p->header.u.class_prop.extra_info = ECMA_MODULE_STATE_LINKED; + + jmem_heap_free_block (last_p, sizeof (ecma_module_stack_item_t)); + last_p = prev_p; + } + while (last_p != end_p); + + if (current_p == NULL) + { + return ECMA_VALUE_TRUE; + } } - module_p->state = ECMA_MODULE_STATE_PARSING; +error: + JERRY_ASSERT (last_p != NULL); - lit_utf8_size_t module_path_size = ecma_string_get_size (module_p->path_p); - lit_utf8_byte_t *module_path_p = (lit_utf8_byte_t *) jmem_heap_alloc_block (module_path_size + 1); - - lit_utf8_size_t module_path_utf8_size; - module_path_utf8_size = ecma_string_copy_to_utf8_buffer (module_p->path_p, - module_path_p, - module_path_size); - module_path_p[module_path_utf8_size] = LIT_CHAR_NULL; - - size_t source_size = 0; - uint8_t *source_p = jerry_port_read_source ((const char *) module_path_p, &source_size); - - if (source_p == NULL) + do { - jmem_heap_free_block (module_path_p, module_path_size + 1); - return ecma_raise_syntax_error (ECMA_ERR_MSG ("File not found")); + ecma_module_stack_item_t *prev_p = last_p->prev_p; + + JERRY_ASSERT (last_p->module_p->header.u.class_prop.extra_info == ECMA_MODULE_STATE_LINKING); + last_p->module_p->header.u.class_prop.extra_info = ECMA_MODULE_STATE_UNLINKED; + + jmem_heap_free_block (last_p, sizeof (ecma_module_stack_item_t)); + last_p = prev_p; } + while (last_p != NULL); - ecma_module_t *prev_module_p = JERRY_CONTEXT (module_current_p); - JERRY_CONTEXT (module_current_p) = module_p; - -#if JERRY_DEBUGGER && JERRY_PARSER - if (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED) - { - jerry_debugger_send_string (JERRY_DEBUGGER_SOURCE_CODE_NAME, - JERRY_DEBUGGER_NO_SUBTYPE, - module_path_p, - module_path_size - 1); - } -#endif /* JERRY_DEBUGGER && JERRY_PARSER */ - - /* TODO: Improve this code in the future. */ - ecma_parse_options_t parse_options; - parse_options.options = ECMA_PARSE_STRICT_MODE | ECMA_PARSE_MODULE | ECMA_PARSE_HAS_RESOURCE; - parse_options.resource_name_p = module_path_p; - parse_options.resource_name_length = module_path_size; - - ecma_compiled_code_t *bytecode_p = parser_parse_script (NULL, - 0, - (jerry_char_t *) source_p, - source_size, - ECMA_PARSE_STRICT_MODE | ECMA_PARSE_MODULE, - &parse_options); - - JERRY_CONTEXT (module_current_p) = prev_module_p; - jmem_heap_free_block (module_path_p, module_path_size + 1); - jerry_port_release_source (source_p); - - if (JERRY_UNLIKELY (bytecode_p == NULL)) - { - return ECMA_VALUE_ERROR; - } - - if (ECMA_IS_VALUE_ERROR (ecma_module_parse_referenced_modules ())) - { - ecma_bytecode_deref (bytecode_p); - return ECMA_VALUE_ERROR; - } - - module_p->compiled_code_p = bytecode_p; - module_p->state = ECMA_MODULE_STATE_PARSED; - - return ECMA_VALUE_EMPTY; -} /* ecma_module_parse */ + return ECMA_VALUE_ERROR; +} /* ecma_module_link */ /** * Cleans up a list of module names. */ -static void +void ecma_module_release_module_names (ecma_module_names_t *module_name_p) /**< first module name */ { while (module_name_p != NULL) @@ -1073,16 +1049,22 @@ ecma_module_release_module_names (ecma_module_names_t *module_name_p) /**< first /** * Cleans up a list of module nodes. */ -void -ecma_module_release_module_nodes (ecma_module_node_t *module_node_p) /**< first module node */ +static void +ecma_module_release_module_nodes (ecma_module_node_t *module_node_p, /**< first module node */ + bool is_import) /**< free path variable */ { while (module_node_p != NULL) { ecma_module_node_t *next_p = module_node_p->next_p; ecma_module_release_module_names (module_node_p->module_names_p); - jmem_heap_free_block (module_node_p, sizeof (ecma_module_node_t)); + if (is_import && ecma_is_value_string (module_node_p->u.path_or_module)) + { + ecma_deref_ecma_string (ecma_get_string_from_value (module_node_p->u.path_or_module)); + } + + jmem_heap_free_block (module_node_p, sizeof (ecma_module_node_t)); module_node_p = next_p; } } /* ecma_module_release_module_nodes */ @@ -1090,15 +1072,10 @@ ecma_module_release_module_nodes (ecma_module_node_t *module_node_p) /**< first /** * Cleans up and releases a module structure including all referenced modules. */ -static void +void ecma_module_release_module (ecma_module_t *module_p) /**< module */ { - ecma_module_state_t state = module_p->state; - - ecma_deref_ecma_string (module_p->path_p); -#ifndef JERRY_NDEBUG - module_p->path_p = NULL; -#endif /* JERRY_NDEBUG */ + ecma_module_state_t state = (ecma_module_state_t) module_p->header.u.class_prop.extra_info; if (module_p->namespace_object_p != NULL) { @@ -1112,21 +1089,13 @@ ecma_module_release_module (ecma_module_t *module_p) /**< module */ if (state == ECMA_MODULE_STATE_NATIVE) { - goto finished; + return; } - if (state >= ECMA_MODULE_STATE_PARSING) - { - ecma_module_release_module_nodes (module_p->imports_p); - ecma_module_release_module_nodes (module_p->local_exports_p); - ecma_module_release_module_nodes (module_p->indirect_exports_p); - ecma_module_release_module_nodes (module_p->star_exports_p); - } - - if (state == ECMA_MODULE_STATE_ROOT) - { - goto finished; - } + ecma_module_release_module_nodes (module_p->imports_p, true); + ecma_module_release_module_names (module_p->local_exports_p); + ecma_module_release_module_nodes (module_p->indirect_exports_p, false); + ecma_module_release_module_nodes (module_p->star_exports_p, false); if (state >= ECMA_MODULE_STATE_EVALUATING) { @@ -1135,30 +1104,13 @@ ecma_module_release_module (ecma_module_t *module_p) /**< module */ ecma_deref_object (module_p->scope_p); } - if (state >= ECMA_MODULE_STATE_PARSED - && state < ECMA_MODULE_STATE_EVALUATED) + if (module_p->compiled_code_p != NULL) { ecma_bytecode_deref (module_p->compiled_code_p); #ifndef JERRY_NDEBUG module_p->compiled_code_p = NULL; #endif /* JERRY_NDEBUG */ } - -finished: - jmem_heap_free_block (module_p, sizeof (ecma_module_t)); } /* ecma_module_release_module */ -/** - * Cleans up and releases a module list. - */ -void -ecma_module_cleanup (ecma_module_t *head_p) /**< module */ -{ - while (head_p != NULL) - { - ecma_module_t *next_p = head_p->next_p; - ecma_module_release_module (head_p); - head_p = next_p; - } -} /* ecma_module_cleanup */ #endif /* JERRY_MODULE_SYSTEM */ diff --git a/jerry-core/ecma/base/ecma-module.h b/jerry-core/ecma/base/ecma-module.h index 121039a9c..d6c2e5220 100644 --- a/jerry-core/ecma/base/ecma-module.h +++ b/jerry-core/ecma/base/ecma-module.h @@ -40,41 +40,55 @@ typedef struct ecma_module_names */ typedef enum { - ECMA_MODULE_STATE_INIT = 0, /**< module is initialized */ - ECMA_MODULE_STATE_PARSING = 1, /**< module is currently being parsed */ - ECMA_MODULE_STATE_PARSED = 2, /**< module has been parsed */ + ECMA_MODULE_STATE_UNLINKED = 0, /**< module is currently unlinked */ + ECMA_MODULE_STATE_LINKING = 1, /**< module is currently being linked */ + ECMA_MODULE_STATE_LINKED = 2, /**< module has been linked */ ECMA_MODULE_STATE_EVALUATING = 3, /**< module is currently being evaluated */ ECMA_MODULE_STATE_EVALUATED = 4, /**< module has been evaluated */ - ECMA_MODULE_STATE_NATIVE = 5, /**< module is native */ - ECMA_MODULE_STATE_ROOT = 6, /**< module is a root module */ + ECMA_MODULE_STATE_ERROR = 5, /**< error is encountered during module init */ + ECMA_MODULE_STATE_NATIVE = 6, /**< module is native */ } ecma_module_state_t; /** * Module structure storing an instance of a module + * + * Note: + * The imports_p list follows the order of import-from/export-from statements in the source + * code of a module, even if a given module specifier is only used by export-from statements. */ typedef struct ecma_module { + /* Note: state is stored in header.u.class_prop.extra_info */ + ecma_extended_object_t header; /**< header part */ /* TODO(dbatyai): These could be compressed pointers */ - struct ecma_module *next_p; /**< next module in the list */ struct ecma_module_node *imports_p; /**< import requests of the module */ - struct ecma_module_node *local_exports_p; /**< local exports of the module */ + ecma_module_names_t *local_exports_p; /**< local exports of the module */ struct ecma_module_node *indirect_exports_p; /**< indirect exports of the module */ - struct ecma_module_node *star_exports_p; /**< star exports of the module*/ - ecma_string_t *path_p; /**< path of the module */ + struct ecma_module_node *star_exports_p; /**< star exports of the module */ ecma_compiled_code_t *compiled_code_p; /**< compiled code for the module */ ecma_object_t *scope_p; /**< lexical lenvironment of the module */ ecma_object_t *namespace_object_p; /**< namespace object of the module */ - ecma_module_state_t state; /**< evaluation state of the module */ } ecma_module_t; /** * Module node to store imports / exports. + * + * Note: + * Only one module node is created for each module specifier: the names are + * concatenated if the same specifier is used multiple times in the source code. + * However, multiple nodes are created for modules with multiple alias + * (for example ./a.mjs and ././a.mjs can refer to the same module). */ typedef struct ecma_module_node { - struct ecma_module_node *next_p; /**< next linked list node */ + struct ecma_module_node *next_p; /**< next linked list node */ ecma_module_names_t *module_names_p; /**< names of the requested import/export node */ - ecma_module_t *module_request_p; /**< module structure of the requested module */ + + union + { + ecma_value_t path_or_module; /**< imports: module specifier (if string) or module reference (if object) */ + ecma_value_t *module_object_p; /**< non-imports: reference to a path_or_module field in the imports */ + } u; } ecma_module_node_t; /** @@ -106,31 +120,20 @@ typedef struct ecma_module_resolve_stack bool resolving; /**< flag storing wether the current frame started resolving */ } ecma_module_resolve_stack_t; -bool ecma_module_resolve_set_insert (ecma_module_resolve_set_t **set_p, - ecma_module_t *const module_p, - ecma_string_t *const export_name_p); -void ecma_module_resolve_set_cleanup (ecma_module_resolve_set_t *set_p); - -void ecma_module_resolve_stack_push (ecma_module_resolve_stack_t **stack_p, - ecma_module_t *const module_p, - ecma_string_t *const export_name_p); -void ecma_module_resolve_stack_pop (ecma_module_resolve_stack_t **stack_p); - -ecma_string_t *ecma_module_create_normalized_path (const lit_utf8_byte_t *char_p, - lit_utf8_size_t size, - ecma_string_t *const base_path_p); - -ecma_module_t *ecma_module_find_module (ecma_string_t *const path_p); -ecma_module_t *ecma_module_find_native_module (ecma_string_t *const path_p); - -ecma_value_t ecma_module_parse_referenced_modules (void); ecma_value_t ecma_module_initialize (ecma_module_t *module_p); +ecma_module_t *ecma_module_get_resolved_module (ecma_value_t module_val); -void ecma_module_initialize_context (const ecma_parse_options_t *options_p); +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); + +void ecma_module_initialize_context (void); void ecma_module_cleanup_context (void); -void ecma_module_release_module_nodes (ecma_module_node_t *module_node_p); -void ecma_module_cleanup (ecma_module_t *head_p); +void ecma_module_release_module_names (ecma_module_names_t *module_name_p); +void ecma_module_release_module (ecma_module_t *module_p); + #endif /* JERRY_MODULE_SYSTEM */ #endif /* !ECMA_MODULE_H */ diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 981e431b3..1171ce26b 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -261,6 +261,13 @@ jerry_value_t jerry_object_get_property_names (const jerry_value_t obj_val, jerr jerry_value_t jerry_from_property_descriptor (const jerry_property_descriptor_t *src_prop_desc_p); jerry_value_t jerry_to_property_descriptor (jerry_value_t obj_value, jerry_property_descriptor_t *out_prop_desc_p); +/** + * Module functions. + */ + +jerry_value_t jerry_module_link (const jerry_value_t module_val, + jerry_module_resolve_callback_t callback_p, void *user_p); + /** * Promise functions. */ diff --git a/jerry-core/include/jerryscript-port.h b/jerry-core/include/jerryscript-port.h index 99b97d4fe..c219a6106 100644 --- a/jerry-core/include/jerryscript-port.h +++ b/jerry-core/include/jerryscript-port.h @@ -217,39 +217,33 @@ uint8_t *jerry_port_read_source (const char *file_name_p, size_t *out_size_p); void jerry_port_release_source (uint8_t *buffer_p); /** - * Normalize a file path string. + * Default module resolver. * * Note: - * This port function is called by jerry-core when JERRY_MODULE_SYSTEM - * is enabled. The normalized path is used to uniquely identify modules. + * This port function is only used when JERRY_MODULE_SYSTEM is enabled. * - * @param in_path_p Input path as a zero terminated string. - * @param out_buf_p Pointer to the output buffer where the normalized path should be written. - * @param out_buf_size Size of the output buffer. - * @param base_file_p A file path that 'in_path_p' is relative to, usually the current module file. - * A NULL value represents that 'in_path_p' is relative to the current working directory. + * @param specifier Module specifier string. + * @param referrer Parent module. + * @param user_p An unused pointer. * - * @return length of the string written to the output buffer - * zero, if the buffer was not sufficient or an error occured + * @return A module object if resolving is successful, an error otherwise. */ -size_t jerry_port_normalize_path (const char *in_path_p, - char *out_buf_p, - size_t out_buf_size, - char *base_file_p); +jerry_value_t +jerry_port_module_resolve (const jerry_value_t specifier, + const jerry_value_t referrer, + void *user_p); /** - * Get the module object of a native module. + * Release known modules. * * Note: - * This port function is called by jerry-core when JERRY_MODULE_SYSTEM - * is enabled. + * This port function should be called by the user application when + * the module database is no longer needed. * - * @param name String value of the module specifier. - * - * @return Undefined, if 'name' is not a native module - * jerry_value_t containing the module object, otherwise + * @param realm If this argument is object, release only those modules, + * which realm value is equal to this argument. */ -jerry_value_t jerry_port_get_native_module (jerry_value_t name); +void jerry_port_module_release (const jerry_value_t realm); /** * @} diff --git a/jerry-core/include/jerryscript-types.h b/jerry-core/include/jerryscript-types.h index 53a6f28e0..cf9d3d797 100644 --- a/jerry-core/include/jerryscript-types.h +++ b/jerry-core/include/jerryscript-types.h @@ -288,6 +288,13 @@ typedef void (*jerry_object_native_free_callback_t) (void *native_p); */ typedef void (*jerry_error_object_created_callback_t) (const jerry_value_t error_object, void *user_p); +/** + * Callback which is called by jerry_module_link to get the referenced module. + */ +typedef jerry_value_t (*jerry_module_resolve_callback_t) (const jerry_value_t specifier, + const jerry_value_t referrer, + void *user_p); + /** * Callback which tells whether the ECMAScript execution should be stopped. * @@ -307,6 +314,7 @@ typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p); typedef bool (*jerry_object_property_foreach_t) (const jerry_value_t property_name, const jerry_value_t property_value, void *user_data_p); + /** * Function type applied for each object in the engine. */ diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index ded8b1ebf..0e44b7d23 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -148,7 +148,6 @@ struct jerry_context_t #endif /* JERRY_ESNEXT */ #if JERRY_MODULE_SYSTEM - ecma_module_t *module_list_p; /**< current module context */ ecma_module_t *module_current_p; /**< current module context */ #endif /* JERRY_MODULE_SYSTEM */ diff --git a/jerry-core/parser/js/js-parser-internal.h b/jerry-core/parser/js/js-parser-internal.h index 4d3b57cb7..22409f48f 100644 --- a/jerry-core/parser/js/js-parser-internal.h +++ b/jerry-core/parser/js/js-parser-internal.h @@ -551,7 +551,7 @@ typedef struct parser_stack_iterator_t last_statement; /**< last statement position */ #if JERRY_MODULE_SYSTEM - ecma_module_node_t *module_current_node_p; /**< import / export node that is being processed */ + ecma_module_names_t *module_names_p; /**< import / export names that is being processed */ lexer_literal_t *module_identifier_lit_p; /**< the literal for the identifier of the current element */ #endif /* JERRY_MODULE_SYSTEM */ @@ -858,16 +858,14 @@ void parser_free_jumps (parser_stack_iterator_t iterator); */ extern const lexer_lit_location_t lexer_default_literal; -void parser_module_finalize_export_node (parser_context_t *context_p); -void parser_module_finalize_import_node (parser_context_t *context_p); void parser_module_check_request_place (parser_context_t *context_p); void parser_module_context_init (parser_context_t *context_p); -void parser_module_handle_module_specifier (parser_context_t *context_p); +void parser_module_append_names (parser_context_t *context_p, ecma_module_names_t **module_names_p); +void parser_module_handle_module_specifier (parser_context_t *context_p, ecma_module_node_t **node_list_p); void parser_module_handle_requests (parser_context_t *context_p); void parser_module_parse_export_clause (parser_context_t *context_p); void parser_module_parse_import_clause (parser_context_t *context_p); void parser_module_set_default (parser_context_t *context_p); -ecma_module_node_t *parser_module_create_module_node (parser_context_t *context_p); bool parser_module_check_duplicate_import (parser_context_t *context_p, ecma_string_t *local_name_p); bool parser_module_check_duplicate_export (parser_context_t *context_p, ecma_string_t *export_name_p); void parser_module_append_export_name (parser_context_t *context_p); diff --git a/jerry-core/parser/js/js-parser-module.c b/jerry-core/parser/js/js-parser-module.c index 27462de0c..31ccffb4b 100644 --- a/jerry-core/parser/js/js-parser-module.c +++ b/jerry-core/parser/js/js-parser-module.c @@ -43,7 +43,8 @@ bool parser_module_check_duplicate_import (parser_context_t *context_p, /**< parser context */ ecma_string_t *local_name_p) /**< newly imported name */ { - ecma_module_names_t *module_names_p = context_p->module_current_node_p->module_names_p; + ecma_module_names_t *module_names_p = context_p->module_names_p; + while (module_names_p != NULL) { if (ecma_compare_ecma_strings (module_names_p->local_name_p, local_name_p)) @@ -55,6 +56,7 @@ parser_module_check_duplicate_import (parser_context_t *context_p, /**< parser c } ecma_module_node_t *module_node_p = JERRY_CONTEXT (module_current_p)->imports_p; + while (module_node_p != NULL) { module_names_p = module_node_p->module_names_p; @@ -113,7 +115,8 @@ parser_module_check_duplicate_export (parser_context_t *context_p, /**< parser c ecma_string_t *export_name_p) /**< exported identifier */ { /* We have to check in the currently constructed node, as well as all of the already added nodes. */ - ecma_module_names_t *current_names_p = context_p->module_current_node_p->module_names_p; + ecma_module_names_t *current_names_p = context_p->module_names_p; + while (current_names_p != NULL) { if (ecma_compare_ecma_strings (current_names_p->imex_name_p, export_name_p)) @@ -123,27 +126,23 @@ parser_module_check_duplicate_export (parser_context_t *context_p, /**< parser c current_names_p = current_names_p->next_p; } - ecma_module_node_t *export_node_p = JERRY_CONTEXT (module_current_p)->local_exports_p; - if (export_node_p != NULL) + ecma_module_names_t *name_p = JERRY_CONTEXT (module_current_p)->local_exports_p; + + while (name_p != NULL) { - JERRY_ASSERT (export_node_p->next_p == NULL); - ecma_module_names_t *name_p = export_node_p->module_names_p; - - while (name_p != NULL) + if (ecma_compare_ecma_strings (name_p->imex_name_p, export_name_p)) { - if (ecma_compare_ecma_strings (name_p->imex_name_p, export_name_p)) - { - return true; - } - - name_p = name_p->next_p; + return true; } + + name_p = name_p->next_p; } - export_node_p = JERRY_CONTEXT (module_current_p)->indirect_exports_p; + ecma_module_node_t *export_node_p = JERRY_CONTEXT (module_current_p)->indirect_exports_p; + while (export_node_p != NULL) { - ecma_module_names_t *name_p = export_node_p->module_names_p; + name_p = export_node_p->module_names_p; while (name_p != NULL) { @@ -162,107 +161,6 @@ parser_module_check_duplicate_export (parser_context_t *context_p, /**< parser c return false; } /* parser_module_check_duplicate_export */ -/** - * Add export node to parser context. - */ -void -parser_module_finalize_export_node (parser_context_t *context_p) /**< parser context */ -{ - ecma_module_node_t *module_node_p = context_p->module_current_node_p; - context_p->module_current_node_p = NULL; - ecma_module_node_t **export_list_p; - - /* Check which list we should add it to. */ - if (module_node_p->module_request_p) - { - /* If the export node has a module request, that means it's either an indirect export, or a star export. */ - if (!module_node_p->module_names_p) - { - /* If there are no names in the node, then it's a star export. */ - export_list_p = &(JERRY_CONTEXT (module_current_p)->star_exports_p); - } - else - { - export_list_p = &(JERRY_CONTEXT (module_current_p)->indirect_exports_p); - } - } - else - { - /* If there is no module request, then it's a local export. */ - export_list_p = &(JERRY_CONTEXT (module_current_p)->local_exports_p); - } - - /* Check if we have a node with the same module request, append to it if we do. */ - ecma_module_node_t *stored_exports_p = *export_list_p; - while (stored_exports_p != NULL) - { - if (stored_exports_p->module_request_p == module_node_p->module_request_p) - { - ecma_module_names_t *module_names_p = module_node_p->module_names_p; - - if (module_names_p != NULL) - { - while (module_names_p->next_p != NULL) - { - module_names_p = module_names_p->next_p; - } - - module_names_p->next_p = stored_exports_p->module_names_p; - stored_exports_p->module_names_p = module_node_p->module_names_p; - module_node_p->module_names_p = NULL; - } - - jmem_heap_free_block (module_node_p, sizeof (ecma_module_node_t)); - return; - } - - stored_exports_p = stored_exports_p->next_p; - } - - module_node_p->next_p = *export_list_p; - *export_list_p = module_node_p; -} /* parser_module_finalize_export_node */ - -/** - * Add import node to parser context. - */ -void -parser_module_finalize_import_node (parser_context_t *context_p) /**< parser context */ -{ - ecma_module_node_t *module_node_p = context_p->module_current_node_p; - context_p->module_current_node_p = NULL; - ecma_module_node_t *stored_imports_p = JERRY_CONTEXT (module_current_p)->imports_p; - - /* Check if we have a node with the same module request, append to it if we do. */ - while (stored_imports_p != NULL) - { - if (stored_imports_p->module_request_p == module_node_p->module_request_p) - { - ecma_module_names_t *module_names_p = module_node_p->module_names_p; - - if (module_names_p != NULL) - { - while (module_names_p->next_p != NULL) - { - module_names_p = module_names_p->next_p; - } - - module_names_p->next_p = stored_imports_p->module_names_p; - stored_imports_p->module_names_p = module_node_p->module_names_p; - module_node_p->module_names_p = NULL; - } - - jmem_heap_free_block (module_node_p, sizeof (ecma_module_node_t)); - return; - } - - stored_imports_p = stored_imports_p->next_p; - } - - module_node_p->next_p = JERRY_CONTEXT (module_current_p)->imports_p; - JERRY_CONTEXT (module_current_p)->imports_p = module_node_p; -} /* parser_module_finalize_import_node */ - /** * Add module names to current module node. */ @@ -271,37 +169,21 @@ parser_module_add_names_to_node (parser_context_t *context_p, /**< parser contex ecma_string_t *imex_name_p, /**< import/export name */ ecma_string_t *local_name_p) /**< local name */ { - ecma_module_names_t *new_names_p = (ecma_module_names_t *) parser_malloc (context_p, - sizeof (ecma_module_names_t)); - memset (new_names_p, 0, sizeof (ecma_module_names_t)); + ecma_module_names_t *new_name_p = (ecma_module_names_t *) parser_malloc (context_p, + sizeof (ecma_module_names_t)); - ecma_module_node_t *module_node_p = context_p->module_current_node_p; - new_names_p->next_p = module_node_p->module_names_p; - module_node_p->module_names_p = new_names_p; + new_name_p->next_p = context_p->module_names_p; + context_p->module_names_p = new_name_p; JERRY_ASSERT (imex_name_p != NULL); ecma_ref_ecma_string (imex_name_p); - new_names_p->imex_name_p = imex_name_p; + new_name_p->imex_name_p = imex_name_p; JERRY_ASSERT (local_name_p != NULL); ecma_ref_ecma_string (local_name_p); - new_names_p->local_name_p = local_name_p; + new_name_p->local_name_p = local_name_p; } /* parser_module_add_names_to_node */ -/** - * Create a permanent import/export node from a template node. - * @return - the copy of the template if the second parameter is not NULL. - * - otherwise: an empty node. - */ -ecma_module_node_t * -parser_module_create_module_node (parser_context_t *context_p) /**< parser context */ -{ - ecma_module_node_t *node_p = (ecma_module_node_t *) parser_malloc (context_p, sizeof (ecma_module_node_t)); - memset (node_p, 0, sizeof (ecma_module_node_t)); - - return node_p; -} /* parser_module_create_module_node */ - /** * Parse an ExportClause. */ @@ -529,13 +411,41 @@ parser_module_check_request_place (parser_context_t *context_p) /**< parser cont } } /* parser_module_check_request_place */ +/** + * Append names to the names list. + */ +void +parser_module_append_names (parser_context_t *context_p, /**< parser context */ + ecma_module_names_t **module_names_p) /**< target names */ +{ + ecma_module_names_t *last_name_p = context_p->module_names_p; + + if (last_name_p == NULL) + { + return; + } + + if (*module_names_p != NULL) + { + while (last_name_p->next_p != NULL) + { + last_name_p = last_name_p->next_p; + } + + last_name_p->next_p = *module_names_p; + } + + *module_names_p = context_p->module_names_p; + context_p->module_names_p = NULL; +} /* parser_module_append_names */ + /** * Handle module specifier at the end of the import / export statement. */ void -parser_module_handle_module_specifier (parser_context_t *context_p) /**< parser context */ +parser_module_handle_module_specifier (parser_context_t *context_p, /**< parser context */ + ecma_module_node_t **node_list_p) /**< target node list */ { - ecma_module_node_t *module_node_p = context_p->module_current_node_p; if (context_p->token.type != LEXER_LITERAL || context_p->token.lit_location.type != LEXER_STRING_LITERAL || context_p->token.lit_location.length == 0) @@ -545,32 +455,94 @@ parser_module_handle_module_specifier (parser_context_t *context_p) /**< parser lexer_construct_literal_object (context_p, &context_p->token.lit_location, LEXER_STRING_LITERAL); - ecma_string_t *name_p = ecma_new_ecma_string_from_utf8 (context_p->lit_object.literal_p->u.char_p, - context_p->lit_object.literal_p->prop.length); + lexer_literal_t *path_p = context_p->lit_object.literal_p; - ecma_module_t *module_p = ecma_module_find_native_module (name_p); - - if (module_p) - { - ecma_deref_ecma_string (name_p); - goto module_found; - } - - ecma_deref_ecma_string (name_p); - ecma_string_t *path_p = ecma_module_create_normalized_path (context_p->lit_object.literal_p->u.char_p, - context_p->lit_object.literal_p->prop.length, - JERRY_CONTEXT (module_current_p)->path_p); - - if (path_p == NULL) - { - parser_raise_error (context_p, PARSER_ERR_FILE_NOT_FOUND); - } - - module_p = ecma_module_find_module (path_p); - -module_found: - module_node_p->module_request_p = module_p; lexer_next_token (context_p); + + /* The lexer_next_token may throw an error, so the path is constructed after its call. */ + ecma_string_t *path_string_p = ecma_new_ecma_string_from_utf8 (path_p->u.char_p, path_p->prop.length); + + ecma_module_node_t *node_p = JERRY_CONTEXT (module_current_p)->imports_p; + ecma_module_node_t *last_node_p = NULL; + + /* Check if we have an import node with the same module request. */ + + while (node_p != NULL) + { + if (ecma_compare_ecma_strings (ecma_get_string_from_value (node_p->u.path_or_module), path_string_p)) + { + ecma_deref_ecma_string (path_string_p); + break; + } + + last_node_p = node_p; + node_p = node_p->next_p; + } + + if (node_p == NULL) + { + node_p = (ecma_module_node_t *) jmem_heap_alloc_block_null_on_error (sizeof (ecma_module_node_t)); + + if (node_p == NULL) + { + ecma_deref_ecma_string (path_string_p); + parser_raise_error (context_p, PARSER_ERR_OUT_OF_MEMORY); + } + + if (last_node_p == NULL) + { + JERRY_CONTEXT (module_current_p)->imports_p = node_p; + } + else + { + last_node_p->next_p = node_p; + } + + node_p->next_p = NULL; + node_p->module_names_p = NULL; + node_p->u.path_or_module = ecma_make_string_value (path_string_p); + } + + /* Append to imports. */ + if (node_list_p == NULL) + { + parser_module_append_names (context_p, &node_p->module_names_p); + return; + } + + ecma_value_t *module_object_p = &node_p->u.path_or_module; + + node_p = *node_list_p; + last_node_p = NULL; + + while (node_p != NULL) + { + if (node_p->u.module_object_p == module_object_p) + { + parser_module_append_names (context_p, &node_p->module_names_p); + return; + } + + last_node_p = node_p; + node_p = node_p->next_p; + } + + node_p = (ecma_module_node_t *) parser_malloc (context_p, sizeof (ecma_module_node_t)); + + if (last_node_p == NULL) + { + *node_list_p = node_p; + } + else + { + last_node_p->next_p = node_p; + } + + node_p->next_p = NULL; + node_p->module_names_p = context_p->module_names_p; + node_p->u.module_object_p = module_object_p; + + context_p->module_names_p = NULL; } /* parser_module_handle_module_specifier */ #endif /* JERRY_MODULE_SYSTEM */ diff --git a/jerry-core/parser/js/js-parser-statm.c b/jerry-core/parser/js/js-parser-statm.c index 054dae177..6ec85cfbe 100644 --- a/jerry-core/parser/js/js-parser-statm.c +++ b/jerry-core/parser/js/js-parser-statm.c @@ -2425,11 +2425,9 @@ static void 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); parser_module_check_request_place (context_p); - - context_p->module_current_node_p = parser_module_create_module_node (context_p); - lexer_next_token (context_p); /* Check for a ModuleSpecifier*/ @@ -2482,7 +2480,7 @@ parser_parse_import_statement (parser_context_t *context_p) /**< parser context if (context_p->token.type == LEXER_MULTIPLY) { - /* NameSpaceImport*/ + /* NameSpaceImport */ lexer_next_token (context_p); if (!lexer_token_is_identifier (context_p, "as", 2)) { @@ -2527,10 +2525,7 @@ parser_parse_import_statement (parser_context_t *context_p) /**< parser context lexer_next_token (context_p); } - parser_module_handle_module_specifier (context_p); - parser_module_finalize_import_node (context_p); - - context_p->module_current_node_p = NULL; + parser_module_handle_module_specifier (context_p, NULL); } /* parser_parse_import_statement */ /** @@ -2543,11 +2538,10 @@ static bool parser_parse_export_statement (parser_context_t *context_p) /**< context */ { JERRY_ASSERT (context_p->token.type == LEXER_KEYW_EXPORT); + JERRY_ASSERT (context_p->module_names_p == NULL); parser_module_check_request_place (context_p); - context_p->module_current_node_p = parser_module_create_module_node (context_p); - bool consume_last_statement = false; lexer_next_token (context_p); @@ -2628,8 +2622,8 @@ parser_parse_export_statement (parser_context_t *context_p) /**< context */ } lexer_next_token (context_p); - parser_module_handle_module_specifier (context_p); - break; + parser_module_handle_module_specifier (context_p, &(JERRY_CONTEXT (module_current_p)->star_exports_p)); + return false; } case LEXER_KEYW_VAR: case LEXER_KEYW_LET: @@ -2660,7 +2654,8 @@ parser_parse_export_statement (parser_context_t *context_p) /**< context */ if (lexer_token_is_identifier (context_p, "from", 4)) { lexer_next_token (context_p); - parser_module_handle_module_specifier (context_p); + parser_module_handle_module_specifier (context_p, &(JERRY_CONTEXT (module_current_p)->indirect_exports_p)); + return false; } break; } @@ -2672,8 +2667,7 @@ parser_parse_export_statement (parser_context_t *context_p) /**< context */ } context_p->status_flags &= (uint32_t) ~(PARSER_MODULE_DEFAULT_CLASS_OR_FUNC | PARSER_MODULE_STORE_IDENT); - parser_module_finalize_export_node (context_p); - context_p->module_current_node_p = NULL; + parser_module_append_names (context_p, &(JERRY_CONTEXT (module_current_p)->local_exports_p)); return consume_last_statement; } /* parser_parse_export_statement */ diff --git a/jerry-core/parser/js/js-parser.c b/jerry-core/parser/js/js-parser.c index a8aef9d10..88347f8e9 100644 --- a/jerry-core/parser/js/js-parser.c +++ b/jerry-core/parser/js/js-parser.c @@ -1810,7 +1810,7 @@ parser_parse_source (const uint8_t *arg_list_p, /**< function argument list */ context.status_flags |= PARSER_IS_STRICT; } - context.module_current_node_p = NULL; + context.module_names_p = NULL; #endif /* JERRY_MODULE_SYSTEM */ if (arg_list_p != NULL) @@ -2051,9 +2051,9 @@ parser_parse_source (const uint8_t *arg_list_p, /**< function argument list */ scanner_cleanup (&context); #if JERRY_MODULE_SYSTEM - if (context.module_current_node_p != NULL) + if (context.module_names_p != NULL) { - ecma_module_release_module_nodes (context.module_current_node_p); + ecma_module_release_module_names (context.module_names_p); } #endif diff --git a/jerry-main/main-jerry.c b/jerry-main/main-jerry.c index 155de5374..c61913cb0 100644 --- a/jerry-main/main-jerry.c +++ b/jerry-main/main-jerry.c @@ -78,6 +78,43 @@ restart: main_source_t *source_file_p = sources_p + source_index; const char *file_path_p = argv[source_file_p->path_index]; + if (source_file_p->type == SOURCE_MODULE) + { + jerry_value_t specifier = jerry_create_string_from_utf8 ((const jerry_char_t *) file_path_p); + jerry_value_t referrer = jerry_create_undefined (); + ret_value = jerry_port_module_resolve (specifier, referrer, NULL); + jerry_release_value (referrer); + jerry_release_value (specifier); + + if (!jerry_value_is_error (ret_value)) + { + jerry_value_t link_val = jerry_module_link (ret_value, NULL, NULL); + + if (jerry_value_is_error (link_val)) + { + jerry_release_value (ret_value); + ret_value = link_val; + } + else + { + jerry_release_value (link_val); + + jerry_value_t func_val = ret_value; + ret_value = jerry_run (func_val); + jerry_release_value (func_val); + } + } + + if (jerry_value_is_error (ret_value)) + { + main_print_unhandled_exception (ret_value); + goto exit; + } + + jerry_release_value (ret_value); + continue; + } + size_t source_size; uint8_t *source_p = jerry_port_read_source (file_path_p, &source_size); @@ -86,8 +123,6 @@ restart: goto exit; } - uint32_t parse_opts = JERRY_PARSE_NO_OPTS; - switch (source_file_p->type) { case SOURCE_SNAPSHOT: @@ -100,11 +135,6 @@ restart: jerry_port_release_source (source_p); break; } - case SOURCE_MODULE: - { - parse_opts = JERRY_PARSE_MODULE; - /* FALLTHRU */ - } default: { assert (source_file_p->type == SOURCE_SCRIPT @@ -118,7 +148,7 @@ restart: } jerry_parse_options_t parse_options; - parse_options.options = parse_opts | JERRY_PARSE_HAS_RESOURCE; + parse_options.options = JERRY_PARSE_HAS_RESOURCE; parse_options.resource_name_p = (jerry_char_t *) file_path_p; parse_options.resource_name_length = (size_t) strlen (file_path_p); diff --git a/jerry-port/default/default-module.c b/jerry-port/default/default-module.c index 00b4ab4f2..f87599970 100644 --- a/jerry-port/default/default-module.c +++ b/jerry-port/default/default-module.c @@ -13,9 +13,6 @@ * limitations under the License. */ -#if !defined (_WIN32) -#include -#endif /* !defined (_WIN32) */ #include #include #include @@ -104,102 +101,316 @@ jerry_port_release_source (uint8_t *buffer_p) /**< buffer to free */ } /* jerry_port_release_source */ /** - * Normalize a file path + * Computes the end of the directory part of a path. * - * @return length of the path written to the output buffer + * @return end of the directory part of a path. */ -size_t -jerry_port_normalize_path (const char *in_path_p, /**< input file path */ - char *out_buf_p, /**< output buffer */ - size_t out_buf_size, /**< size of output buffer */ - char *base_file_p) /**< base file path */ +static size_t +jerry_port_get_directory_end (const jerry_char_t *path_p) /**< path */ { - size_t ret = 0; + const jerry_char_t *end_p = path_p + strlen ((const char *) path_p); + while (end_p > path_p) + { #if defined (_WIN32) - size_t base_drive_dir_len; - const size_t in_path_len = strnlen (in_path_p, _MAX_PATH); + if (end_p[-1] == '/' || end_p[-1] == '\\') + { + return (size_t) (end_p - path_p); + } +#else /* !_WIN32 */ + if (end_p[-1] == '/') + { + return (size_t) (end_p - path_p); + } +#endif /* _WIN32 */ + + end_p--; + } + + return 0; +} /* jerry_port_get_directory_end */ + +/** + * Normalize a file path. + * + * @return a newly allocated buffer with the normalized path if the operation is successful, + * NULL otherwise + */ +static jerry_char_t * +jerry_port_normalize_path (const jerry_char_t *in_path_p, /**< path to the referenced module */ + size_t in_path_length, /**< length of the path */ + const jerry_char_t *base_path_p, /**< base path */ + size_t base_path_length) /**< length of the base path */ +{ char *path_p; - if (base_file_p != NULL) + if (base_path_length > 0) { - char drive[_MAX_DRIVE]; - char *dir_p = (char *) malloc (_MAX_DIR); + path_p = (char *) malloc (base_path_length + in_path_length + 1); - _splitpath_s (base_file_p, drive, _MAX_DRIVE, dir_p, _MAX_DIR, NULL, 0, NULL, 0); - const size_t drive_len = strnlen (drive, _MAX_DRIVE); - const size_t dir_len = strnlen (dir_p, _MAX_DIR); - base_drive_dir_len = drive_len + dir_len; - path_p = (char *) malloc (base_drive_dir_len + in_path_len + 1); + if (path_p == NULL) + { + return NULL; + } - memcpy (path_p, &drive, drive_len); - memcpy (path_p + drive_len, dir_p, dir_len); - - free (dir_p); + memcpy (path_p, base_path_p, base_path_length); + memcpy (path_p + base_path_length, in_path_p, in_path_length); + path_p[base_path_length + in_path_length] = '\0'; } else { - base_drive_dir_len = 0; - path_p = (char *) malloc (in_path_len + 1); - } + path_p = (char *) malloc (in_path_length + 1); - memcpy (path_p + base_drive_dir_len, in_path_p, in_path_len + 1); - - char *norm_p = _fullpath (out_buf_p, path_p, out_buf_size); - free (path_p); - - if (norm_p != NULL) - { - ret = strnlen (norm_p, out_buf_size); - } -#elif defined (__unix__) || defined (__APPLE__) - char *base_dir_p = dirname (base_file_p); - const size_t base_dir_len = strnlen (base_dir_p, PATH_MAX); - const size_t in_path_len = strnlen (in_path_p, PATH_MAX); - char *path_p = (char *) malloc (base_dir_len + 1 + in_path_len + 1); - - memcpy (path_p, base_dir_p, base_dir_len); - memcpy (path_p + base_dir_len, "/", 1); - memcpy (path_p + base_dir_len + 1, in_path_p, in_path_len + 1); - - char *norm_p = realpath (path_p, NULL); - free (path_p); - - if (norm_p != NULL) - { - const size_t norm_len = strnlen (norm_p, out_buf_size); - if (norm_len < out_buf_size) + if (path_p == NULL) { - memcpy (out_buf_p, norm_p, norm_len + 1); - ret = norm_len; + return NULL; } - free (norm_p); + memcpy (path_p, in_path_p, in_path_length); + path_p[in_path_length] = '\0'; } -#else - (void) base_file_p; /* unused */ - /* Do nothing, just copy the input. */ - const size_t in_path_len = strnlen (in_path_p, out_buf_size); - if (in_path_len < out_buf_size) +#if defined (_WIN32) + char full_path[_MAX_PATH]; + + if (_fullpath (full_path, path_p, _MAX_PATH) != NULL) { - memcpy (out_buf_p, in_path_p, in_path_len + 1); - ret = in_path_len; - } -#endif + free (path_p); - return ret; + size_t full_path_len = strlen (full_path); + + path_p = (char *) malloc (full_path_len + 1); + + if (path_p == NULL) + { + return NULL; + } + + memcpy (path_p, full_path, full_path_len + 1); + } +#elif defined (__unix__) || defined (__APPLE__) + char *norm_p = realpath (path_p, NULL); + + if (norm_p != NULL) + { + free (path_p); + path_p = norm_p; + } +#endif /* _WIN32 */ + + return (jerry_char_t *) path_p; } /* jerry_port_normalize_path */ /** - * Get the module object of a native module. + * A module descriptor. + */ +typedef struct jerry_port_module_t +{ + struct jerry_port_module_t *next_p; /**< next_module */ + jerry_char_t *path_p; /**< path to the module */ + size_t base_path_length; /**< base path length for relative difference */ + jerry_value_t realm; /**< the realm of the module */ + jerry_value_t module; /**< the module itself */ +} jerry_port_module_t; + +/** + * Native info descriptor for modules. + */ +static const jerry_object_native_info_t jerry_port_module_native_info = +{ + .free_cb = NULL, +}; + +/** + * Default module manager. + */ +typedef struct +{ + jerry_port_module_t *module_head_p; /**< first module */ +} jerry_port_module_manager_t; + +/** + * Release known modules. + */ +static void +jerry_port_module_free (jerry_port_module_manager_t *manager_p, /**< module manager */ + const jerry_value_t realm) /**< if this argument is object, release only those modules, + * which realm value is equal to this argument. */ +{ + jerry_port_module_t *module_p = manager_p->module_head_p; + + bool release_all = !jerry_value_is_object (realm); + + jerry_port_module_t *prev_p = NULL; + + while (module_p != NULL) + { + jerry_port_module_t *next_p = module_p->next_p; + + if (release_all || module_p->realm == realm) + { + free (module_p->path_p); + jerry_release_value (module_p->realm); + jerry_release_value (module_p->module); + + free (module_p); + + if (prev_p == NULL) + { + manager_p->module_head_p = next_p; + } + else + { + prev_p->next_p = next_p; + } + } + else + { + prev_p = module_p; + } + + module_p = next_p; + } +} /* jerry_port_module_free */ + +/** + * Initialize the default module manager. + */ +static void +jerry_port_module_manager_init (void *user_data_p) +{ + ((jerry_port_module_manager_t *) user_data_p)->module_head_p = NULL; +} /* jerry_port_module_manager_init */ + +/** + * Deinitialize the default module manager. + */ +static void +jerry_port_module_manager_deinit (void *user_data_p) /**< context pointer to deinitialize */ +{ + jerry_value_t undef = jerry_create_undefined (); + jerry_port_module_free ((jerry_port_module_manager_t *) user_data_p, undef); + jerry_release_value (undef); +} /* jerry_port_module_manager_deinit */ + +/** + * Declare the context data manager for modules. + */ +static const jerry_context_data_manager_t jerry_port_module_manager = +{ + .init_cb = jerry_port_module_manager_init, + .deinit_cb = jerry_port_module_manager_deinit, + .bytes_needed = sizeof (jerry_port_module_manager_t) +}; + +/** + * Default module resolver. * - * @return Undefined, if 'name' is not a native module - * jerry_value_t containing the module object, otherwise + * @return a module object if resolving is successful, an error otherwise */ jerry_value_t -jerry_port_get_native_module (jerry_value_t name) /**< module specifier */ +jerry_port_module_resolve (const jerry_value_t specifier, /**< module specifier string */ + const jerry_value_t referrer, /**< parent module */ + void *user_p) /**< user data */ { - (void) name; - return jerry_create_undefined (); -} /* jerry_port_get_native_module */ + (void) user_p; + + jerry_port_module_t *module_p; + const jerry_char_t *base_path_p = NULL; + size_t base_path_length = 0; + + if (jerry_get_object_native_pointer (referrer, (void **) &module_p, &jerry_port_module_native_info)) + { + base_path_p = module_p->path_p; + base_path_length = module_p->base_path_length; + } + + jerry_size_t in_path_length = jerry_get_utf8_string_size (specifier); + jerry_char_t *in_path_p = (jerry_char_t *) malloc (in_path_length + 1); + jerry_string_to_utf8_char_buffer (specifier, in_path_p, in_path_length); + in_path_p[in_path_length] = '\0'; + + jerry_char_t *path_p = jerry_port_normalize_path (in_path_p, in_path_length, base_path_p, base_path_length); + + if (path_p == NULL) + { + return jerry_create_error (JERRY_ERROR_COMMON, (const jerry_char_t *) "Out of memory"); + } + + jerry_value_t realm = jerry_get_global_object (); + + jerry_port_module_manager_t *manager_p; + manager_p = (jerry_port_module_manager_t *) jerry_get_context_data (&jerry_port_module_manager); + + module_p = manager_p->module_head_p; + + while (module_p != NULL) + { + if (module_p->realm == realm + && strcmp ((const char *) module_p->path_p, (const char *) path_p) == 0) + { + free (path_p); + free (in_path_p); + jerry_release_value (realm); + return jerry_acquire_value (module_p->module); + } + + module_p = module_p->next_p; + } + + size_t source_size; + uint8_t *source_p = jerry_port_read_source ((const char *) path_p, &source_size); + + if (source_p == NULL) + { + free (path_p); + free (in_path_p); + jerry_release_value (realm); + /* TODO: This is incorrect, but makes test262 module tests pass + * (they should throw SyntaxError, but not because the module cannot be found). */ + return jerry_create_error (JERRY_ERROR_SYNTAX, (const jerry_char_t *) "Module file not found"); + } + + jerry_parse_options_t parse_options; + parse_options.options = JERRY_PARSE_MODULE | JERRY_PARSE_HAS_RESOURCE; + parse_options.resource_name_p = (jerry_char_t *) in_path_p; + parse_options.resource_name_length = (size_t) in_path_length; + + jerry_value_t ret_value = jerry_parse (source_p, + source_size, + &parse_options); + + jerry_port_release_source (source_p); + free (in_path_p); + + if (jerry_value_is_error (ret_value)) + { + free (path_p); + jerry_release_value (realm); + return ret_value; + } + + module_p = (jerry_port_module_t *) malloc (sizeof (jerry_port_module_t)); + + module_p->next_p = manager_p->module_head_p; + module_p->path_p = path_p; + module_p->base_path_length = jerry_port_get_directory_end (module_p->path_p); + module_p->realm = realm; + module_p->module = jerry_acquire_value (ret_value); + + jerry_set_object_native_pointer (ret_value, module_p, &jerry_port_module_native_info); + manager_p->module_head_p = module_p; + + return ret_value; +} /* jerry_port_module_resolve */ + +/** + * Release known modules. + */ +void +jerry_port_module_release (const jerry_value_t realm) /**< if this argument is object, release only those modules, + * which realm value is equal to this argument. */ +{ + jerry_port_module_free ((jerry_port_module_manager_t *) jerry_get_context_data (&jerry_port_module_manager), + realm); +} /* jerry_port_module_release */ diff --git a/targets/nuttx-stm32f4/Makefile b/targets/nuttx-stm32f4/Makefile index c63b9a0a4..cee5a0f56 100644 --- a/targets/nuttx-stm32f4/Makefile +++ b/targets/nuttx-stm32f4/Makefile @@ -37,7 +37,7 @@ LIBS = libjerry-core.a libjerry-ext.a libjerry-math.a APPNAME = jerry ASRCS = setjmp.S -CSRCS = jerry_port.c +CSRCS = jerry_port.c jerry_module.c MAINSRC = jerry_main.c .PHONY: copylibs diff --git a/targets/nuttx-stm32f4/jerry_module.c b/targets/nuttx-stm32f4/jerry_module.c new file mode 100644 index 000000000..1e3c5b590 --- /dev/null +++ b/targets/nuttx-stm32f4/jerry_module.c @@ -0,0 +1,302 @@ +/* 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 +#include +#include +#include + +#include "jerryscript.h" +#include "jerryscript-port.h" + +/** + * Computes the end of the directory part of a path. + * + * @return end of the directory part of a path. + */ +static size_t +jerry_port_get_directory_end (const jerry_char_t *path_p) /**< path */ +{ + const jerry_char_t *end_p = path_p + strlen ((const char *) path_p); + + while (end_p > path_p) + { + if (end_p[-1] == '/') + { + return (size_t) (end_p - path_p); + } + + end_p--; + } + + return 0; +} /* jerry_port_get_directory_end */ + +/** + * Normalize a file path. + * + * @return a newly allocated buffer with the normalized path if the operation is successful, + * NULL otherwise + */ +static jerry_char_t * +jerry_port_normalize_path (const jerry_char_t *in_path_p, /**< path to the referenced module */ + size_t in_path_length, /**< length of the path */ + const jerry_char_t *base_path_p, /**< base path */ + size_t base_path_length) /**< length of the base path */ +{ + char *path_p; + + if (base_path_length > 0) + { + path_p = (char *) malloc (base_path_length + in_path_length + 1); + + if (path_p == NULL) + { + return NULL; + } + + memcpy (path_p, base_path_p, base_path_length); + memcpy (path_p + base_path_length, in_path_p, in_path_length); + path_p[base_path_length + in_path_length] = '\0'; + } + else + { + path_p = (char *) malloc (in_path_length + 1); + + if (path_p == NULL) + { + return NULL; + } + + memcpy (path_p, in_path_p, in_path_length); + path_p[in_path_length] = '\0'; + } + + return (jerry_char_t *) path_p; +} /* jerry_port_normalize_path */ + +/** + * A module descriptor. + */ +typedef struct jerry_port_module_t +{ + struct jerry_port_module_t *next_p; /**< next_module */ + jerry_char_t *path_p; /**< path to the module */ + size_t base_path_length; /**< base path length for relative difference */ + jerry_value_t realm; /**< the realm of the module */ + jerry_value_t module; /**< the module itself */ +} jerry_port_module_t; + +/** + * Native info descriptor for modules. + */ +static const jerry_object_native_info_t jerry_port_module_native_info = +{ + .free_cb = NULL, +}; + +/** + * Default module manager. + */ +typedef struct +{ + jerry_port_module_t *module_head_p; /**< first module */ +} jerry_port_module_manager_t; + +/** + * Release known modules. + */ +static void +jerry_port_module_free (jerry_port_module_manager_t *manager_p, /**< module manager */ + const jerry_value_t realm) /**< if this argument is object, release only those modules, + * which realm value is equal to this argument. */ +{ + jerry_port_module_t *module_p = manager_p->module_head_p; + + bool release_all = !jerry_value_is_object (realm); + + jerry_port_module_t *prev_p = NULL; + + while (module_p != NULL) + { + jerry_port_module_t *next_p = module_p->next_p; + + if (release_all || module_p->realm == realm) + { + free (module_p->path_p); + jerry_release_value (module_p->realm); + jerry_release_value (module_p->module); + + free (module_p); + + if (prev_p == NULL) + { + manager_p->module_head_p = next_p; + } + else + { + prev_p->next_p = next_p; + } + } + else + { + prev_p = module_p; + } + + module_p = next_p; + } +} /* jerry_port_module_free */ + +/** + * Initialize the default module manager. + */ +static void +jerry_port_module_manager_init (void *user_data_p) +{ + ((jerry_port_module_manager_t *) user_data_p)->module_head_p = NULL; +} /* jerry_port_module_manager_init */ + +/** + * Deinitialize the default module manager. + */ +static void +jerry_port_module_manager_deinit (void *user_data_p) /**< context pointer to deinitialize */ +{ + jerry_value_t undef = jerry_create_undefined (); + jerry_port_module_free ((jerry_port_module_manager_t *) user_data_p, undef); + jerry_release_value (undef); +} /* jerry_port_module_manager_deinit */ + +/** + * Declare the context data manager for modules. + */ +static const jerry_context_data_manager_t jerry_port_module_manager = +{ + .init_cb = jerry_port_module_manager_init, + .deinit_cb = jerry_port_module_manager_deinit, + .bytes_needed = sizeof (jerry_port_module_manager_t) +}; + +/** + * Default module resolver. + * + * @return a module object if resolving is successful, an error otherwise + */ +jerry_value_t +jerry_port_module_resolve (const jerry_value_t specifier, /**< module specifier string */ + const jerry_value_t referrer, /**< parent module */ + void *user_p) /**< user data */ +{ + (void) user_p; + + jerry_port_module_t *module_p; + const jerry_char_t *base_path_p = NULL; + size_t base_path_length = 0; + + if (jerry_get_object_native_pointer (referrer, (void **) &module_p, &jerry_port_module_native_info)) + { + base_path_p = module_p->path_p; + base_path_length = module_p->base_path_length; + } + + jerry_size_t in_path_length = jerry_get_utf8_string_size (specifier); + jerry_char_t *in_path_p = (jerry_char_t *) malloc (in_path_length + 1); + jerry_string_to_utf8_char_buffer (specifier, in_path_p, in_path_length); + in_path_p[in_path_length] = '\0'; + + jerry_char_t *path_p = jerry_port_normalize_path (in_path_p, in_path_length, base_path_p, base_path_length); + + if (path_p == NULL) + { + return jerry_create_error (JERRY_ERROR_COMMON, (const jerry_char_t *) "Out of memory"); + } + + jerry_value_t realm = jerry_get_global_object (); + + jerry_port_module_manager_t *manager_p; + manager_p = (jerry_port_module_manager_t *) jerry_get_context_data (&jerry_port_module_manager); + + module_p = manager_p->module_head_p; + + while (module_p != NULL) + { + if (module_p->realm == realm + && strcmp ((const char *) module_p->path_p, (const char *) path_p) == 0) + { + free (path_p); + free (in_path_p); + jerry_release_value (realm); + return jerry_acquire_value (module_p->module); + } + + module_p = module_p->next_p; + } + + size_t source_size; + uint8_t *source_p = jerry_port_read_source ((const char *) path_p, &source_size); + + if (source_p == NULL) + { + free (path_p); + free (in_path_p); + jerry_release_value (realm); + /* TODO: This is incorrect, but makes test262 module tests pass + * (they should throw SyntaxError, but not because the module cannot be found). */ + return jerry_create_error (JERRY_ERROR_SYNTAX, (const jerry_char_t *) "Module file not found"); + } + + jerry_parse_options_t parse_options; + parse_options.options = JERRY_PARSE_MODULE | JERRY_PARSE_HAS_RESOURCE; + parse_options.resource_name_p = (jerry_char_t *) in_path_p; + parse_options.resource_name_length = (size_t) in_path_length; + + jerry_value_t ret_value = jerry_parse (source_p, + source_size, + &parse_options); + + jerry_port_release_source (source_p); + free (in_path_p); + + if (jerry_value_is_error (ret_value)) + { + free (path_p); + jerry_release_value (realm); + return ret_value; + } + + module_p = (jerry_port_module_t *) malloc (sizeof (jerry_port_module_t)); + + module_p->next_p = manager_p->module_head_p; + module_p->path_p = path_p; + module_p->base_path_length = jerry_port_get_directory_end (module_p->path_p); + module_p->realm = realm; + module_p->module = jerry_acquire_value (ret_value); + + jerry_set_object_native_pointer (ret_value, module_p, &jerry_port_module_native_info); + manager_p->module_head_p = module_p; + + return ret_value; +} /* jerry_port_module_resolve */ + +/** + * Release known modules. + */ +void +jerry_port_module_release (const jerry_value_t realm) /**< if this argument is object, release only those modules, + * which realm value is equal to this argument. */ +{ + jerry_port_module_free ((jerry_port_module_manager_t *) jerry_get_context_data (&jerry_port_module_manager), + realm); +} /* jerry_port_module_release */ diff --git a/targets/nuttx-stm32f4/jerry_port.c b/targets/nuttx-stm32f4/jerry_port.c index bdf21f7dd..6814fbad3 100644 --- a/targets/nuttx-stm32f4/jerry_port.c +++ b/targets/nuttx-stm32f4/jerry_port.c @@ -126,42 +126,6 @@ jerry_port_release_source (uint8_t *buffer_p) /**< buffer to free */ free (buffer_p); } /* jerry_port_release_source */ -/** - * Normalize a file path - * - * @return length of the path written to the output buffer - */ -size_t -jerry_port_normalize_path (const char *in_path_p, /**< input file path */ - char *out_buf_p, /**< output buffer */ - size_t out_buf_size, /**< size of output buffer */ - char *base_file_p) /**< base file path */ -{ - (void) base_file_p; - - size_t len = strlen (in_path_p); - if (len + 1 > out_buf_size) - { - return 0; - } - - /* Return the original string. */ - strcpy (out_buf_p, in_path_p); - return len; -} /* jerry_port_normalize_path */ - -/** - * Get the module object of a native module. - * - * @return undefined - */ -jerry_value_t -jerry_port_get_native_module (jerry_value_t name) /**< module specifier */ -{ - (void) name; - return jerry_create_undefined (); -} /* jerry_port_get_native_module */ - /** * Dummy function to get the time zone adjustment. * diff --git a/tests/unit-core/CMakeLists.txt b/tests/unit-core/CMakeLists.txt index 4603f6911..45b646d5e 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.c test-native-callback-nested.c test-native-instanceof.c test-native-pointer.c diff --git a/tests/unit-core/test-module.c b/tests/unit-core/test-module.c new file mode 100644 index 000000000..fae114218 --- /dev/null +++ b/tests/unit-core/test-module.c @@ -0,0 +1,185 @@ +/* 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 + +#include "jerryscript.h" +#include "test-common.h" + +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 +create_module (int id) /**< module id */ +{ + jerry_parse_options_t module_parse_options; + module_parse_options.options = JERRY_PARSE_MODULE; + + jerry_value_t result; + + if (id == 0) + { + result = jerry_parse ((jerry_char_t *) "", 0, &module_parse_options); + } + else + { + jerry_char_t source[] = "import a from 'XX_module.mjs'"; + + TEST_ASSERT (id >= 1 && id <= 99 && source[15] == 'X' && source[16] == 'X'); + + source[15] = (jerry_char_t) ((id / 10) + '0'); + source[16] = (jerry_char_t) ((id % 10) + '0'); + + result = jerry_parse (source, sizeof (source) - 1, &module_parse_options); + } + + TEST_ASSERT (!jerry_value_is_error (result)); + return result; +} /* create_module */ + +static int counter = 0; +static jerry_value_t module; + +static jerry_value_t +resolve_callback1 (const jerry_value_t specifier, /**< module specifier */ + const jerry_value_t referrer, /**< parent module */ + void *user_p) /**< user data */ +{ + TEST_ASSERT (user_p == (void *) &module); + TEST_ASSERT (referrer == module); + compare_specifier (specifier, 1); + + counter++; + return counter == 1 ? jerry_create_number (7) : jerry_create_object (); +} /* resolve_callback1 */ + +static jerry_value_t prev_module; +static bool terminate_with_error; + +static jerry_value_t +resolve_callback2 (const jerry_value_t specifier, /**< module specifier */ + const jerry_value_t referrer, /**< parent module */ + void *user_p) /**< user data */ +{ + TEST_ASSERT (prev_module == referrer); + TEST_ASSERT (user_p == NULL); + + compare_specifier (specifier, ++counter); + + if (counter >= 32) + { + if (terminate_with_error) + { + return jerry_create_error (JERRY_ERROR_RANGE, (const jerry_char_t *) "Module not found"); + } + + return create_module (0); + } + + prev_module = create_module (counter + 1); + return prev_module; +} /* resolve_callback2 */ + +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; + } + + jerry_value_t number = jerry_create_number (5); + jerry_value_t object = jerry_create_object (); + + jerry_value_t result = jerry_module_link (number, resolve_callback1, NULL); + TEST_ASSERT (jerry_value_is_error (result)); + jerry_release_value (result); + + result = jerry_module_link (object, resolve_callback1, NULL); + TEST_ASSERT (jerry_value_is_error (result)); + jerry_release_value (result); + + module = create_module (1); + + /* After an error, module must remain in unlinked mode. */ + result = jerry_module_link (module, resolve_callback1, (void *) &module); + TEST_ASSERT (jerry_value_is_error (result)); + TEST_ASSERT (counter == 1); + jerry_release_value (result); + + result = jerry_module_link (module, resolve_callback1, (void *) &module); + TEST_ASSERT (jerry_value_is_error (result)); + TEST_ASSERT (counter == 2); + jerry_release_value (result); + + prev_module = module; + counter = 0; + terminate_with_error = true; + result = jerry_module_link (module, resolve_callback2, NULL); + TEST_ASSERT (jerry_value_is_error (result)); + TEST_ASSERT (counter == 32); + jerry_release_value (result); + + /* The successfully resolved modules is kept around in unlinked state. */ + jerry_gc (JERRY_GC_PRESSURE_HIGH); + + counter = 31; + terminate_with_error = false; + result = jerry_module_link (module, resolve_callback2, NULL); + TEST_ASSERT (jerry_value_is_boolean (result) && jerry_get_boolean_value (result)); + TEST_ASSERT (counter == 32); + jerry_release_value (result); + jerry_release_value (module); + + module = create_module (1); + + prev_module = module; + counter = 0; + terminate_with_error = false; + result = jerry_module_link (module, resolve_callback2, NULL); + TEST_ASSERT (jerry_value_is_boolean (result) && jerry_get_boolean_value (result)); + TEST_ASSERT (counter == 32); + jerry_release_value (result); + jerry_release_value (module); + + jerry_release_value (object); + jerry_release_value (number); + + jerry_cleanup (); + + return 0; +} /* main */