duk_module_duktape.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. /*
  2. * Duktape 1.x compatible module loading framework
  3. */
  4. #include "duktape.h"
  5. #include "duk_module_duktape.h"
  6. /* (v)snprintf() is missing before MSVC 2015. Note that _(v)snprintf() does
  7. * NOT NUL terminate on truncation, but that's OK here.
  8. * http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010
  9. */
  10. #if defined(_MSC_VER) && (_MSC_VER < 1900)
  11. #define snprintf _snprintf
  12. #endif
  13. #if 0 /* Enable manually */
  14. #define DUK__ASSERT(x) do { \
  15. if (!(x)) { \
  16. fprintf(stderr, "ASSERTION FAILED at %s:%d: " #x "\n", __FILE__, __LINE__); \
  17. fflush(stderr); \
  18. } \
  19. } while (0)
  20. #define DUK__ASSERT_TOP(ctx,val) do { \
  21. DUK__ASSERT(duk_get_top((ctx)) == (val)); \
  22. } while (0)
  23. #else
  24. #define DUK__ASSERT(x) do { (void) (x); } while (0)
  25. #define DUK__ASSERT_TOP(ctx,val) do { (void) ctx; (void) (val); } while (0)
  26. #endif
  27. static void duk__resolve_module_id(duk_context *ctx, const char *req_id, const char *mod_id) {
  28. duk_uint8_t buf[DUK_COMMONJS_MODULE_ID_LIMIT];
  29. duk_uint8_t *p;
  30. duk_uint8_t *q;
  31. duk_uint8_t *q_last; /* last component */
  32. duk_int_t int_rc;
  33. DUK__ASSERT(req_id != NULL);
  34. /* mod_id may be NULL */
  35. /*
  36. * A few notes on the algorithm:
  37. *
  38. * - Terms are not allowed to begin with a period unless the term
  39. * is either '.' or '..'. This simplifies implementation (and
  40. * is within CommonJS modules specification).
  41. *
  42. * - There are few output bound checks here. This is on purpose:
  43. * the resolution input is length checked and the output is never
  44. * longer than the input. The resolved output is written directly
  45. * over the input because it's never longer than the input at any
  46. * point in the algorithm.
  47. *
  48. * - Non-ASCII characters are processed as individual bytes and
  49. * need no special treatment. However, U+0000 terminates the
  50. * algorithm; this is not an issue because U+0000 is not a
  51. * desirable term character anyway.
  52. */
  53. /*
  54. * Set up the resolution input which is the requested ID directly
  55. * (if absolute or no current module path) or with current module
  56. * ID prepended (if relative and current module path exists).
  57. *
  58. * Suppose current module is 'foo/bar' and relative path is './quux'.
  59. * The 'bar' component must be replaced so the initial input here is
  60. * 'foo/bar/.././quux'.
  61. */
  62. if (mod_id != NULL && req_id[0] == '.') {
  63. int_rc = snprintf((char *) buf, sizeof(buf), "%s/../%s", mod_id, req_id);
  64. } else {
  65. int_rc = snprintf((char *) buf, sizeof(buf), "%s", req_id);
  66. }
  67. if (int_rc >= (duk_int_t) sizeof(buf) || int_rc < 0) {
  68. /* Potentially truncated, NUL not guaranteed in any case.
  69. * The (int_rc < 0) case should not occur in practice.
  70. */
  71. goto resolve_error;
  72. }
  73. DUK__ASSERT(strlen((const char *) buf) < sizeof(buf)); /* at most sizeof(buf) - 1 */
  74. /*
  75. * Resolution loop. At the top of the loop we're expecting a valid
  76. * term: '.', '..', or a non-empty identifier not starting with a period.
  77. */
  78. p = buf;
  79. q = buf;
  80. for (;;) {
  81. duk_uint_fast8_t c;
  82. /* Here 'p' always points to the start of a term.
  83. *
  84. * We can also unconditionally reset q_last here: if this is
  85. * the last (non-empty) term q_last will have the right value
  86. * on loop exit.
  87. */
  88. DUK__ASSERT(p >= q); /* output is never longer than input during resolution */
  89. q_last = q;
  90. c = *p++;
  91. if (c == 0) {
  92. goto resolve_error;
  93. } else if (c == '.') {
  94. c = *p++;
  95. if (c == '/') {
  96. /* Term was '.' and is eaten entirely (including dup slashes). */
  97. goto eat_dup_slashes;
  98. }
  99. if (c == '.' && *p == '/') {
  100. /* Term was '..', backtrack resolved name by one component.
  101. * q[-1] = previous slash (or beyond start of buffer)
  102. * q[-2] = last char of previous component (or beyond start of buffer)
  103. */
  104. p++; /* eat (first) input slash */
  105. DUK__ASSERT(q >= buf);
  106. if (q == buf) {
  107. goto resolve_error;
  108. }
  109. DUK__ASSERT(*(q - 1) == '/');
  110. q--; /* Backtrack to last output slash (dups already eliminated). */
  111. for (;;) {
  112. /* Backtrack to previous slash or start of buffer. */
  113. DUK__ASSERT(q >= buf);
  114. if (q == buf) {
  115. break;
  116. }
  117. if (*(q - 1) == '/') {
  118. break;
  119. }
  120. q--;
  121. }
  122. goto eat_dup_slashes;
  123. }
  124. goto resolve_error;
  125. } else if (c == '/') {
  126. /* e.g. require('/foo'), empty terms not allowed */
  127. goto resolve_error;
  128. } else {
  129. for (;;) {
  130. /* Copy term name until end or '/'. */
  131. *q++ = c;
  132. c = *p++;
  133. if (c == 0) {
  134. /* This was the last term, and q_last was
  135. * updated to match this term at loop top.
  136. */
  137. goto loop_done;
  138. } else if (c == '/') {
  139. *q++ = '/';
  140. break;
  141. } else {
  142. /* write on next loop */
  143. }
  144. }
  145. }
  146. eat_dup_slashes:
  147. for (;;) {
  148. /* eat dup slashes */
  149. c = *p;
  150. if (c != '/') {
  151. break;
  152. }
  153. p++;
  154. }
  155. }
  156. loop_done:
  157. /* Output #1: resolved absolute name. */
  158. DUK__ASSERT(q >= buf);
  159. duk_push_lstring(ctx, (const char *) buf, (size_t) (q - buf));
  160. /* Output #2: last component name. */
  161. DUK__ASSERT(q >= q_last);
  162. DUK__ASSERT(q_last >= buf);
  163. duk_push_lstring(ctx, (const char *) q_last, (size_t) (q - q_last));
  164. return;
  165. resolve_error:
  166. (void) duk_type_error(ctx, "cannot resolve module id: %s", (const char *) req_id);
  167. }
  168. /* Stack indices for better readability. */
  169. #define DUK__IDX_REQUESTED_ID 0 /* module id requested */
  170. #define DUK__IDX_REQUIRE 1 /* current require() function */
  171. #define DUK__IDX_REQUIRE_ID 2 /* the base ID of the current require() function, resolution base */
  172. #define DUK__IDX_RESOLVED_ID 3 /* resolved, normalized absolute module ID */
  173. #define DUK__IDX_LASTCOMP 4 /* last component name in resolved path */
  174. #define DUK__IDX_DUKTAPE 5 /* Duktape object */
  175. #define DUK__IDX_MODLOADED 6 /* Duktape.modLoaded[] module cache */
  176. #define DUK__IDX_UNDEFINED 7 /* 'undefined', artifact of lookup */
  177. #define DUK__IDX_FRESH_REQUIRE 8 /* new require() function for module, updated resolution base */
  178. #define DUK__IDX_EXPORTS 9 /* default exports table */
  179. #define DUK__IDX_MODULE 10 /* module object containing module.exports, etc */
  180. static duk_ret_t duk__require(duk_context *ctx) {
  181. const char *str_req_id; /* requested identifier */
  182. const char *str_mod_id; /* require.id of current module */
  183. duk_int_t pcall_rc;
  184. /* NOTE: we try to minimize code size by avoiding unnecessary pops,
  185. * so the stack looks a bit cluttered in this function. DUK__ASSERT_TOP()
  186. * assertions are used to ensure stack configuration is correct at each
  187. * step.
  188. */
  189. /*
  190. * Resolve module identifier into canonical absolute form.
  191. */
  192. str_req_id = duk_require_string(ctx, DUK__IDX_REQUESTED_ID);
  193. duk_push_current_function(ctx);
  194. duk_get_prop_string(ctx, -1, "id");
  195. str_mod_id = duk_get_string(ctx, DUK__IDX_REQUIRE_ID); /* ignore non-strings */
  196. duk__resolve_module_id(ctx, str_req_id, str_mod_id);
  197. str_req_id = NULL;
  198. str_mod_id = NULL;
  199. /* [ requested_id require require.id resolved_id last_comp ] */
  200. DUK__ASSERT_TOP(ctx, DUK__IDX_LASTCOMP + 1);
  201. /*
  202. * Cached module check.
  203. *
  204. * If module has been loaded or its loading has already begun without
  205. * finishing, return the same cached value (module.exports). The
  206. * value is registered when module load starts so that circular
  207. * references can be supported to some extent.
  208. */
  209. duk_push_global_stash(ctx);
  210. duk_get_prop_string(ctx, -1, "\xff" "module:Duktape");
  211. duk_remove(ctx, -2); /* Lookup stashed, original 'Duktape' object. */
  212. duk_get_prop_string(ctx, DUK__IDX_DUKTAPE, "modLoaded"); /* Duktape.modLoaded */
  213. duk_require_type_mask(ctx, DUK__IDX_MODLOADED, DUK_TYPE_MASK_OBJECT);
  214. DUK__ASSERT_TOP(ctx, DUK__IDX_MODLOADED + 1);
  215. duk_dup(ctx, DUK__IDX_RESOLVED_ID);
  216. if (duk_get_prop(ctx, DUK__IDX_MODLOADED)) {
  217. /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded Duktape.modLoaded[id] ] */
  218. duk_get_prop_string(ctx, -1, "exports"); /* return module.exports */
  219. return 1;
  220. }
  221. DUK__ASSERT_TOP(ctx, DUK__IDX_UNDEFINED + 1);
  222. /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined ] */
  223. /*
  224. * Module not loaded (and loading not started previously).
  225. *
  226. * Create a new require() function with 'id' set to resolved ID
  227. * of module being loaded. Also create 'exports' and 'module'
  228. * tables but don't register exports to the loaded table yet.
  229. * We don't want to do that unless the user module search callbacks
  230. * succeeds in finding the module.
  231. */
  232. /* Fresh require: require.id is left configurable (but not writable)
  233. * so that is not easy to accidentally tweak it, but it can still be
  234. * done with Object.defineProperty().
  235. *
  236. * XXX: require.id could also be just made non-configurable, as there
  237. * is no practical reason to touch it (at least from Ecmascript code).
  238. */
  239. duk_push_c_function(ctx, duk__require, 1 /*nargs*/);
  240. duk_push_string(ctx, "name");
  241. duk_push_string(ctx, "require");
  242. duk_def_prop(ctx, DUK__IDX_FRESH_REQUIRE, DUK_DEFPROP_HAVE_VALUE); /* not writable, not enumerable, not configurable */
  243. duk_push_string(ctx, "id");
  244. duk_dup(ctx, DUK__IDX_RESOLVED_ID);
  245. 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 */
  246. /* Module table:
  247. * - module.exports: initial exports table (may be replaced by user)
  248. * - module.id is non-writable and non-configurable, as the CommonJS
  249. * spec suggests this if possible
  250. * - module.filename: not set, defaults to resolved ID if not explicitly
  251. * set by modSearch() (note capitalization, not .fileName, matches Node.js)
  252. * - module.name: not set, defaults to last component of resolved ID if
  253. * not explicitly set by modSearch()
  254. */
  255. duk_push_object(ctx); /* exports */
  256. duk_push_object(ctx); /* module */
  257. duk_push_string(ctx, "exports");
  258. duk_dup(ctx, DUK__IDX_EXPORTS);
  259. duk_def_prop(ctx, DUK__IDX_MODULE, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_SET_WRITABLE | DUK_DEFPROP_SET_CONFIGURABLE); /* module.exports = exports */
  260. duk_push_string(ctx, "id");
  261. duk_dup(ctx, DUK__IDX_RESOLVED_ID); /* resolved id: require(id) must return this same module */
  262. duk_def_prop(ctx, DUK__IDX_MODULE, DUK_DEFPROP_HAVE_VALUE); /* module.id = resolved_id; not writable, not enumerable, not configurable */
  263. duk_compact(ctx, DUK__IDX_MODULE); /* module table remains registered to modLoaded, minimize its size */
  264. DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 1);
  265. /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module ] */
  266. /* Register the module table early to modLoaded[] so that we can
  267. * support circular references even in modSearch(). If an error
  268. * is thrown, we'll delete the reference.
  269. */
  270. duk_dup(ctx, DUK__IDX_RESOLVED_ID);
  271. duk_dup(ctx, DUK__IDX_MODULE);
  272. duk_put_prop(ctx, DUK__IDX_MODLOADED); /* Duktape.modLoaded[resolved_id] = module */
  273. /*
  274. * Call user provided module search function and build the wrapped
  275. * module source code (if necessary). The module search function
  276. * can be used to implement pure Ecmacsript, pure C, and mixed
  277. * Ecmascript/C modules.
  278. *
  279. * The module search function can operate on the exports table directly
  280. * (e.g. DLL code can register values to it). It can also return a
  281. * string which is interpreted as module source code (if a non-string
  282. * is returned the module is assumed to be a pure C one). If a module
  283. * cannot be found, an error must be thrown by the user callback.
  284. *
  285. * Because Duktape.modLoaded[] already contains the module being
  286. * loaded, circular references for C modules should also work
  287. * (although expected to be quite rare).
  288. */
  289. duk_push_string(ctx, "(function(require,exports,module){");
  290. /* Duktape.modSearch(resolved_id, fresh_require, exports, module). */
  291. duk_get_prop_string(ctx, DUK__IDX_DUKTAPE, "modSearch"); /* Duktape.modSearch */
  292. duk_dup(ctx, DUK__IDX_RESOLVED_ID);
  293. duk_dup(ctx, DUK__IDX_FRESH_REQUIRE);
  294. duk_dup(ctx, DUK__IDX_EXPORTS);
  295. duk_dup(ctx, DUK__IDX_MODULE); /* [ ... Duktape.modSearch resolved_id last_comp fresh_require exports module ] */
  296. pcall_rc = duk_pcall(ctx, 4 /*nargs*/); /* -> [ ... source ] */
  297. DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 3);
  298. if (pcall_rc != DUK_EXEC_SUCCESS) {
  299. /* Delete entry in Duktape.modLoaded[] and rethrow. */
  300. goto delete_rethrow;
  301. }
  302. /* If user callback did not return source code, module loading
  303. * is finished (user callback initialized exports table directly).
  304. */
  305. if (!duk_is_string(ctx, -1)) {
  306. /* User callback did not return source code, so module loading
  307. * is finished: just update modLoaded with final module.exports
  308. * and we're done.
  309. */
  310. goto return_exports;
  311. }
  312. /* Finish the wrapped module source. Force module.filename as the
  313. * function .fileName so it gets set for functions defined within a
  314. * module. This also ensures loggers created within the module get
  315. * the module ID (or overridden filename) as their default logger name.
  316. * (Note capitalization: .filename matches Node.js while .fileName is
  317. * used elsewhere in Duktape.)
  318. */
  319. duk_push_string(ctx, "\n})"); /* Newline allows module last line to contain a // comment. */
  320. duk_concat(ctx, 3);
  321. if (!duk_get_prop_string(ctx, DUK__IDX_MODULE, "filename")) {
  322. /* module.filename for .fileName, default to resolved ID if
  323. * not present.
  324. */
  325. duk_pop(ctx);
  326. duk_dup(ctx, DUK__IDX_RESOLVED_ID);
  327. }
  328. pcall_rc = duk_pcompile(ctx, DUK_COMPILE_EVAL);
  329. if (pcall_rc != DUK_EXEC_SUCCESS) {
  330. goto delete_rethrow;
  331. }
  332. pcall_rc = duk_pcall(ctx, 0); /* -> eval'd function wrapper (not called yet) */
  333. if (pcall_rc != DUK_EXEC_SUCCESS) {
  334. goto delete_rethrow;
  335. }
  336. /* Module has now evaluated to a wrapped module function. Force its
  337. * .name to match module.name (defaults to last component of resolved
  338. * ID) so that it is shown in stack traces too. Note that we must not
  339. * introduce an actual name binding into the function scope (which is
  340. * usually the case with a named function) because it would affect the
  341. * scope seen by the module and shadow accesses to globals of the same name.
  342. * This is now done by compiling the function as anonymous and then forcing
  343. * its .name without setting a "has name binding" flag.
  344. */
  345. duk_push_string(ctx, "name");
  346. if (!duk_get_prop_string(ctx, DUK__IDX_MODULE, "name")) {
  347. /* module.name for .name, default to last component if
  348. * not present.
  349. */
  350. duk_pop(ctx);
  351. duk_dup(ctx, DUK__IDX_LASTCOMP);
  352. }
  353. duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);
  354. /*
  355. * Call the wrapped module function.
  356. *
  357. * Use a protected call so that we can update Duktape.modLoaded[resolved_id]
  358. * even if the module throws an error.
  359. */
  360. /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module mod_func ] */
  361. DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 2);
  362. duk_dup(ctx, DUK__IDX_EXPORTS); /* exports (this binding) */
  363. duk_dup(ctx, DUK__IDX_FRESH_REQUIRE); /* fresh require (argument) */
  364. duk_get_prop_string(ctx, DUK__IDX_MODULE, "exports"); /* relookup exports from module.exports in case it was changed by modSearch */
  365. duk_dup(ctx, DUK__IDX_MODULE); /* module (argument) */
  366. DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 6);
  367. /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module mod_func exports fresh_require exports module ] */
  368. pcall_rc = duk_pcall_method(ctx, 3 /*nargs*/);
  369. if (pcall_rc != DUK_EXEC_SUCCESS) {
  370. /* Module loading failed. Node.js will forget the module
  371. * registration so that another require() will try to load
  372. * the module again. Mimic that behavior.
  373. */
  374. goto delete_rethrow;
  375. }
  376. /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module result(ignored) ] */
  377. DUK__ASSERT_TOP(ctx, DUK__IDX_MODULE + 2);
  378. /* fall through */
  379. return_exports:
  380. duk_get_prop_string(ctx, DUK__IDX_MODULE, "exports");
  381. duk_compact(ctx, -1); /* compact the exports table */
  382. return 1; /* return module.exports */
  383. delete_rethrow:
  384. duk_dup(ctx, DUK__IDX_RESOLVED_ID);
  385. duk_del_prop(ctx, DUK__IDX_MODLOADED); /* delete Duktape.modLoaded[resolved_id] */
  386. (void) duk_throw(ctx); /* rethrow original error */
  387. return 0; /* not reachable */
  388. }
  389. void duk_module_duktape_init(duk_context *ctx) {
  390. /* Stash 'Duktape' in case it's modified. */
  391. duk_push_global_stash(ctx);
  392. duk_get_global_string(ctx, "Duktape");
  393. duk_put_prop_string(ctx, -2, "\xff" "module:Duktape");
  394. duk_pop(ctx);
  395. /* Register `require` as a global function. */
  396. duk_eval_string(ctx,
  397. "(function(req){"
  398. "var D=Object.defineProperty;"
  399. "D(req,'name',{value:'require'});"
  400. "D(this,'require',{value:req,writable:true,configurable:true});"
  401. "D(Duktape,'modLoaded',{value:Object.create(null),writable:true,configurable:true});"
  402. "})");
  403. duk_push_c_function(ctx, duk__require, 1 /*nargs*/);
  404. duk_call(ctx, 1);
  405. duk_pop(ctx);
  406. }
  407. #undef DUK__ASSERT
  408. #undef DUK__ASSERT_TOP
  409. #undef DUK__IDX_REQUESTED_ID
  410. #undef DUK__IDX_REQUIRE
  411. #undef DUK__IDX_REQUIRE_ID
  412. #undef DUK__IDX_RESOLVED_ID
  413. #undef DUK__IDX_LASTCOMP
  414. #undef DUK__IDX_DUKTAPE
  415. #undef DUK__IDX_MODLOADED
  416. #undef DUK__IDX_UNDEFINED
  417. #undef DUK__IDX_FRESH_REQUIRE
  418. #undef DUK__IDX_EXPORTS
  419. #undef DUK__IDX_MODULE