From 86a98e0375dd9b3b5da5b70da6e03df865429bff Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Mon, 14 Mar 2016 14:46:31 +0200 Subject: [PATCH 1/4] Add support for module.{fileName,name} * Add module.fileName and initialize it to resolved module ID * Add module.name and initialize it to the last component of the resolved module ID * Use module.fileName as the eval fileName which ends up in the wrapper function's .fileName property * Use module.name as the forced .name property of the wrapper function which affects stack traces (but doesn't introduce an automatic binding for the function name because the function is compiled as anonymous and .name is only then overridden) Also add .name for fresh require() functions created to improve stack traces for errors in sub-modules. --- src/duk_bi_global.c | 212 ++++++++++++++++++++++++++++---------------- 1 file changed, 137 insertions(+), 75 deletions(-) diff --git a/src/duk_bi_global.c b/src/duk_bi_global.c index 43c0f8d7..12ae61f2 100644 --- a/src/duk_bi_global.c +++ b/src/duk_bi_global.c @@ -580,7 +580,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) { DUK_DDD(DUK_DDDPRINT("eval -> lex_env=%!iO, var_env=%!iO, this_binding=%!T", (duk_heaphdr *) outer_lex_env, (duk_heaphdr *) outer_var_env, - (duk_tval *) duk_get_tval(ctx, -1))); + duk_get_tval(ctx, -1))); /* [ source template closure this ] */ @@ -846,6 +846,7 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re duk_uint8_t buf_out[DUK_BI_COMMONJS_MODULE_ID_LIMIT]; duk_uint8_t *p; duk_uint8_t *q; + duk_uint8_t *q_last; /* last component */ DUK_ASSERT(req_id != NULL); /* mod_id may be NULL */ @@ -907,10 +908,18 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re for (;;) { duk_uint_fast8_t c; - /* Here 'p' always points to the start of a term. */ + /* 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_DDD(DUK_DDDPRINT("resolve loop top: p -> '%s', q=%p, buf_out=%p", (const char *) p, (void *) q, (void *) buf_out)); + q_last = q; + c = *p++; if (DUK_UNLIKELY(c == 0)) { DUK_DD(DUK_DDPRINT("resolve error: requested ID must end with a non-empty term")); @@ -959,6 +968,9 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re *q++ = c; c = *p++; if (DUK_UNLIKELY(c == 0)) { + /* This was the last term, and q_last was + * updated to match this term at loop top. + */ goto loop_done; } else if (DUK_UNLIKELY(c == '/')) { *q++ = '/'; @@ -980,8 +992,17 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re } } loop_done: - + /* Output #1: resolved absolute name */ + DUK_ASSERT(q >= buf_out); duk_push_lstring(ctx, (const char *) buf_out, (size_t) (q - buf_out)); + + /* Output #2: last component name */ + DUK_ASSERT(q >= q_last); + DUK_ASSERT(q_last >= buf_out); + duk_push_lstring(ctx, (const char *) q_last, (size_t) (q - q_last)); + + DUK_DD(DUK_DDPRINT("after resolving module name: buf_out=%p, q_last=%p, q=%p", + (void *) buf_out, (void *) q_last, (void *) q)); return; resolve_error: @@ -990,6 +1011,19 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re #endif /* DUK_USE_COMMONJS_MODULES */ #if defined(DUK_USE_COMMONJS_MODULES) +/* 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 */ + DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { const char *str_req_id; /* requested identifier */ const char *str_mod_id; /* require.id of current module */ @@ -1005,23 +1039,24 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * Resolve module identifier into canonical absolute form. */ - str_req_id = duk_require_string(ctx, 0); + str_req_id = duk_require_string(ctx, DUK__IDX_REQUESTED_ID); duk_push_current_function(ctx); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_ID); - str_mod_id = duk_get_string(ctx, 2); /* ignore non-strings */ + str_mod_id = duk_get_string(ctx, DUK__IDX_REQUIRE_ID); /* ignore non-strings */ DUK_DDD(DUK_DDDPRINT("resolve module id: requested=%!T, currentmodule=%!T", - (duk_tval *) duk_get_tval(ctx, 0), - (duk_tval *) duk_get_tval(ctx, 2))); + duk_get_tval(ctx, DUK__IDX_REQUESTED_ID), + duk_get_tval(ctx, DUK__IDX_REQUIRE_ID))); duk__bi_global_resolve_module_id(ctx, str_req_id, str_mod_id); str_req_id = NULL; str_mod_id = NULL; - DUK_DDD(DUK_DDDPRINT("resolved module id: requested=%!T, currentmodule=%!T, result=%!T", - (duk_tval *) duk_get_tval(ctx, 0), - (duk_tval *) duk_get_tval(ctx, 2), - (duk_tval *) duk_get_tval(ctx, 3))); + DUK_DDD(DUK_DDDPRINT("resolved module id: requested=%!T, currentmodule=%!T, result=%!T, lastcomp=%!T", + duk_get_tval(ctx, DUK__IDX_REQUESTED_ID), + duk_get_tval(ctx, DUK__IDX_REQUIRE_ID), + duk_get_tval(ctx, DUK__IDX_RESOLVED_ID), + duk_get_tval(ctx, DUK__IDX_LASTCOMP))); - /* [ requested_id require require.id resolved_id ] */ - DUK_ASSERT_TOP(ctx, 4); + /* [ requested_id require require.id resolved_id last_comp ] */ + DUK_ASSERT_TOP(ctx, DUK__IDX_LASTCOMP + 1); /* * Cached module check. @@ -1032,27 +1067,22 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * be supported to some extent. */ - /* [ requested_id require require.id resolved_id ] */ - DUK_ASSERT_TOP(ctx, 4); - duk_push_hobject_bidx(ctx, DUK_BIDX_DUKTAPE); - duk_get_prop_stridx(ctx, 4, DUK_STRIDX_MOD_LOADED); /* Duktape.modLoaded */ - (void) duk_require_hobject(ctx, 5); - - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded ] */ - DUK_ASSERT_TOP(ctx, 6); + duk_get_prop_stridx(ctx, DUK__IDX_DUKTAPE, DUK_STRIDX_MOD_LOADED); /* Duktape.modLoaded */ + (void) duk_require_hobject(ctx, DUK__IDX_MODLOADED); + DUK_ASSERT_TOP(ctx, DUK__IDX_MODLOADED + 1); - duk_dup(ctx, 3); - if (duk_get_prop(ctx, 5)) { - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded Duktape.modLoaded[id] ] */ + 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_DD(DUK_DDPRINT("module already loaded: %!T", - (duk_tval *) duk_get_tval(ctx, 3))); + duk_get_tval(ctx, DUK__IDX_RESOLVED_ID))); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_EXPORTS); /* return module.exports */ return 1; } + DUK_ASSERT_TOP(ctx, DUK__IDX_UNDEFINED + 1); - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined ] */ - DUK_ASSERT_TOP(ctx, 7); + /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined ] */ /* * Module not loaded (and loading not started previously). @@ -1064,8 +1094,12 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * succeeds in finding the module. */ - DUK_DD(DUK_DDPRINT("module not yet loaded: %!T", - (duk_tval *) duk_get_tval(ctx, 3))); + DUK_D(DUK_DPRINT("loading module %!T, resolution base %!T, requested ID %!T -> resolved ID %!T, last component %!T", + duk_get_tval(ctx, DUK__IDX_RESOLVED_ID), + duk_get_tval(ctx, DUK__IDX_REQUIRE_ID), + duk_get_tval(ctx, DUK__IDX_REQUESTED_ID), + duk_get_tval(ctx, DUK__IDX_RESOLVED_ID), + duk_get_tval(ctx, DUK__IDX_LASTCOMP))); /* Fresh require: require.id is left configurable (but not writable) * so that is not easy to accidentally tweak it, but it can still be @@ -1075,32 +1109,44 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * is no practical reason to touch it. */ duk_push_c_function(ctx, duk_bi_global_object_require, 1 /*nargs*/); - duk_dup(ctx, 3); - duk_xdef_prop_stridx(ctx, 7, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_C); /* a fresh require() with require.id = resolved target module id */ + duk_push_hstring_stridx(ctx, DUK_STRIDX_REQUIRE); + duk_xdef_prop_stridx(ctx, DUK__IDX_FRESH_REQUIRE, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); + duk_dup(ctx, DUK__IDX_RESOLVED_ID); + duk_xdef_prop_stridx(ctx, DUK__IDX_FRESH_REQUIRE, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_C); /* 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. + * spec suggests this if possible + * - module.fileName: fileName where module loaded from, defaults to + * resolved ID + * - module.name: name for module wrapper function, defaults to last + * component of resolved ID */ duk_push_object(ctx); /* exports */ duk_push_object(ctx); /* module */ - duk_dup(ctx, -2); - duk_xdef_prop_stridx(ctx, 9, DUK_STRIDX_EXPORTS, DUK_PROPDESC_FLAGS_WC); /* module.exports = exports */ - duk_dup(ctx, 3); /* resolved id: require(id) must return this same module */ - duk_xdef_prop_stridx(ctx, 9, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_NONE); /* module.id = resolved_id */ - duk_compact(ctx, 9); /* module table remains registered to modLoaded, minimize its size */ + duk_dup(ctx, DUK__IDX_EXPORTS); + duk_xdef_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_EXPORTS, DUK_PROPDESC_FLAGS_WC); /* module.exports = exports */ + duk_dup(ctx, DUK__IDX_RESOLVED_ID); /* resolved id: require(id) must return this same module */ + duk_dup_top(ctx); + duk_xdef_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_NONE); /* module.id = resolved_id */ + duk_xdef_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC); /* module.fileName = resolved_id */ + duk_dup(ctx, DUK__IDX_LASTCOMP); + duk_xdef_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_WC); /* module.name = last_comp */ + 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 Duktape Duktape.modLoaded undefined fresh_require exports module ] */ - DUK_ASSERT_TOP(ctx, 10); + DUK_DD(DUK_DDPRINT("module table created: %!T", duk_get_tval(ctx, DUK__IDX_MODULE))); + + /* [ 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, 3); - duk_dup(ctx, 9); - duk_put_prop(ctx, 5); /* Duktape.modLoaded[resolved_id] = module */ + 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 @@ -1122,13 +1168,13 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { duk_push_string(ctx, "(function(require,exports,module){"); /* Duktape.modSearch(resolved_id, fresh_require, exports, module). */ - duk_get_prop_stridx(ctx, 4, DUK_STRIDX_MOD_SEARCH); /* Duktape.modSearch */ - duk_dup(ctx, 3); - duk_dup(ctx, 7); - duk_dup(ctx, 8); - duk_dup(ctx, 9); /* [ ... Duktape.modSearch resolved_id fresh_require exports module ] */ + duk_get_prop_stridx(ctx, DUK__IDX_DUKTAPE, DUK_STRIDX_MOD_SEARCH); /* 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, 12); + DUK_ASSERT_TOP(ctx, DUK__IDX_MODULE + 3); if (pcall_rc != DUK_EXEC_SUCCESS) { /* Delete entry in Duktape.modLoaded[] and rethrow. */ @@ -1138,7 +1184,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { /* If user callback did not return source code, module loading * is finished (user callback initialized exports table directly). */ - if (!duk_is_string(ctx, 11)) { + 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. @@ -1146,26 +1192,30 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { goto return_exports; } - /* Finish the wrapped module source. Force resolved module ID as the - * fileName so it gets set for functions defined within a module. This - * also ensures loggers created within the module get the module ID as - * their default logger name. + /* 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. */ duk_push_string(ctx, "})"); duk_concat(ctx, 3); - duk_dup(ctx, 3); /* resolved module ID for fileName */ + duk_get_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_FILE_NAME); /* module.fileName for fileName */ duk_eval_raw(ctx, NULL, 0, DUK_COMPILE_EVAL); - /* XXX: The module wrapper function is currently anonymous and is shown - * in stack traces. It would be nice to force it to match the module - * name (perhaps just the cleaned up last term). At the moment 'name' - * is write protected so we can't change it directly. 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. + /* 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_hstring_stridx(ctx, DUK_STRIDX_NAME); + duk_get_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_NAME); + duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); + /* * Call the wrapped module function. * @@ -1173,16 +1223,16 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * even if the module throws an error. */ - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module mod_func ] */ - DUK_ASSERT_TOP(ctx, 11); + /* [ 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, 8); /* exports (this binding) */ - duk_dup(ctx, 7); /* fresh require (argument) */ - duk_get_prop_stridx(ctx, 9, DUK_STRIDX_EXPORTS); /* relookup exports from module.exports in case it was changed by modSearch */ - duk_dup(ctx, 9); /* module (argument) */ + duk_dup(ctx, DUK__IDX_EXPORTS); /* exports (this binding) */ + duk_dup(ctx, DUK__IDX_FRESH_REQUIRE); /* fresh require (argument) */ + duk_get_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_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 Duktape Duktape.modLoaded undefined fresh_require exports module mod_func exports fresh_require exports module ] */ - DUK_ASSERT_TOP(ctx, 15); + /* [ 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) { @@ -1193,21 +1243,33 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { goto delete_rethrow; } - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module result(ignored) ] */ - DUK_ASSERT_TOP(ctx, 11); + /* [ 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_stridx(ctx, 9, DUK_STRIDX_EXPORTS); + duk_get_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_EXPORTS); return 1; /* return module.exports */ delete_rethrow: - duk_dup(ctx, 3); - duk_del_prop(ctx, 5); /* delete Duktape.modLoaded[resolved_id] */ + duk_dup(ctx, DUK__IDX_RESOLVED_ID); + duk_del_prop(ctx, DUK__IDX_MODLOADED); /* delete Duktape.modLoaded[resolved_id] */ duk_throw(ctx); /* rethrow original error */ return 0; /* not reachable */ } + +#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 #else DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { DUK_UNREF(ctx); From 6d2025d144de58299d3f0478aadf191e322e4e8c Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Sat, 19 Mar 2016 04:13:19 +0200 Subject: [PATCH 2/4] Testcase for module.{fileName,name} changes --- .../test-commonjs-module-filename.js | 87 +++++++++++++++++++ .../test-commonjs-require-filename.js | 16 ++-- .../test-commonjs-require-subrequire-name.js | 38 ++++++++ 3 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 tests/ecmascript/test-commonjs-module-filename.js create mode 100644 tests/ecmascript/test-commonjs-require-subrequire-name.js diff --git a/tests/ecmascript/test-commonjs-module-filename.js b/tests/ecmascript/test-commonjs-module-filename.js new file mode 100644 index 00000000..9c9ec776 --- /dev/null +++ b/tests/ecmascript/test-commonjs-module-filename.js @@ -0,0 +1,87 @@ +/* + * Duktape 1.5 added module.fileName and module.name support. + */ + +/*=== +default behavior +test/foo2 test/foo2 test/foo2 foo2 +test/foo2 +2 +foo2 +TIME INF test/foo2: test +override .name and .fileName +test/bar2 test/bar2 test/bar2 bar2 +my_source.js +2 +my_module +TIME INF my_source.js: test +.name shadowing test +test/quux2 test/quux2 test/quux2 quux2 +object +number +123 +===*/ + +var testSource = + '/* test */\n' + + 'var err = new Error("aiee");\n' + + 'print(err.fileName);\n' + + 'print(err.lineNumber);\n' + + 'print(Duktape.act(-2).function.name);\n' + + '/*print(err.stack);*/\n' + + 'var logger = new Duktape.Logger();\n' + + 'logger.info("test");\n'; + +function test() { + // Replace Duktape.Logger.prototype.raw to censor timestamps. + + Duktape.Logger.prototype.raw = function (buf) { + print(String(buf).replace(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.*?Z/, 'TIME')); + }; + + // Default behavior, module.fileName is resolved module ID. + + Duktape.modSearch = function modSearch(id, require, exports, module) { + print(id, module.id, module.fileName, module.name); + return testSource; + }; + + print('default behavior'); + var tmp = require('test/foo1/../foo2'); + + // Override module.fileName and module.name in modSearch(). + + Duktape.modSearch = function modSearch(id, require, exports, module) { + print(id, module.id, module.fileName, module.name); + module.fileName = 'my_source.js'; // match source filename for example + module.name = 'my_module'; + return testSource; + }; + + print('override .name and .fileName'); + var tmp = require('test/bar1/../bar2'); + + // Test that the forced .name property of the module wrapper function + // does not introduce a shadowing binding -- this is important for + // semantics and works because the wrapper function is initially + // compiled as an anonymous function (which ensures the function doesn't + // get a "has a name binding" flag) and .name is then forced manually. + + var global = new Function('return this')(); + global.myFileName = 123; // this should be visible + + Duktape.modSearch = function modSearch(id, require, exports, module) { + print(id, module.id, module.fileName, module.name); + module.fileName = 'myFileName'; + return 'print(typeof Math); print(typeof myFileName); print(myFileName);'; + }; + + print('.name shadowing test'); + var tmp = require('test/quux1/../quux2'); +} + +try { + test(); +} catch (e) { + print(e.stack || e); +} diff --git a/tests/ecmascript/test-commonjs-require-filename.js b/tests/ecmascript/test-commonjs-require-filename.js index d682065b..0d622264 100644 --- a/tests/ecmascript/test-commonjs-require-filename.js +++ b/tests/ecmascript/test-commonjs-require-filename.js @@ -2,8 +2,14 @@ * Filename for functions inside a module loaded using require() * * In Duktape 1.0.0 this would always be "duk_bi_global.c" which is confusing. - * For Duktape 1.1.0 this was fixed to be the fully resolved module ID. + * + * In Duktape 1.1.0 this was fixed to be the fully resolved module ID. * See GH-58 for discussion. + * + * In Duktape 1.5.0 the module wrapper function is also given a .name which + * defaults to the last component of the resolved module ID. Both the .name + * and .fileName can be overridden by modSearch via module.name and + * module.fileName. */ /*--- @@ -13,10 +19,10 @@ ---*/ /*=== -moduleFunc name: -moduleFunc fileName: foo +moduleFunc name: foo +moduleFunc fileName: my/foo testFunc name: testFunc -testFunc fileName: foo +testFunc fileName: my/foo ===*/ function modSearch() { @@ -35,7 +41,7 @@ function test() { */ Duktape.modSearch = modSearch; - var mod = require('foo'); + var mod = require('my/foo'); /* However, functions defined within the module don't have a proper * fileName in Duktape 1.0.0. diff --git a/tests/ecmascript/test-commonjs-require-subrequire-name.js b/tests/ecmascript/test-commonjs-require-subrequire-name.js new file mode 100644 index 00000000..c83aef4f --- /dev/null +++ b/tests/ecmascript/test-commonjs-require-subrequire-name.js @@ -0,0 +1,38 @@ +/* + * The fresh require() functions given to modules should have a .name so that + * they appear nicely in tracebacks. This was not the case in Duktape 1.4.x, + * fixed in Duktape 1.5.x. + */ + +/*=== +function +string +require +false false false +===*/ + +function test() { + Duktape.modSearch = function modSearch(id, require, exports, module) { + var pd; + + print(typeof require); + print(typeof require.name); + print(require.name); + + pd = Object.getOwnPropertyDescriptor(require, 'name'); + print(pd.writable, pd.enumerable, pd.configurable); + + //print(new Error('aiee').stack); + //require('../../../../foo') + + return undefined; + }; + + require('foo/bar'); +} + +try { + test(); +} catch (e) { + print(e.stack || e); +} From c2f6755f7d17748baec8a676c3ab5d756eefb18d Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Sun, 20 Mar 2016 23:00:12 +0200 Subject: [PATCH 3/4] Module doc update for module.{fileName,name} --- doc/modules.rst | 16 +++++++++++++++- website/guide/modules.html | 3 ++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/doc/modules.rst b/doc/modules.rst index 0a29ecf6..4f67ede0 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -12,7 +12,8 @@ built into Duktape: - http://wiki.commonjs.org/wiki/Modules/1.1.1 - - Duktape supports also ``module.exports`` + - Duktape supports also ``module.exports`` and a few Duktape specific + properties (``module.fileName`` and ``module.name``) * The user must provide a *module search function* which locates a module corresponding to a resolved module ID, and can register module symbols @@ -252,6 +253,19 @@ Duktape supports ``module.exports`` since Duktape 1.3, see: * ``test-commonjs-module-exports-repl.js`` +module.fileName and module.name +=============================== + +The ``module.fileName`` and ``module.name`` properties are Duktape specific +and allow modSearch() to control the ``.fileName`` and ``.name`` properties +of the module wrapper function used to implement module loading. This is +useful because they appear in e.g. tracebacks for errors created from the +module, see: https://github.com/svaarala/duktape/pull/639. + +The initial value for ``module.fileName`` is the full resolved module ID +(e.g. ``foo/bar``) and for ``module.name`` the last component of the +resolved module ID (e.g. ``bar``). + C modules and DLLs ================== diff --git a/website/guide/modules.html b/website/guide/modules.html index 8dd9ad0f..d7ecd012 100644 --- a/website/guide/modules.html +++ b/website/guide/modules.html @@ -3,7 +3,8 @@

Duktape has a built-in minimal module loading framework based on CommonJS modules version 1.1.1, -with additional support for module.exports. +with additional support for module.exports and a few Duktape +specific module object properties. The internals are documented in modules.rst. A recommended (but not mandatory) C module convention is described in From 75f3c0cc3688edc4591b7d929aa71c5c40e30745 Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Sat, 19 Mar 2016 04:16:40 +0200 Subject: [PATCH 4/4] Releases: module.fileName and module.name --- RELEASES.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/RELEASES.rst b/RELEASES.rst index fe96b7b5..5e0bf65c 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -1396,6 +1396,17 @@ Planned explicit filename is known; this makes file/line information thrown from such code more useful in practice (GH-516, GH-644) +* Add support for non-standard module.fileName (initialized to resolved + module ID) and module.name (initialized to last component of resolved + module ID) which are used for the internal module wrapper function's + .fileName and .name properties; they show up in stack traces, debugger + integration, logger instance default naming, etc, and can now be + controlled by modSearch() (GH-639) + +* Add a .name property for the require() functions created for included + modules, so that they have a readable name in stack traces like the top + level require() function (GH-639) + * Add Windows version of the debugger example TCP transport (GH-579) * Add support for application specific debugger commands (AppRequest) and