File Coverage

duk_module_node.c
Criterion Covered Total %
statement 134 148 90.5
branch 12 18 66.6
condition n/a
subroutine n/a
pod n/a
total 146 166 87.9


line stmt bran cond sub pod time code
1             /*
2             * Node.js-like module loading framework for Duktape
3             *
4             * https://nodejs.org/api/modules.html
5             */
6              
7             #include "duktape.h"
8             #include "duk_module_node.h"
9              
10             #if DUK_VERSION >= 19999
11             static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata);
12             #else
13             static duk_int_t duk__eval_module_source(duk_context *ctx);
14             #endif
15             static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main);
16              
17 16           static duk_bool_t duk__get_cached_module(duk_context *ctx, const char *id) {
18 16           duk_push_global_stash(ctx);
19 16           (void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
20 16 100         if (duk_get_prop_string(ctx, -1, id)) {
21 4           duk_remove(ctx, -2);
22 4           duk_remove(ctx, -2);
23 4           return 1;
24             } else {
25 12           duk_pop_3(ctx);
26 12           return 0;
27             }
28             }
29              
30             /* Place a `module` object on the top of the value stack into the require cache
31             * based on its `.id` property. As a convenience to the caller, leave the
32             * object on top of the value stack afterwards.
33             */
34 12           static void duk__put_cached_module(duk_context *ctx) {
35             /* [ ... module ] */
36              
37 12           duk_push_global_stash(ctx);
38 12           (void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
39 12           duk_dup(ctx, -3);
40              
41             /* [ ... module stash req_cache module ] */
42              
43 12           (void) duk_get_prop_string(ctx, -1, "id");
44 12           duk_dup(ctx, -2);
45 12           duk_put_prop(ctx, -4);
46              
47 12           duk_pop_3(ctx); /* [ ... module ] */
48 12           }
49              
50 1           static void duk__del_cached_module(duk_context *ctx, const char *id) {
51 1           duk_push_global_stash(ctx);
52 1           (void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
53 1           duk_del_prop_string(ctx, -1, id);
54 1           duk_pop_2(ctx);
55 1           }
56              
57 18           static duk_ret_t duk__handle_require(duk_context *ctx) {
58             /*
59             * Value stack handling here is a bit sloppy but should be correct.
60             * Call handling will clean up any extra garbage for us.
61             */
62              
63             const char *id;
64             const char *parent_id;
65             duk_idx_t module_idx;
66             duk_idx_t stash_idx;
67             duk_int_t ret;
68              
69 18           duk_push_global_stash(ctx);
70 18           stash_idx = duk_normalize_index(ctx, -1);
71              
72 18           duk_push_current_function(ctx);
73 18           (void) duk_get_prop_string(ctx, -1, "\xff" "moduleId");
74 18           parent_id = duk_require_string(ctx, -1);
75             (void) parent_id; /* not used directly; suppress warning */
76              
77             /* [ id stash require parent_id ] */
78              
79 18           id = duk_require_string(ctx, 0);
80              
81 18           (void) duk_get_prop_string(ctx, stash_idx, "\xff" "modResolve");
82 18           duk_dup(ctx, 0); /* module ID */
83 18           duk_dup(ctx, -3); /* parent ID */
84 18           duk_call(ctx, 2);
85              
86             /* [ ... stash ... resolved_id ] */
87              
88 16           id = duk_require_string(ctx, -1);
89              
90 16 100         if (duk__get_cached_module(ctx, id)) {
91 4           goto have_module; /* use the cached module */
92             }
93              
94 12           duk__push_module_object(ctx, id, 0 /*main*/);
95 12           duk__put_cached_module(ctx); /* module remains on stack */
96              
97             /*
98             * From here on out, we have to be careful not to throw. If it can't be
99             * avoided, the error must be caught and the module removed from the
100             * require cache before rethrowing. This allows the application to
101             * reattempt loading the module.
102             */
103              
104 12           module_idx = duk_normalize_index(ctx, -1);
105              
106             /* [ ... stash ... resolved_id module ] */
107              
108 12           (void) duk_get_prop_string(ctx, stash_idx, "\xff" "modLoad");
109 12           duk_dup(ctx, -3); /* resolved ID */
110 12           (void) duk_get_prop_string(ctx, module_idx, "exports");
111 12           duk_dup(ctx, module_idx);
112 12           ret = duk_pcall(ctx, 3);
113 10 100         if (ret != DUK_EXEC_SUCCESS) {
114 1           duk__del_cached_module(ctx, id);
115 1           (void) duk_throw(ctx); /* rethrow */
116             }
117              
118 9 50         if (duk_is_string(ctx, -1)) {
119             duk_int_t ret;
120              
121             /* [ ... module source ] */
122              
123             #if DUK_VERSION >= 19999
124 9           ret = duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
125             #else
126             ret = duk_safe_call(ctx, duk__eval_module_source, 2, 1);
127             #endif
128 9 50         if (ret != DUK_EXEC_SUCCESS) {
129 0           duk__del_cached_module(ctx, id);
130 0           (void) duk_throw(ctx); /* rethrow */
131             }
132 0 0         } else if (duk_is_undefined(ctx, -1)) {
133 0           duk_pop(ctx);
134             } else {
135 0           duk__del_cached_module(ctx, id);
136 0           (void) duk_type_error(ctx, "invalid module load callback return value");
137             }
138              
139             /* fall through */
140              
141 13           have_module:
142             /* [ ... module ] */
143              
144 13           (void) duk_get_prop_string(ctx, -1, "exports");
145 13           return 1;
146             }
147              
148 385           static void duk__push_require_function(duk_context *ctx, const char *id) {
149 385           duk_push_c_function(ctx, duk__handle_require, 1);
150 385           duk_push_string(ctx, "name");
151 385           duk_push_string(ctx, "require");
152 385           duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE);
153 385           duk_push_string(ctx, id);
154 385           duk_put_prop_string(ctx, -2, "\xff" "moduleId");
155              
156             /* require.cache */
157 385           duk_push_global_stash(ctx);
158 385           (void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
159 385           duk_put_prop_string(ctx, -3, "cache");
160 385           duk_pop(ctx);
161              
162             /* require.main */
163 385           duk_push_global_stash(ctx);
164 385           (void) duk_get_prop_string(ctx, -1, "\xff" "mainModule");
165 385           duk_put_prop_string(ctx, -3, "main");
166 385           duk_pop(ctx);
167 385           }
168              
169 12           static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main) {
170 12           duk_push_object(ctx);
171              
172             /* Set this as the main module, if requested */
173 12 50         if (main) {
174 0           duk_push_global_stash(ctx);
175 0           duk_dup(ctx, -2);
176 0           duk_put_prop_string(ctx, -2, "\xff" "mainModule");
177 0           duk_pop(ctx);
178             }
179              
180             /* Node.js uses the canonicalized filename of a module for both module.id
181             * and module.filename. We have no concept of a file system here, so just
182             * use the module ID for both values.
183             */
184 12           duk_push_string(ctx, id);
185 12           duk_dup(ctx, -1);
186 12           duk_put_prop_string(ctx, -3, "filename");
187 12           duk_put_prop_string(ctx, -2, "id");
188              
189             /* module.exports = {} */
190 12           duk_push_object(ctx);
191 12           duk_put_prop_string(ctx, -2, "exports");
192              
193             /* module.loaded = false */
194 12           duk_push_false(ctx);
195 12           duk_put_prop_string(ctx, -2, "loaded");
196              
197             /* module.require */
198 12           duk__push_require_function(ctx, id);
199 12           duk_put_prop_string(ctx, -2, "require");
200 12           }
201              
202             #if DUK_VERSION >= 19999
203 9           static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata) {
204             #else
205             static duk_int_t duk__eval_module_source(duk_context *ctx) {
206             #endif
207             const char *src;
208              
209             /*
210             * Stack: [ ... module source ]
211             */
212              
213             #if DUK_VERSION >= 19999
214             (void) udata;
215             #endif
216              
217             /* Wrap the module code in a function expression. This is the simplest
218             * way to implement CommonJS closure semantics and matches the behavior of
219             * e.g. Node.js.
220             */
221 9           duk_push_string(ctx, "(function(exports,require,module,__filename,__dirname){");
222 9           src = duk_require_string(ctx, -2);
223 9 100         duk_push_string(ctx, (src[0] == '#' && src[1] == '!') ? "//" : ""); /* Shebang support. */
    50          
224 9           duk_dup(ctx, -3); /* source */
225 9           duk_push_string(ctx, "\n})"); /* Newline allows module last line to contain a // comment. */
226 9           duk_concat(ctx, 4);
227              
228             /* [ ... module source func_src ] */
229              
230 9           (void) duk_get_prop_string(ctx, -3, "filename");
231 9           duk_compile(ctx, DUK_COMPILE_EVAL);
232 9           duk_call(ctx, 0);
233              
234             /* [ ... module source func ] */
235              
236             /* Set name for the wrapper function. */
237 9           duk_push_string(ctx, "name");
238 9           duk_push_string(ctx, "main");
239 9           duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);
240              
241             /* call the function wrapper */
242 9           (void) duk_get_prop_string(ctx, -3, "exports"); /* exports */
243 9           (void) duk_get_prop_string(ctx, -4, "require"); /* require */
244 9           duk_dup(ctx, -5); /* module */
245 9           (void) duk_get_prop_string(ctx, -6, "filename"); /* __filename */
246 9           duk_push_undefined(ctx); /* __dirname */
247 9           duk_call(ctx, 5);
248              
249             /* [ ... module source result(ignore) ] */
250              
251             /* module.loaded = true */
252 9           duk_push_true(ctx);
253 9           duk_put_prop_string(ctx, -4, "loaded");
254              
255             /* [ ... module source retval ] */
256              
257 9           duk_pop_2(ctx);
258              
259             /* [ ... module ] */
260              
261 9           return 1;
262             }
263              
264             /* Load a module as the 'main' module. */
265 0           duk_ret_t duk_module_node_peval_main(duk_context *ctx, const char *path) {
266             /*
267             * Stack: [ ... source ]
268             */
269              
270 0           duk__push_module_object(ctx, path, 1 /*main*/);
271             /* [ ... source module ] */
272              
273 0           duk_dup(ctx, 0);
274             /* [ ... source module source ] */
275              
276             #if DUK_VERSION >= 19999
277 0           return duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
278             #else
279             return duk_safe_call(ctx, duk__eval_module_source, 2, 1);
280             #endif
281             }
282              
283 373           void duk_module_node_init(duk_context *ctx) {
284             /*
285             * Stack: [ ... options ] => [ ... ]
286             */
287              
288             duk_idx_t options_idx;
289              
290 373           duk_require_object_coercible(ctx, -1); /* error before setting up requireCache */
291 373           options_idx = duk_require_normalize_index(ctx, -1);
292              
293             /* Initialize the require cache to a fresh object. */
294 373           duk_push_global_stash(ctx);
295             #if DUK_VERSION >= 19999
296 373           duk_push_bare_object(ctx);
297             #else
298             duk_push_object(ctx);
299             duk_push_undefined(ctx);
300             duk_set_prototype(ctx, -2);
301             #endif
302 373           duk_put_prop_string(ctx, -2, "\xff" "requireCache");
303 373           duk_pop(ctx);
304              
305             /* Stash callbacks for later use. User code can overwrite them later
306             * on directly by accessing the global stash.
307             */
308 373           duk_push_global_stash(ctx);
309 373           duk_get_prop_string(ctx, options_idx, "resolve");
310 373           duk_require_function(ctx, -1);
311 373           duk_put_prop_string(ctx, -2, "\xff" "modResolve");
312 373           duk_get_prop_string(ctx, options_idx, "load");
313 373           duk_require_function(ctx, -1);
314 373           duk_put_prop_string(ctx, -2, "\xff" "modLoad");
315 373           duk_pop(ctx);
316              
317             /* Stash main module. */
318 373           duk_push_global_stash(ctx);
319 373           duk_push_undefined(ctx);
320 373           duk_put_prop_string(ctx, -2, "\xff" "mainModule");
321 373           duk_pop(ctx);
322              
323             /* register `require` as a global function. */
324 373           duk_push_global_object(ctx);
325 373           duk_push_string(ctx, "require");
326 373           duk__push_require_function(ctx, "");
327 373           duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE |
328             DUK_DEFPROP_SET_WRITABLE |
329             DUK_DEFPROP_SET_CONFIGURABLE);
330 373           duk_pop(ctx);
331              
332 373           duk_pop(ctx); /* pop argument */
333 373           }