aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/gopkg.in/olebedev/go-duktape.v3/duk_module_duktape.c
blob: e2616ba196b0573e2b15c0928dbf7eb36efcb5a8 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471






















































































































































































































































































































































































































































































                                                                                                                                                                                      
/*
 *  Duktape 1.x compatible module loading framework
 */

#include "duktape.h"
#include "duk_module_duktape.h"

/* (v)snprintf() is missing before MSVC 2015.  Note that _(v)snprintf() does
 * NOT NUL terminate on truncation, but that's OK here.
 * http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010
 */
#if defined(_MSC_VER) && (_MSC_VER < 1900)
#define snprintf _snprintf
#endif

#if 0  /* Enable manually */
#define DUK__ASSERT(x) do { \
        if (!(x)) { \
            fprintf(stderr, "ASSERTION FAILED at %s:%d: " #x "\n", __FILE__, __LINE__); \
            fflush(stderr);  \
        } \
    } while (0)
#define DUK__ASSERT_TOP(ctx,val) do { \
        DUK__ASSERT(duk_get_top((ctx)) == (val)); \
    } while (0)
#else
#define DUK__ASSERT(x) do { (void) (x); } while (0)
#define DUK__ASSERT_TOP(ctx,val) do { (void) ctx; (void) (val); } while (0)
#endif

static void duk__resolve_module_id(duk_context *ctx, const char *req_id, const char *mod_id) {
    duk_uint8_t buf[DUK_COMMONJS_MODULE_ID_LIMIT];
    duk_uint8_t *p;
    duk_uint8_t *q;
    duk_uint8_t *q_last;  /* last component */
    duk_int_t int_rc;

    DUK__ASSERT(req_id != NULL);
    /* mod_id may be NULL */

    /*
     *  A few notes on the algorithm:
     *
     *    - Terms are not allowed to begin with a period unless the term
     *      is either '.' or '..'.  This simplifies implementation (and
     *      is within CommonJS modules specification).
     *
     *    - There are few output bound checks here.  This is on purpose:
     *      the resolution input is length checked and the output is never
     *      longer than the input.  The resolved output is written directly
     *      over the input because it's never longer than the input at any
     *      point in the algorithm.
     *
     *    - Non-ASCII characters are processed as individual bytes and
     *      need no special treatment.  However, U+0000 terminates the
     *      algorithm; this is not an issue because U+0000 is not a
     *      desirable term character anyway.
     */

    /*
     *  Set up the resolution input which is the requested ID directly
     *  (if absolute or no current module path) or with current module
     *  ID prepended (if relative and current module path exists).
     *
     *  Suppose current module is 'foo/bar' and relative path is './quux'.
     *  The 'bar' component must be replaced so the initial input here is
     *  'foo/bar/.././quux'.
     */

    if (mod_id != NULL && req_id[0] == '.') {
        int_rc = snprintf((char *) buf, sizeof(buf), "%s/../%s", mod_id, req_id);
    } else {
        int_rc = snprintf((char *) buf, sizeof(buf), "%s", req_id);
    }
    if (int_rc >= (duk_int_t) sizeof(buf) || int_rc < 0) {
        /* Potentially truncated, NUL not guaranteed in any case.
         * The (int_rc < 0) case should not occur in practice.
         */
        goto resolve_error;
    }
    DUK__ASSERT(strlen((const char *) buf) < sizeof(buf));  /* at most sizeof(buf) - 1 */

    /*
     *  Resolution loop.  At the top of the loop we're expecting a valid
     *  term: '.', '..', or a non-empty identifier not starting with a period.
     */

    p = buf;
    q = buf;
    for (;;) {
        duk_uint_fast8_t c;

        /* Here 'p' always points to the start of a term.
         *
         * We can also unconditionally reset q_last here: if this is
         * the last (non-empty) term q_last will have the right value
         * on loop exit.
         */

        DUK__ASSERT(p >= q);  /* output is never longer than input during resolution */

        q_last = q;

        c = *p++;
        if (c == 0) {
            goto resolve_error;
        } else if (c == '.') {
            c = *p++;
            if (c == '/') {
                /* Term was '.' and is eaten entirely (including dup slashes). */
                goto eat_dup_slashes;
            }
            if (c == '.' && *p == '/') {
                /* Term was '..', backtrack resolved name by one component.
                 *  q[-1] = previous slash (or beyond start of buffer)
                 *  q[-2] = last char of previous component (or beyond start of buffer)
                 */
                p++;  /* eat (first) input slash */
                DUK__ASSERT(q >= buf);
                if (q == buf) {
                    goto resolve_error;
                }
                DUK__ASSERT(*(q - 1) == '/');
                q--;  /* Backtrack to last output slash (dups already eliminated). */
                for (;;) {
                    /* Backtrack to previous slash or start of buffer. */
                    DUK__ASSERT(q >= buf);
                    if (q == buf) {
                        break;
                    }
                    if (*(q - 1) == '/') {
                        break;
                    }
                    q--;
                }
                goto eat_dup_slashes;
            }
            goto resolve_error;
        } else if (c == '/') {
            /* e.g. require('/foo'), empty terms not allowed */
            goto resolve_error;
        } else {
            for (;;) {
                /* Copy term name until end or '/'. */
                *q++ = c;
                c = *p++;
                if (c == 0) {
                    /* This was the last term, and q_last was
                     * updated to match this term at loop top.
                     */
                    goto loop_done;
                } else if (c == '/') {
                    *q++ = '/';
                    break;
                } else {
                    /* write on next loop */
                }
            }
        }

     eat_dup_slashes:
        for (;;) {
            /* eat dup slashes */
            c = *p;
            if (c != '/') {
                break;
            }
            p++;
        }
    }
 loop_done:
    /* Output #1: resolved absolute name. */
    DUK__ASSERT(q >= buf);
    duk_push_lstring(ctx, (const char *) buf, (size_t) (q - buf));

    /* Output #2: last component name. */
    DUK__ASSERT(q >= q_last);
    DUK__ASSERT(q_last >= buf);
    duk_push_lstring(ctx, (const char *) q_last, (size_t) (q - q_last));
    return;

 resolve_error:
    (void) duk_type_error(ctx, "cannot resolve module id: %s", (const char *) req_id);
}

/* Stack indices for better readability. */
#define DUK__IDX_REQUESTED_ID   0   /* module id requested */
#define DUK__IDX_REQUIRE        1   /* current require() function */
#define DUK__IDX_REQUIRE_ID     2   /* the base ID of the current require() function, resolution base */
#define DUK__IDX_RESOLVED_ID    3   /* resolved, normalized absolute module ID */
#define DUK__IDX_LASTCOMP       4   /* last component name in resolved path */
#define DUK__IDX_DUKTAPE        5   /* Duktape object */
#define DUK__IDX_MODLOADED      6   /* Duktape.modLoaded[] module cache */
#define DUK__IDX_UNDEFINED      7   /* 'undefined', artifact of lookup */
#define DUK__IDX_FRESH_REQUIRE  8   /* new require() function for module, updated resolution base */
#define DUK__IDX_EXPORTS        9   /* default exports table */
#define DUK__IDX_MODULE         10  /* module object containing module.exports, etc */

static duk_ret_t duk__require(duk_context *ctx) {
    const char *str_req_id;  /* requested identifier */
    const char *str_mod_id;  /* require.id of current module */
    duk_int_t pcall_rc;

    /* NOTE: we try to minimize code size by avoiding unnecessary pops,
     * so the stack looks a bit cluttered in this function.  DUK__ASSERT_TOP()
     * assertions are used to ensure stack configuration is correct at each
     * step.
     */

    /*
     *  Resolve module identifier into canonical absolute form.
     */

    str_req_id = duk_require_string(ctx, DUK__IDX_REQUESTED_ID);
    duk_push_current_function(ctx);
    duk_get_prop_string(ctx, -1, "id");
    str_mod_id = duk_get_string(ctx, DUK__IDX_REQUIRE_ID);  /* ignore non-strings */
    duk__resolve_module_id(ctx, str_req_id, str_mod_id);
    str_req_id = NULL;
    str_mod_id = NULL;

    /* [ requested_id require require.id resolved_id last_comp ] */
    DUK__ASSERT_TOP(ctx, DUK__IDX_LASTCOMP + 1);

    /*
     *  Cached module check.
     *
     *  If module has been loaded or its loading has already begun without
     *  finishing, return the same cached value (module.exports).  The
     *  value is registered when module load starts so that circular
     *  references can be supported to some extent.
     */

    duk_push_global_stash(ctx);
    duk_get_prop_string(ctx, -1, "\xff" "module:Duktape");
    duk_remove(ctx, -2);  /* Lookup stashed, original 'Duktape' object. */
    duk_get_prop_string(ctx, DUK__IDX_DUKTAPE, "modLoaded");  /* Duktape.modLoaded */
    duk_require_type_mask(ctx, DUK__IDX_MODLOADED, DUK_TYPE_MASK_OBJECT);
    DUK__ASSERT_TOP(ctx, DUK__IDX_MODLOADED + 1);

    duk_dup(ctx, DUK__IDX_RESOLVED_ID);
    if (duk_get_prop(ctx, DUK__IDX_MODLOADED)) {
        /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded Duktape.modLoaded[id] ] */
        duk_get_prop_string(ctx, -1, "exports");  /* return module.exports */
        return 1;
    }
    DUK__ASSERT_TOP(ctx, DUK__IDX_UNDEFINED + 1);

    /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined ] */

    /*
     *  Module not loaded (and loading not started previously).
     *
     *  Create a new require() function with 'id' set to resolved ID
     *  of module being loaded.  Also create 'exports' and 'module'
     *  tables but don't register exports to the loaded table yet.
     *  We don't want to do that unless the user module search callbacks
     *  succeeds in finding the module.
     */

    /* Fresh require: require.id is left configurable (but not writable)
     * so that is not easy to accidentally tweak it, but it can still be
     * done with Object.defineProperty().
     *
     * XXX: require.id could also be just made non-configurable, as there
     * is no practical reason to touch it (at least from Ecmascript code).
     */
    duk_push_c_function(ctx, duk__require, 1 /*nargs*/);
    duk_push_string(ctx, "name");
    duk_push_string(ctx, "require");
    duk_def_prop(ctx, DUK__IDX_FRESH_REQUIRE, DUK_DEFPROP_HAVE_VALUE);  /* not writable, not enumerable, not configurable */
    duk_push_string(ctx, "id");
    duk_dup(ctx, DUK__IDX_RESOLVED_ID);
    duk_def_prop(ctx, DUK__IDX_FRESH_REQUIRE, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_SET_CONFIGURABLE);  /* a fresh require() with require.id = resolved target module id */

    /* Module table:
     * - module.exports: initial exports table (may be replaced by user)
     * - module.id is non-writable and non-configurable, as the CommonJS
     *   spec suggests this if possible
     * - module.filename: not set, defaults to resolved ID if not explicitly
     *   set by modSearch() (note capitalization, not .fileName, matches Node.js)
     * - module.name: not set, defaults to last component of resolved ID if
     *   not explicitly set by modSearch()
     */
    duk_push_object(ctx);  /* exports */
    duk_push_object(ctx);  /* module */
    duk_push_string(ctx, "exports");
    duk_dup(ctx, DUK__IDX_EXPORTS);
    duk_def_prop(ctx, DUK__IDX_MODULE, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_SET_WRITABLE | DUK_DEFPROP_SET_CONFIGURABLE);  /* module.exports = exports */
    duk_push_string(ctx, "id");
    duk_dup(ctx, DUK__IDX_RESOLVED_ID);  /* resolved id: require(id) must return this same module */
    duk_def_prop(ctx, DUK__IDX_MODULE, DUK_DEFPROP_HAVE_VALUE);  /* module.id = resolved_id; not writable, not enumerable, not configurable */
    duk_compact(ctx, DUK__IDX_MODULE);  /* module table remains registered to modLoaded, minimize its size */
    DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 1);

    /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module ] */

    /* Register the module table early to modLoaded[] so that we can
     * support circular references even in modSearch().  If an error
     * is thrown, we'll delete the reference.
     */
    duk_dup(ctx, DUK__IDX_RESOLVED_ID);
    duk_dup(ctx, DUK__IDX_MODULE);
    duk_put_prop(ctx, DUK__IDX_MODLOADED);  /* Duktape.modLoaded[resolved_id] = module */

    /*
     *  Call user provided module search function and build the wrapped
     *  module source code (if necessary).  The module search function
     *  can be used to implement pure Ecmacsript, pure C, and mixed
     *  Ecmascript/C modules.
     *
     *  The module search function can operate on the exports table directly
     *  (e.g. DLL code can register values to it).  It can also return a
     *  string which is interpreted as module source code (if a non-string
     *  is returned the module is assumed to be a pure C one).  If a module
     *  cannot be found, an error must be thrown by the user callback.
     *
     *  Because Duktape.modLoaded[] already contains the module being
     *  loaded, circular references for C modules should also work
     *  (although expected to be quite rare).
     */

    duk_push_string(ctx, "(function(require,exports,module){");

    /* Duktape.modSearch(resolved_id, fresh_require, exports, module). */
    duk_get_prop_string(ctx, DUK__IDX_DUKTAPE, "modSearch");  /* Duktape.modSearch */
    duk_dup(ctx, DUK__IDX_RESOLVED_ID);
    duk_dup(ctx, DUK__IDX_FRESH_REQUIRE);
    duk_dup(ctx, DUK__IDX_EXPORTS);
    duk_dup(ctx, DUK__IDX_MODULE);  /* [ ... Duktape.modSearch resolved_id last_comp fresh_require exports module ] */
    pcall_rc = duk_pcall(ctx, 4 /*nargs*/);  /* -> [ ... source ] */
    DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 3);

    if (pcall_rc != DUK_EXEC_SUCCESS) {
        /* Delete entry in Duktape.modLoaded[] and rethrow. */
        goto delete_rethrow;
    }

    /* If user callback did not return source code, module loading
     * is finished (user callback initialized exports table directly).
     */
    if (!duk_is_string(ctx, -1)) {
        /* User callback did not return source code, so module loading
         * is finished: just update modLoaded with final module.exports
         * and we're done.
         */
        goto return_exports;
    }

    /* Finish the wrapped module source.  Force module.filename as the
     * function .fileName so it gets set for functions defined within a
     * module.  This also ensures loggers created within the module get
     * the module ID (or overridden filename) as their default logger name.
     * (Note capitalization: .filename matches Node.js while .fileName is
     * used elsewhere in Duktape.)
     */
    duk_push_string(ctx, "\n})");  /* Newline allows module last line to contain a // comment. */
    duk_concat(ctx, 3);
    if (!duk_get_prop_string(ctx, DUK__IDX_MODULE, "filename")) {
        /* module.filename for .fileName, default to resolved ID if
         * not present.
         */
        duk_pop(ctx);
        duk_dup(ctx, DUK__IDX_RESOLVED_ID);
    }
    pcall_rc = duk_pcompile(ctx, DUK_COMPILE_EVAL);
    if (pcall_rc != DUK_EXEC_SUCCESS) {
        goto delete_rethrow;
    }
    pcall_rc = duk_pcall(ctx, 0);  /* -> eval'd function wrapper (not called yet) */
    if (pcall_rc != DUK_EXEC_SUCCESS) {
        goto delete_rethrow;
    }

    /* Module has now evaluated to a wrapped module function.  Force its
     * .name to match module.name (defaults to last component of resolved
     * ID) so that it is shown in stack traces too.  Note that we must not
     * introduce an actual name binding into the function scope (which is
     * usually the case with a named function) because it would affect the
     * scope seen by the module and shadow accesses to globals of the same name.
     * This is now done by compiling the function as anonymous and then forcing
     * its .name without setting a "has name binding" flag.
     */

    duk_push_string(ctx, "name");
    if (!duk_get_prop_string(ctx, DUK__IDX_MODULE, "name")) {
        /* module.name for .name, default to last component if
         * not present.
         */
        duk_pop(ctx);
        duk_dup(ctx, DUK__IDX_LASTCOMP);
    }
    duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);

    /*
     *  Call the wrapped module function.
     *
     *  Use a protected call so that we can update Duktape.modLoaded[resolved_id]
     *  even if the module throws an error.
     */

    /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module mod_func ] */
    DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 2);

    duk_dup(ctx, DUK__IDX_EXPORTS);  /* exports (this binding) */
    duk_dup(ctx, DUK__IDX_FRESH_REQUIRE);  /* fresh require (argument) */
    duk_get_prop_string(ctx, DUK__IDX_MODULE, "exports");  /* relookup exports from module.exports in case it was changed by modSearch */
    duk_dup(ctx, DUK__IDX_MODULE);  /* module (argument) */
    DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 6);

    /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module mod_func exports fresh_require exports module ] */

    pcall_rc = duk_pcall_method(ctx, 3 /*nargs*/);
    if (pcall_rc != DUK_EXEC_SUCCESS) {
        /* Module loading failed.  Node.js will forget the module
         * registration so that another require() will try to load
         * the module again.  Mimic that behavior.
         */
        goto delete_rethrow;
    }

    /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module result(ignored) ] */
    DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 2);

    /* fall through */

 return_exports:
    duk_get_prop_string(ctx, DUK__IDX_MODULE, "exports");
    duk_compact(ctx, -1);  /* compact the exports table */
    return 1;  /* return module.exports */

 delete_rethrow:
    duk_dup(ctx, DUK__IDX_RESOLVED_ID);
    duk_del_prop(ctx, DUK__IDX_MODLOADED);  /* delete Duktape.modLoaded[resolved_id] */
    (void) duk_throw(ctx);  /* rethrow original error */
    return 0;  /* not reachable */
}

void duk_module_duktape_init(duk_context *ctx) {
    /* Stash 'Duktape' in case it's modified. */
    duk_push_global_stash(ctx);
    duk_get_global_string(ctx, "Duktape");
    duk_put_prop_string(ctx, -2, "\xff" "module:Duktape");
    duk_pop(ctx);

    /* Register `require` as a global function. */
    duk_eval_string(ctx,
        "(function(req){"
        "var D=Object.defineProperty;"
        "D(req,'name',{value:'require'});"
        "D(this,'require',{value:req,writable:true,configurable:true});"
        "D(Duktape,'modLoaded',{value:Object.create(null),writable:true,configurable:true});"
        "})");
    duk_push_c_function(ctx, duk__require, 1 /*nargs*/);
    duk_call(ctx, 1);
    duk_pop(ctx);
}

#undef DUK__ASSERT
#undef DUK__ASSERT_TOP
#undef DUK__IDX_REQUESTED_ID
#undef DUK__IDX_REQUIRE
#undef DUK__IDX_REQUIRE_ID
#undef DUK__IDX_RESOLVED_ID
#undef DUK__IDX_LASTCOMP
#undef DUK__IDX_DUKTAPE
#undef DUK__IDX_MODLOADED
#undef DUK__IDX_UNDEFINED
#undef DUK__IDX_FRESH_REQUIRE
#undef DUK__IDX_EXPORTS
#undef DUK__IDX_MODULE