1 | /* Copyright JS Foundation and other contributors, http://js.foundation |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); |
4 | * you may not use this file except in compliance with the License. |
5 | * You may obtain a copy of the License at |
6 | * |
7 | * http://www.apache.org/licenses/LICENSE-2.0 |
8 | * |
9 | * Unless required by applicable law or agreed to in writing, software |
10 | * distributed under the License is distributed on an "AS IS" BASIS |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | * See the License for the specific language governing permissions and |
13 | * limitations under the License. |
14 | */ |
15 | |
16 | #include <string.h> |
17 | #include "jerryscript.h" |
18 | #include "jerryscript-ext/module.h" |
19 | |
20 | static const jerry_char_t *module_name_property_name = (jerry_char_t *) "moduleName" ; |
21 | static const jerry_char_t *module_not_found = (jerry_char_t *) "Module not found" ; |
22 | static const jerry_char_t *module_name_not_string = (jerry_char_t *) "Module name is not a string" ; |
23 | |
24 | /** |
25 | * Create an error related to modules |
26 | * |
27 | * Creates an error object of the requested type with the additional property "moduleName" the value of which is a |
28 | * string containing the name of the module that was requested when the error occurred. |
29 | * |
30 | * @return the error |
31 | */ |
32 | static jerry_value_t |
33 | jerryx_module_create_error (jerry_error_t error_type, /**< the type of error to create */ |
34 | const jerry_char_t *message, /**< the error message */ |
35 | const jerry_value_t module_name) /**< the module name */ |
36 | { |
37 | jerry_value_t ret = jerry_create_error (error_type, message); |
38 | |
39 | jerry_value_t error_object = jerry_get_value_from_error (ret, false); |
40 | jerry_value_t property_name = jerry_create_string (module_name_property_name); |
41 | |
42 | jerry_release_value (jerry_set_property (error_object, property_name, module_name)); |
43 | |
44 | jerry_release_value (property_name); |
45 | jerry_release_value (error_object); |
46 | return ret; |
47 | } /* jerryx_module_create_error */ |
48 | |
49 | /** |
50 | * Initialize the module manager extension. |
51 | */ |
52 | static void |
53 | jerryx_module_manager_init (void *user_data_p) |
54 | { |
55 | *((jerry_value_t *) user_data_p) = jerry_create_object (); |
56 | } /* jerryx_module_manager_init */ |
57 | |
58 | /** |
59 | * Deinitialize the module manager extension. |
60 | */ |
61 | static void |
62 | jerryx_module_manager_deinit (void *user_data_p) /**< context pointer to deinitialize */ |
63 | { |
64 | jerry_release_value (*(jerry_value_t *) user_data_p); |
65 | } /* jerryx_module_manager_deinit */ |
66 | |
67 | /** |
68 | * Declare the context data manager for modules. |
69 | */ |
70 | static const jerry_context_data_manager_t jerryx_module_manager = |
71 | { |
72 | .init_cb = jerryx_module_manager_init, |
73 | .deinit_cb = jerryx_module_manager_deinit, |
74 | .bytes_needed = sizeof (jerry_value_t) |
75 | }; |
76 | |
77 | /** |
78 | * Global static entry point to the linked list of available modules. |
79 | */ |
80 | static jerryx_native_module_t *first_module_p = NULL; |
81 | |
82 | void jerryx_native_module_register (jerryx_native_module_t *module_p) |
83 | { |
84 | module_p->next_p = first_module_p; |
85 | first_module_p = module_p; |
86 | } /* jerryx_native_module_register */ |
87 | |
88 | void jerryx_native_module_unregister (jerryx_native_module_t *module_p) |
89 | { |
90 | jerryx_native_module_t *parent_p = NULL, *iter_p = NULL; |
91 | |
92 | for (iter_p = first_module_p; iter_p != NULL; parent_p = iter_p, iter_p = iter_p->next_p) |
93 | { |
94 | if (iter_p == module_p) |
95 | { |
96 | if (parent_p) |
97 | { |
98 | parent_p->next_p = module_p->next_p; |
99 | } |
100 | else |
101 | { |
102 | first_module_p = module_p->next_p; |
103 | } |
104 | module_p->next_p = NULL; |
105 | } |
106 | } |
107 | } /* jerryx_native_module_unregister */ |
108 | |
109 | /** |
110 | * Attempt to retrieve a module by name from a cache, and return false if not found. |
111 | */ |
112 | static bool |
113 | jerryx_module_check_cache (jerry_value_t cache, /**< cache from which to attempt to retrieve the module by name */ |
114 | jerry_value_t module_name, /**< JerryScript string value holding the module name */ |
115 | jerry_value_t *result) /**< Resulting value */ |
116 | { |
117 | bool ret = false; |
118 | |
119 | /* Check if the cache has the module. */ |
120 | jerry_value_t js_has_property = jerry_has_property (cache, module_name); |
121 | |
122 | /* If we succeed in getting an answer, we examine the answer. */ |
123 | if (!jerry_value_is_error (js_has_property)) |
124 | { |
125 | bool has_property = jerry_get_boolean_value (js_has_property); |
126 | |
127 | /* If the module is indeed in the cache, we return it. */ |
128 | if (has_property) |
129 | { |
130 | if (result != NULL) |
131 | { |
132 | (*result) = jerry_get_property (cache, module_name); |
133 | } |
134 | ret = true; |
135 | } |
136 | } |
137 | |
138 | jerry_release_value (js_has_property); |
139 | |
140 | return ret; |
141 | } /* jerryx_module_check_cache */ |
142 | |
143 | /** |
144 | * Attempt to cache a loaded module. |
145 | * |
146 | * @return the module on success, otherwise the error encountered when attempting to cache. In the latter case, the |
147 | * @p module is released. |
148 | */ |
149 | static jerry_value_t |
150 | jerryx_module_add_to_cache (jerry_value_t cache, /**< cache to which to add the module */ |
151 | jerry_value_t module_name, /**< key at which to cache the module */ |
152 | jerry_value_t module) /**< the module to cache */ |
153 | { |
154 | jerry_value_t ret = jerry_set_property (cache, module_name, module); |
155 | |
156 | if (jerry_value_is_error (ret)) |
157 | { |
158 | jerry_release_value (module); |
159 | } |
160 | else |
161 | { |
162 | jerry_release_value (ret); |
163 | ret = module; |
164 | } |
165 | |
166 | return ret; |
167 | } /* jerryx_module_add_to_cache */ |
168 | |
169 | static const jerry_char_t *on_resolve_absent = (jerry_char_t *) "Module on_resolve () must not be NULL" ; |
170 | |
171 | /** |
172 | * Declare and define the default module resolver - one which examines what modules are defined in the above linker |
173 | * section and loads one that matches the requested name, caching the result for subsequent requests using the context |
174 | * data mechanism. |
175 | */ |
176 | static bool |
177 | jerryx_resolve_native_module (const jerry_value_t canonical_name, /**< canonical name of the module */ |
178 | jerry_value_t *result) /**< [out] where to put the resulting module instance */ |
179 | { |
180 | const jerryx_native_module_t *module_p = NULL; |
181 | |
182 | jerry_size_t name_size = jerry_get_utf8_string_size (canonical_name); |
183 | JERRY_VLA (jerry_char_t, name_string, name_size); |
184 | jerry_string_to_utf8_char_buffer (canonical_name, name_string, name_size); |
185 | |
186 | /* Look for the module by its name in the list of module definitions. */ |
187 | for (module_p = first_module_p; module_p != NULL; module_p = module_p->next_p) |
188 | { |
189 | if (module_p->name_p != NULL |
190 | && strlen ((char *) module_p->name_p) == name_size |
191 | && !strncmp ((char *) module_p->name_p, (char *) name_string, name_size)) |
192 | { |
193 | /* If we find the module by its name we load it and cache it if it has an on_resolve () and complain otherwise. */ |
194 | (*result) = ((module_p->on_resolve_p) ? module_p->on_resolve_p () |
195 | : jerryx_module_create_error (JERRY_ERROR_TYPE, |
196 | on_resolve_absent, |
197 | canonical_name)); |
198 | return true; |
199 | } |
200 | } |
201 | |
202 | return false; |
203 | } /* jerryx_resolve_native_module */ |
204 | |
205 | jerryx_module_resolver_t jerryx_module_native_resolver = |
206 | { |
207 | .get_canonical_name_p = NULL, |
208 | .resolve_p = jerryx_resolve_native_module |
209 | }; |
210 | |
211 | static void |
212 | jerryx_module_resolve_local (const jerry_value_t name, /**< name of the module to load */ |
213 | const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */ |
214 | size_t resolver_count, /**< number of resolvers in @p resolvers */ |
215 | jerry_value_t *result) /**< location to store the result, or NULL to remove the module */ |
216 | { |
217 | size_t index; |
218 | size_t canonical_names_used = 0; |
219 | jerry_value_t instances; |
220 | JERRY_VLA (jerry_value_t, canonical_names, resolver_count); |
221 | jerry_value_t (*get_canonical_name_p) (const jerry_value_t name); |
222 | bool (*resolve_p) (const jerry_value_t canonical_name, |
223 | jerry_value_t *result); |
224 | |
225 | if (!jerry_value_is_string (name)) |
226 | { |
227 | if (result != NULL) |
228 | { |
229 | *result = jerryx_module_create_error (JERRY_ERROR_COMMON, module_name_not_string, name); |
230 | } |
231 | goto done; |
232 | } |
233 | |
234 | instances = *(jerry_value_t *) jerry_get_context_data (&jerryx_module_manager); |
235 | |
236 | /** |
237 | * Establish the canonical name for the requested module. Each resolver presents its own canonical name. If one of |
238 | * the canonical names matches a cached module, it is returned as the result. |
239 | */ |
240 | for (index = 0; index < resolver_count; index++) |
241 | { |
242 | get_canonical_name_p = (resolvers_p[index] == NULL ? NULL : resolvers_p[index]->get_canonical_name_p); |
243 | canonical_names[index] = ((get_canonical_name_p == NULL) ? jerry_acquire_value (name) |
244 | : get_canonical_name_p (name)); |
245 | canonical_names_used++; |
246 | if (jerryx_module_check_cache (instances, canonical_names[index], result)) |
247 | { |
248 | /* A NULL for result indicates that we are to delete the module from the cache if found. Let's do that here.*/ |
249 | if (result == NULL) |
250 | { |
251 | jerry_delete_property (instances, canonical_names[index]); |
252 | } |
253 | goto done; |
254 | } |
255 | } |
256 | |
257 | if (result == NULL) |
258 | { |
259 | goto done; |
260 | } |
261 | |
262 | /** |
263 | * Past this point we assume a module is wanted, and therefore result is not NULL. So, we try each resolver until one |
264 | * manages to resolve the module. |
265 | */ |
266 | for (index = 0; index < resolver_count; index++) |
267 | { |
268 | resolve_p = (resolvers_p[index] == NULL ? NULL : resolvers_p[index]->resolve_p); |
269 | if (resolve_p != NULL && resolve_p (canonical_names[index], result)) |
270 | { |
271 | if (!jerry_value_is_error (*result)) |
272 | { |
273 | *result = jerryx_module_add_to_cache (instances, canonical_names[index], *result); |
274 | } |
275 | goto done; |
276 | } |
277 | } |
278 | |
279 | /* If none of the resolvers manage to find the module, complain with "Module not found" */ |
280 | *result = jerryx_module_create_error (JERRY_ERROR_COMMON, module_not_found, name); |
281 | |
282 | done: |
283 | /* Release the canonical names as returned by the various resolvers. */ |
284 | for (index = 0; index < canonical_names_used; index++) |
285 | { |
286 | jerry_release_value (canonical_names[index]); |
287 | } |
288 | } /* jerryx_module_resolve_local */ |
289 | |
290 | /** |
291 | * Resolve a single module using the module resolvers available in the section declared above and load it into the |
292 | * current context. |
293 | * |
294 | * @p name - name of the module to resolve |
295 | * @p resolvers - list of resolvers to invoke |
296 | * @p count - number of resolvers in the list |
297 | * |
298 | * @return a jerry_value_t containing one of the followings: |
299 | * - the result of having loaded the module named @p name, or |
300 | * - the result of a previous successful load, or |
301 | * - an error indicating that something went wrong during the attempt to load the module. |
302 | */ |
303 | jerry_value_t |
304 | jerryx_module_resolve (const jerry_value_t name, /**< name of the module to load */ |
305 | const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */ |
306 | size_t resolver_count) /**< number of resolvers in @p resolvers */ |
307 | { |
308 | /* Set to zero to circumvent fatal warning. */ |
309 | jerry_value_t ret = 0; |
310 | jerryx_module_resolve_local (name, resolvers_p, resolver_count, &ret); |
311 | return ret; |
312 | } /* jerryx_module_resolve */ |
313 | |
314 | void |
315 | jerryx_module_clear_cache (const jerry_value_t name, /**< name of the module to remove, or undefined */ |
316 | const jerryx_module_resolver_t **resolvers_p, /**< list of resolvers */ |
317 | size_t resolver_count) /**< number of resolvers in @p resolvers */ |
318 | { |
319 | void *instances_p = jerry_get_context_data (&jerryx_module_manager); |
320 | |
321 | if (jerry_value_is_undefined (name)) |
322 | { |
323 | /* We were requested to clear the entire cache, so we bounce the context data in the most agnostic way possible. */ |
324 | jerryx_module_manager.deinit_cb (instances_p); |
325 | jerryx_module_manager.init_cb (instances_p); |
326 | return; |
327 | } |
328 | |
329 | /* Delete the requested module from the cache if it's there. */ |
330 | jerryx_module_resolve_local (name, resolvers_p, resolver_count, NULL); |
331 | } /* jerryx_module_clear_cache */ |
332 | |