From c2e83bf801770cfa85f48ef6d4a1e5fae6e559e3 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Mon, 8 Dec 2025 12:19:55 +0000 Subject: [PATCH 1/2] benchmark: add microbench on isInsideNodeModules --- .../internal/util_isinsidenodemodules.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 benchmark/internal/util_isinsidenodemodules.js diff --git a/benchmark/internal/util_isinsidenodemodules.js b/benchmark/internal/util_isinsidenodemodules.js new file mode 100644 index 00000000000000..bb6746db93f339 --- /dev/null +++ b/benchmark/internal/util_isinsidenodemodules.js @@ -0,0 +1,52 @@ +'use strict'; +const common = require('../common.js'); +const assert = require('assert'); + +const bench = common.createBenchmark(main, { + n: [1e6], + stackLimit: [100], + stackCount: [99, 101], + method: ['isInsideNodeModules', 'noop'], +}, { + flags: ['--expose-internals', '--disable-warning=internal/test/binding'], +}); + +function main({ n, stackLimit, stackCount, method }) { + const { internalBinding } = require('internal/test/binding'); + const { isInsideNodeModules } = internalBinding('util'); + + const testFunction = method === 'noop' ? + () => { + bench.start(); + for (let i = 0; i < n; i++) { + noop(); + } + bench.end(n); + } : + () => { + Error.stackTraceLimit = Infinity; + const existingStackFrameCount = new Error().stack.split('\n').length - 1; + assert.strictEqual(existingStackFrameCount, stackCount); + + bench.start(); + for (let i = 0; i < n; i++) { + isInsideNodeModules(stackLimit, true); + } + bench.end(n); + }; + + // Excluding the message line. + const existingStackFrameCount = new Error().stack.split('\n').length - 1; + // Excluding the test function itself. + nestCallStack(stackCount - existingStackFrameCount - 1, testFunction); +} + +function nestCallStack(depth, callback) { + // nestCallStack(1) already adds a stack frame, so we stop at 1. + if (depth === 1) { + return callback(); + } + return nestCallStack(depth - 1, callback); +} + +function noop() {} From 780517cbdf8de11ac0b0e48e0600ed0db3531b98 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Mon, 8 Dec 2025 12:36:38 +0000 Subject: [PATCH 2/2] lib,src: isInsideNodeModules should test on the first non-internal frame --- .../internal/util_isinsidenodemodules.js | 5 ++- lib/buffer.js | 5 ++- lib/internal/modules/cjs/loader.js | 5 ++- lib/punycode.js | 2 +- lib/url.js | 2 +- src/node_util.cc | 30 +++++++--------- .../test-internal-util-isinsidenodemodules.js | 36 +++++++++++++++++++ typings/internalBinding/util.d.ts | 2 +- 8 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 test/parallel/test-internal-util-isinsidenodemodules.js diff --git a/benchmark/internal/util_isinsidenodemodules.js b/benchmark/internal/util_isinsidenodemodules.js index bb6746db93f339..b75ef5614143d4 100644 --- a/benchmark/internal/util_isinsidenodemodules.js +++ b/benchmark/internal/util_isinsidenodemodules.js @@ -4,14 +4,13 @@ const assert = require('assert'); const bench = common.createBenchmark(main, { n: [1e6], - stackLimit: [100], stackCount: [99, 101], method: ['isInsideNodeModules', 'noop'], }, { flags: ['--expose-internals', '--disable-warning=internal/test/binding'], }); -function main({ n, stackLimit, stackCount, method }) { +function main({ n, stackCount, method }) { const { internalBinding } = require('internal/test/binding'); const { isInsideNodeModules } = internalBinding('util'); @@ -30,7 +29,7 @@ function main({ n, stackLimit, stackCount, method }) { bench.start(); for (let i = 0; i < n; i++) { - isInsideNodeModules(stackLimit, true); + isInsideNodeModules(); } bench.end(n); }; diff --git a/lib/buffer.js b/lib/buffer.js index 5e324ad19bb03e..dc189712fda29f 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -178,15 +178,14 @@ function showFlaggedDeprecation() { if (bufferWarningAlreadyEmitted || ++nodeModulesCheckCounter > 10000 || (!require('internal/options').getOptionValue('--pending-deprecation') && - isInsideNodeModules(100, true))) { + isInsideNodeModules(3))) { // We don't emit a warning, because we either: // - Already did so, or // - Already checked too many times whether a call is coming // from node_modules and want to stop slowing down things, or // - We aren't running with `--pending-deprecation` enabled, // and the code is inside `node_modules`. - // - We found node_modules in up to the topmost 100 frames, or - // there are more than 100 frames and we don't want to search anymore. + // - If the topmost non-internal frame is not inside `node_modules`. return; } diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 6409b913e78602..2faae411c476a2 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -1537,9 +1537,8 @@ function loadESMFromCJS(mod, filename, format, source) { } else if (mod[kIsCachedByESMLoader]) { // It comes from the require() built for `import cjs` and doesn't have a parent recorded // in the CJS module instance. Inspect the stack trace to see if the require() - // comes from node_modules and reduce the noise. If there are more than 100 frames, - // just give up and assume it is under node_modules. - shouldEmitWarning = !isInsideNodeModules(100, true); + // comes from node_modules as a direct call and reduce the noise. + shouldEmitWarning = !isInsideNodeModules(); } } else { shouldEmitWarning = true; diff --git a/lib/punycode.js b/lib/punycode.js index e303a5373b8839..6121923f2f5fdc 100644 --- a/lib/punycode.js +++ b/lib/punycode.js @@ -3,7 +3,7 @@ const { isInsideNodeModules, } = internalBinding('util'); -if (!isInsideNodeModules(100, true)) { +if (!isInsideNodeModules()) { process.emitWarning( 'The `punycode` module is deprecated. Please use a userland ' + 'alternative instead.', diff --git a/lib/url.js b/lib/url.js index 7248c9c277fa49..50b776d38539a7 100644 --- a/lib/url.js +++ b/lib/url.js @@ -132,7 +132,7 @@ const { let urlParseWarned = false; function urlParse(url, parseQueryString, slashesDenoteHost) { - if (!urlParseWarned && !isInsideNodeModules(100, true)) { + if (!urlParseWarned && !isInsideNodeModules(2)) { urlParseWarned = true; process.emitWarning( '`url.parse()` behavior is not standardized and prone to ' + diff --git a/src/node_util.cc b/src/node_util.cc index 2e4d98a8a66a18..af42a3bd72c3f4 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -320,23 +320,21 @@ static void GetCallSites(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(callsites); } +/** + * Checks whether the current call directly initiated from a file inside + * node_modules. This checks up to `frame_limit` stack frames, until it finds + * a frame that is not part of node internal modules. + */ static void IsInsideNodeModules(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); - CHECK_EQ(args.Length(), 2); - CHECK(args[0]->IsInt32()); // frame_limit - // The second argument is the default value. - int frames_limit = args[0].As()->Value(); + int frames_limit = (args.Length() > 0 && args[0]->IsInt32()) + ? args[0].As()->Value() + : 10; Local stack = StackTrace::CurrentStackTrace(isolate, frames_limit); int frame_count = stack->GetFrameCount(); - // If the search requires looking into more than |frames_limit| frames, give - // up and return the specified default value. - if (frame_count == frames_limit) { - return args.GetReturnValue().Set(args[1]); - } - bool result = false; for (int i = 0; i < frame_count; ++i) { Local stack_frame = stack->GetFrame(isolate, i); @@ -350,13 +348,11 @@ static void IsInsideNodeModules(const FunctionCallbackInfo& args) { if (script_name_str.starts_with("node:")) { continue; } - if (script_name_str.find("/node_modules/") != std::string::npos || - script_name_str.find("\\node_modules\\") != std::string::npos || - script_name_str.find("/node_modules\\") != std::string::npos || - script_name_str.find("\\node_modules/") != std::string::npos) { - result = true; - break; - } + result = script_name_str.find("/node_modules/") != std::string::npos || + script_name_str.find("\\node_modules\\") != std::string::npos || + script_name_str.find("/node_modules\\") != std::string::npos || + script_name_str.find("\\node_modules/") != std::string::npos; + break; } args.GetReturnValue().Set(result); diff --git a/test/parallel/test-internal-util-isinsidenodemodules.js b/test/parallel/test-internal-util-isinsidenodemodules.js new file mode 100644 index 00000000000000..d9eecd5e4bcf14 --- /dev/null +++ b/test/parallel/test-internal-util-isinsidenodemodules.js @@ -0,0 +1,36 @@ +'use strict'; + +// Flags: --expose-internals + +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const { internalBinding } = require('internal/test/binding'); +const { isInsideNodeModules } = internalBinding('util'); + +const script = new vm.Script(` + const runInsideNodeModules = (cb) => { + return cb(); + }; + + runInsideNodeModules; +`, { + filename: '/workspace/node_modules/test.js', +}); +const runInsideNodeModules = script.runInThisContext(); + +// Test when called directly inside node_modules +assert.strictEqual(runInsideNodeModules(isInsideNodeModules), true); + +// Test when called inside a user callback from node_modules +runInsideNodeModules(common.mustCall(() => { + function nonNodeModulesFunction() { + assert.strictEqual(isInsideNodeModules(), false); + } + + nonNodeModulesFunction(); +})); + +// Test when called outside node_modules +assert.strictEqual(isInsideNodeModules(), false); diff --git a/typings/internalBinding/util.d.ts b/typings/internalBinding/util.d.ts index c6af1ee01e798c..a3026b5a0305e5 100644 --- a/typings/internalBinding/util.d.ts +++ b/typings/internalBinding/util.d.ts @@ -45,7 +45,7 @@ export interface UtilBinding { guessHandleType(fd: number): 'TCP' | 'TTY' | 'UDP' | 'FILE' | 'PIPE' | 'UNKNOWN'; parseEnv(content: string): Record; styleText(format: Array | string, text: string): string; - isInsideNodeModules(frameLimit: number, defaultValue: unknown): boolean; + isInsideNodeModules(frameLimit?: number): boolean; constructSharedArrayBuffer(length?: number): SharedArrayBuffer; constants: {