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