Skip to content

Commit 08323c2

Browse files
committed
src: implement IsInsideNodeModules() in C++
This previously compiles a script and run it in a new context to avoid global pollution, which is more complex than necessary and can be too slow for it to be reused in other cases. The new implementation just checks the frames in C++ which is safe from global pollution, faster and simpler. The previous implementation also had a bug when the call site is in a ESM, because ESM have URLs as their script names, which don't start with '/' or '\' and will be skipped. The new implementation removes the skipping to fix it for ESM. PR-URL: nodejs#55286 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 6987bb2 commit 08323c2

File tree

10 files changed

+108
-31
lines changed

10 files changed

+108
-31
lines changed

lib/buffer.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ const {
7979
ONLY_ENUMERABLE,
8080
},
8181
getOwnNonIndexProperties,
82+
isInsideNodeModules,
8283
} = internalBinding('util');
8384
const {
8485
customInspectSymbol,
85-
isInsideNodeModules,
8686
lazyDOMException,
8787
normalizeEncoding,
8888
kIsEncodingSymbol,
@@ -178,13 +178,15 @@ function showFlaggedDeprecation() {
178178
if (bufferWarningAlreadyEmitted ||
179179
++nodeModulesCheckCounter > 10000 ||
180180
(!require('internal/options').getOptionValue('--pending-deprecation') &&
181-
isInsideNodeModules())) {
181+
isInsideNodeModules(100, true))) {
182182
// We don't emit a warning, because we either:
183183
// - Already did so, or
184184
// - Already checked too many times whether a call is coming
185185
// from node_modules and want to stop slowing down things, or
186186
// - We aren't running with `--pending-deprecation` enabled,
187187
// and the code is inside `node_modules`.
188+
// - We found node_modules in up to the topmost 100 frames, or
189+
// there are more than 100 frames and we don't want to search anymore.
188190
return;
189191
}
190192

lib/internal/util.js

-29
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
const {
44
ArrayFrom,
5-
ArrayIsArray,
65
ArrayPrototypePush,
76
ArrayPrototypeSlice,
87
ArrayPrototypeSort,
@@ -34,9 +33,7 @@ const {
3433
SafeSet,
3534
SafeWeakMap,
3635
SafeWeakRef,
37-
StringPrototypeIncludes,
3836
StringPrototypeReplace,
39-
StringPrototypeStartsWith,
4037
StringPrototypeToLowerCase,
4138
StringPrototypeToUpperCase,
4239
Symbol,
@@ -513,31 +510,6 @@ function getStructuredStack() {
513510
return getStructuredStackImpl();
514511
}
515512

516-
function isInsideNodeModules() {
517-
const stack = getStructuredStack();
518-
519-
// Iterate over all stack frames and look for the first one not coming
520-
// from inside Node.js itself:
521-
if (ArrayIsArray(stack)) {
522-
for (const frame of stack) {
523-
const filename = frame.getFileName();
524-
525-
if (
526-
filename == null ||
527-
StringPrototypeStartsWith(filename, 'node:') === true ||
528-
(
529-
filename[0] !== '/' &&
530-
StringPrototypeIncludes(filename, '\\') === false
531-
)
532-
) {
533-
continue;
534-
}
535-
return isUnderNodeModules(filename);
536-
}
537-
}
538-
return false;
539-
}
540-
541513
function once(callback, { preserveReturnValue = false } = kEmptyObject) {
542514
let called = false;
543515
let returnValue;
@@ -911,7 +883,6 @@ module.exports = {
911883
getSystemErrorName,
912884
guessHandleType,
913885
isError,
914-
isInsideNodeModules,
915886
isUnderNodeModules,
916887
isMacOS,
917888
isWindows,

src/node_util.cc

+44
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,48 @@ static void GetCallSite(const FunctionCallbackInfo<Value>& args) {
298298
args.GetReturnValue().Set(callsites);
299299
}
300300

301+
static void IsInsideNodeModules(const FunctionCallbackInfo<Value>& args) {
302+
Isolate* isolate = args.GetIsolate();
303+
CHECK_EQ(args.Length(), 2);
304+
CHECK(args[0]->IsInt32()); // frame_limit
305+
// The second argument is the default value.
306+
307+
int frames_limit = args[0].As<v8::Int32>()->Value();
308+
Local<StackTrace> stack =
309+
StackTrace::CurrentStackTrace(isolate, frames_limit);
310+
int frame_count = stack->GetFrameCount();
311+
312+
// If the search requires looking into more than |frames_limit| frames, give
313+
// up and return the specified default value.
314+
if (frame_count == frames_limit) {
315+
return args.GetReturnValue().Set(args[1]);
316+
}
317+
318+
bool result = false;
319+
for (int i = 0; i < frame_count; ++i) {
320+
Local<StackFrame> stack_frame = stack->GetFrame(isolate, i);
321+
Local<String> script_name = stack_frame->GetScriptName();
322+
323+
if (script_name.IsEmpty() || script_name->Length() == 0) {
324+
continue;
325+
}
326+
Utf8Value script_name_utf8(isolate, script_name);
327+
std::string_view script_name_str = script_name_utf8.ToStringView();
328+
if (script_name_str.starts_with("node:")) {
329+
continue;
330+
}
331+
if (script_name_str.find("/node_modules/") != std::string::npos ||
332+
script_name_str.find("\\node_modules\\") != std::string::npos ||
333+
script_name_str.find("/node_modules\\") != std::string::npos ||
334+
script_name_str.find("\\node_modules/") != std::string::npos) {
335+
result = true;
336+
break;
337+
}
338+
}
339+
340+
args.GetReturnValue().Set(result);
341+
}
342+
301343
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
302344
registry->Register(GetPromiseDetails);
303345
registry->Register(GetProxyDetails);
@@ -313,6 +355,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
313355
registry->Register(FastGuessHandleType);
314356
registry->Register(fast_guess_handle_type_.GetTypeInfo());
315357
registry->Register(ParseEnv);
358+
registry->Register(IsInsideNodeModules);
316359
}
317360

318361
void Initialize(Local<Object> target,
@@ -396,6 +439,7 @@ void Initialize(Local<Object> target,
396439
target->Set(context, env->constants_string(), constants).Check();
397440
}
398441

442+
SetMethod(context, target, "isInsideNodeModules", IsInsideNodeModules);
399443
SetMethodNoSideEffect(
400444
context, target, "getPromiseDetails", GetPromiseDetails);
401445
SetMethodNoSideEffect(context, target, "getProxyDetails", GetProxyDetails);

test/fixtures/warning_node_modules/new-buffer-cjs.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/warning_node_modules/new-buffer-esm.mjs

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/warning_node_modules/node_modules/new-buffer-cjs/index.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/warning_node_modules/node_modules/new-buffer-cjs/package.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/warning_node_modules/node_modules/new-buffer-esm/index.js

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/warning_node_modules/node_modules/new-buffer-esm/package.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const fixtures = require('../common/fixtures');
5+
const { spawnSyncAndAssert } = require('../common/child_process');
6+
7+
if (process.env.NODE_PENDING_DEPRECATION)
8+
common.skip('test does not work when NODE_PENDING_DEPRECATION is set');
9+
10+
spawnSyncAndAssert(
11+
process.execPath,
12+
[ fixtures.path('warning_node_modules', 'new-buffer-cjs.js') ],
13+
{
14+
trim: true,
15+
stderr: '',
16+
}
17+
);
18+
19+
spawnSyncAndAssert(
20+
process.execPath,
21+
[ fixtures.path('warning_node_modules', 'new-buffer-esm.mjs') ],
22+
{
23+
trim: true,
24+
stderr: '',
25+
}
26+
);
27+
28+
spawnSyncAndAssert(
29+
process.execPath,
30+
[
31+
'--pending-deprecation',
32+
fixtures.path('warning_node_modules', 'new-buffer-cjs.js'),
33+
],
34+
{
35+
stderr: /DEP0005/
36+
}
37+
);
38+
39+
spawnSyncAndAssert(
40+
process.execPath,
41+
[
42+
'--pending-deprecation',
43+
fixtures.path('warning_node_modules', 'new-buffer-esm.mjs'),
44+
],
45+
{
46+
stderr: /DEP0005/
47+
}
48+
);

0 commit comments

Comments
 (0)