From 6dc2764a945b2ef7873fa2438bba9d6822512017 Mon Sep 17 00:00:00 2001 From: "Gabriel \"_|Nix|_\" Schulhof" Date: Thu, 3 May 2018 21:12:17 -0400 Subject: [PATCH] modules: add ability to clear cache (#2300) This adds the ability to remove a single module from the cache, or to clear the entire module cache. JerryScript-DCO-1.0-Signed-off-by: Gabriel Schulhof gabriel.schulhof@intel.com --- docs/12.EXT-REFERENCE-MODULE.md | 27 +++- jerry-ext/include/jerryscript-ext/module.h | 7 + jerry-ext/module/module.c | 163 ++++++++++++++------- tests/unit-ext/module/jerry-module-test.c | 51 ++++++- 4 files changed, 186 insertions(+), 62 deletions(-) diff --git a/docs/12.EXT-REFERENCE-MODULE.md b/docs/12.EXT-REFERENCE-MODULE.md index 0abbe04f1..67e4447f2 100644 --- a/docs/12.EXT-REFERENCE-MODULE.md +++ b/docs/12.EXT-REFERENCE-MODULE.md @@ -27,6 +27,11 @@ The purpose of having resolvers is to be able to account for the fact that diffe differently and thus, for each type of module a module resolver must be supplied at the point where an instance of that type of module is requested. +Individual modules may be removed from the cache by calling `jerryx_module_clear_cache`. This function behaves +identically to `jerryx_module_resolve` in that it first checks the cache for the requested module, except that it +removes the module if found. Additionally, it clears the entire cache of all modules if called using a JavaScript value +of `undefined` as its first parameter. + Additionally, this extension provides a means of easily defining so-called "native" JerryScript modules which can be resolved using the native JerryScript module resolver `jerryx_module_native_resolver`, which can be passed to `jerryx_module_resolve()`. Native modules are registered during application startup and by calling `dlopen()` by means @@ -56,7 +61,7 @@ to `jerryx_module_resolve` with a module name whose canonical name matches an al ```c jerry_value_t -jerryx_module_resolve (const jerry_char_t *name, +jerryx_module_resolve (const jerry_value_t name, const jerryx_module_resolver_t *resolvers_p, size_t resolver_count); ``` @@ -67,6 +72,26 @@ jerryx_module_resolve (const jerry_char_t *name, - return value - `jerry_value_t` representing the module that was loaded, or the error that occurred in the process. +## jerryx_module_clear_cache + +**Summary** + +Remove a module from the current context's cache, or clear the cache entirely. + +**Prototype** + +```c +void +jerryx_module_clear_cache (const jerry_value_t name, + const jerryx_module_resolver_t *resolvers_p, + size_t resolver_count); +``` + +- `name` - the name of the module to remove from cache or a JavaScript `undefined` to clear the entire cache +- `resolvers_p` - the list of resolvers to call in sequence +- `resolver_count` - the number of resolvers in `resolvers_p` + + ## jerryx_module_native_resolver **Summary** diff --git a/jerry-ext/include/jerryscript-ext/module.h b/jerry-ext/include/jerryscript-ext/module.h index 21a644d5b..1bb76def0 100644 --- a/jerry-ext/include/jerryscript-ext/module.h +++ b/jerry-ext/include/jerryscript-ext/module.h @@ -134,6 +134,13 @@ jerry_value_t jerryx_module_resolve (const jerry_value_t name, const jerryx_module_resolver_t **resolvers, size_t count); +/** + * Delete a module from the cache or, if name has the JavaScript value of undefined, clear the entire cache. + */ +void jerryx_module_clear_cache (const jerry_value_t name, + const jerryx_module_resolver_t **resolvers, + size_t count); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/jerry-ext/module/module.c b/jerry-ext/module/module.c index 4871715c7..2593735c7 100644 --- a/jerry-ext/module/module.c +++ b/jerry-ext/module/module.c @@ -123,7 +123,10 @@ jerryx_module_check_cache (jerry_value_t cache, /**< cache from which to attempt /* If the module is indeed in the cache, we return it. */ if (has_property) { - (*result) = jerry_get_property (cache, module_name); + if (result != NULL) + { + (*result) = jerry_get_property (cache, module_name); + } ret = true; } } @@ -201,6 +204,86 @@ jerryx_module_resolver_t jerryx_module_native_resolver = .resolve_p = jerryx_resolve_native_module }; + +static void +jerryx_module_resolve_local (const jerry_value_t name, /**< name of the module to load */ + const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */ + size_t resolver_count, /**< number of resolvers in @p resolvers */ + jerry_value_t *result) /**< location to store the result, or NULL to remove the module */ +{ + size_t index; + size_t canonical_names_used = 0; + jerry_value_t instances; + jerry_value_t canonical_names[resolver_count]; + jerry_value_t (*get_canonical_name_p) (const jerry_value_t name); + bool (*resolve_p) (const jerry_value_t canonical_name, + jerry_value_t *result); + + if (!jerry_value_is_string (name)) + { + if (result != NULL) + { + *result = jerryx_module_create_error (JERRY_ERROR_COMMON, module_name_not_string, name); + } + goto done; + } + + instances = *(jerry_value_t *) jerry_get_context_data (&jerryx_module_manager); + + /** + * Establish the canonical name for the requested module. Each resolver presents its own canonical name. If one of + * the canonical names matches a cached module, it is returned as the result. + */ + for (index = 0; index < resolver_count; index++) + { + get_canonical_name_p = (resolvers_p[index] == NULL ? NULL : resolvers_p[index]->get_canonical_name_p); + canonical_names[index] = ((get_canonical_name_p == NULL) ? jerry_acquire_value (name) + : get_canonical_name_p (name)); + canonical_names_used++; + if (jerryx_module_check_cache (instances, canonical_names[index], result)) + { + /* A NULL for result indicates that we are to delete the module from the cache if found. Let's do that here.*/ + if (result == NULL) + { + jerry_delete_property (instances, canonical_names[index]); + } + goto done; + } + } + + if (result == NULL) + { + goto done; + } + + /** + * Past this point we assume a module is wanted, and therefore result is not NULL. So, we try each resolver until one + * manages to resolve the module. + */ + for (index = 0; index < resolver_count; index++) + { + resolve_p = (resolvers_p[index] == NULL ? NULL : resolvers_p[index]->resolve_p); + if (resolve_p != NULL && resolve_p (canonical_names[index], result)) + { + if (!jerry_value_is_error (*result)) + { + *result = jerryx_module_add_to_cache (instances, canonical_names[index], *result); + } + goto done; + } + } + + /* If none of the resolvers manage to find the module, complain with "Module not found" */ + *result = jerryx_module_create_error (JERRY_ERROR_COMMON, module_not_found, name); + +done: + /* Release the canonical names as returned by the various resolvers. */ + for (index = 0; index < canonical_names_used; index++) + { + jerry_release_value (canonical_names[index]); + } +} /* jerryx_module_resolve_local */ + /** * Resolve a single module using the module resolvers available in the section declared above and load it into the * current context. @@ -219,61 +302,27 @@ jerryx_module_resolve (const jerry_value_t name, /**< name of the module to load const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */ size_t resolver_count) /**< number of resolvers in @p resolvers */ { - size_t index; - size_t canonical_names_used = 0; - jerry_value_t ret; - jerry_value_t instances; - jerry_value_t canonical_names[resolver_count]; - jerry_value_t (*get_canonical_name_p) (const jerry_value_t name); - bool (*resolve_p) (const jerry_value_t canonical_name, - jerry_value_t *result); - - if (!jerry_value_is_string (name)) - { - ret = jerryx_module_create_error (JERRY_ERROR_COMMON, module_name_not_string, name); - goto done; - } - - instances = *(jerry_value_t *) jerry_get_context_data (&jerryx_module_manager); - - /** - * Establish the canonical name for the requested module. Each resolver presents its own canonical name. If one of - * the canonical names matches a cached module, it is returned as the result. - */ - for (index = 0; index < resolver_count; index++) - { - get_canonical_name_p = (resolvers_p[index] == NULL ? NULL : resolvers_p[index]->get_canonical_name_p); - canonical_names[index] = ((get_canonical_name_p == NULL) ? jerry_acquire_value (name) - : get_canonical_name_p (name)); - canonical_names_used++; - if (jerryx_module_check_cache (instances, canonical_names[index], &ret)) - { - goto done; - } - } - - /* Try each resolver until one manages to find the module. */ - for (index = 0; index < resolver_count; index++) - { - resolve_p = (resolvers_p[index] == NULL ? NULL : resolvers_p[index]->resolve_p); - if (resolve_p != NULL && resolve_p (canonical_names[index], &ret)) - { - if (!jerry_value_is_error (ret)) - { - ret = jerryx_module_add_to_cache (instances, canonical_names[index], ret); - } - goto done; - } - } - - /* If none of the resolvers manage to find the module, complain with "Module not found" */ - ret = jerryx_module_create_error (JERRY_ERROR_COMMON, module_not_found, name); - -done: - /* Release the canonical names as returned by the various resolvers. */ - for (index = 0; index < canonical_names_used; index++) - { - jerry_release_value (canonical_names[index]); - } + /* Set to zero to circumvent fatal warning. */ + jerry_value_t ret = 0; + jerryx_module_resolve_local (name, resolvers_p, resolver_count, &ret); return ret; } /* jerryx_module_resolve */ + +void +jerryx_module_clear_cache (const jerry_value_t name, /**< name of the module to remove, or undefined */ + const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */ + size_t resolver_count) /**< number of resolvers in @p resolvers */ +{ + void *instances_p = jerry_get_context_data (&jerryx_module_manager); + + if (jerry_value_is_undefined (name)) + { + /* We were requested to clear the entire cache, so we bounce the context data in the most agnostic way possible. */ + jerryx_module_manager.deinit_cb (instances_p); + jerryx_module_manager.init_cb (instances_p); + return; + } + + /* Delete the requested module from the cache if it's there. */ + jerryx_module_resolve_local (name, resolvers_p, resolver_count, NULL); +} /* jerryx_module_clear_cache */ diff --git a/tests/unit-ext/module/jerry-module-test.c b/tests/unit-ext/module/jerry-module-test.c index 736f4a1de..108fba262 100644 --- a/tests/unit-ext/module/jerry-module-test.c +++ b/tests/unit-ext/module/jerry-module-test.c @@ -60,6 +60,24 @@ const char eval_string5[] = " return x === y ? 1 : 0;" "}) ();"; +/* Make sure the result of a module load is removed from the cache. */ +const char eval_string6[] = +"(function() {" +" var x = require('cache-check');" +" clear_require_cache('cache-check');" +" var y = require('cache-check');" +" return x !== y ? 1 : 0;" +"}) ();"; + +/* Make sure the entire cache is cleared. */ +const char eval_string7[] = +"(function() {" +" var x = require('cache-check');" +" clear_require_cache(undefined);" +" var y = require('cache-check');" +" return x !== y ? 1 : 0;" +"}) ();"; + /* * Define a resolver for a module named "differently-handled-module" to check that custom resolvers work. */ @@ -119,6 +137,22 @@ static const jerryx_module_resolver_t *resolvers[3] = &cache_check_resolver }; +static jerry_value_t +handle_clear_require_cache (const jerry_value_t js_function, + const jerry_value_t this_val, + const jerry_value_t args_p[], + const jerry_length_t args_count) +{ + (void) js_function; + (void) this_val; + (void) args_count; + + TEST_ASSERT (args_count == 1); + jerryx_module_clear_cache (args_p[0], resolvers, 3); + + return 0; +} /* handle_clear_require_cache */ + static jerry_value_t handle_require (const jerry_value_t js_function, const jerry_value_t this_val, @@ -172,19 +206,28 @@ main (int argc, char **argv) jerry_init (JERRY_INIT_EMPTY); js_global = jerry_get_global_object (); + js_function = jerry_create_external_function (handle_require); js_property_name = jerry_create_string ((const jerry_char_t *) "require"); jerry_set_property (js_global, js_property_name, js_function); + jerry_release_value (js_property_name); + jerry_release_value (js_function); + + js_function = jerry_create_external_function (handle_clear_require_cache); + js_property_name = jerry_create_string ((const jerry_char_t *) "clear_require_cache"); + jerry_set_property (js_global, js_property_name, js_function); + jerry_release_value (js_property_name); + jerry_release_value (js_function); + + jerry_release_value (js_global); eval_one (eval_string1, 42); eval_one (eval_string2, 29); eval_one (eval_string3, 1); eval_one (eval_string4, 1); eval_one (eval_string5, 1); - - jerry_release_value (js_property_name); - jerry_release_value (js_function); - jerry_release_value (js_global); + eval_one (eval_string6, 1); + eval_one (eval_string7, 1); jerry_cleanup (); } /* main */