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