Skip to content

Commit a3801b3

Browse files
authored
Turbopack: detect spawn(process.argv[0], ['-e', ...]) (vercel/turborepo#6118)
This detects a specific pattern for invoking a node process to evaluate a string of JavaScript code. Since a filepath can't (or shouldn't) be passed here, this exempts it from static analysis. This pattern is used by the package `xmlhttprequest-ssl`, which is required transitively by socket.io-client: https://github.com/mjwwit/node-XMLHttpRequest/blob/b0271d5e52692d9f48da6088b27d5bf2a6f50d86/lib/XMLHttpRequest.js#L544 Fixes WEB-1554 Resolves #54787 Test Plan: - Added snapshot test - Manual test with a project using socket.io-client
1 parent 946398e commit a3801b3

13 files changed

+138
-2
lines changed

crates/turbopack-ecmascript/src/analyzer/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,10 @@ impl JsValue {
13701370
"process",
13711371
"The Node.js process module: https://nodejs.org/api/process.html",
13721372
),
1373+
WellKnownObjectKind::NodeProcessArgv => (
1374+
"process.argv",
1375+
"The Node.js process.argv property: https://nodejs.org/api/process.html#processargv",
1376+
),
13731377
WellKnownObjectKind::NodeProcessEnv => (
13741378
"process.env",
13751379
"The Node.js process.env property: https://nodejs.org/api/process.html#processenv",
@@ -2960,6 +2964,7 @@ pub enum WellKnownObjectKind {
29602964
OsModule,
29612965
OsModuleDefault,
29622966
NodeProcess,
2967+
NodeProcessArgv,
29632968
NodeProcessEnv,
29642969
NodePreGyp,
29652970
NodeExpressApp,
@@ -2978,6 +2983,7 @@ impl WellKnownObjectKind {
29782983
Self::ChildProcess => Some(&["child_process"]),
29792984
Self::OsModule => Some(&["os"]),
29802985
Self::NodeProcess => Some(&["process"]),
2986+
Self::NodeProcessArgv => Some(&["process", "argv"]),
29812987
Self::NodeProcessEnv => Some(&["process", "env"]),
29822988
Self::NodeBuffer => Some(&["Buffer"]),
29832989
Self::RequireCache => Some(&["require", "cache"]),

crates/turbopack-ecmascript/src/analyzer/well_known.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ pub fn child_process_module_member(kind: WellKnownObjectKind, prop: JsValue) ->
666666
(WellKnownObjectKind::ChildProcess, Some("default")) => {
667667
JsValue::WellKnownObject(WellKnownObjectKind::ChildProcessDefault)
668668
}
669+
669670
_ => JsValue::unknown(
670671
JsValue::member(
671672
Box::new(JsValue::WellKnownObject(WellKnownObjectKind::ChildProcess)),
@@ -714,6 +715,7 @@ async fn node_process_member(
714715
.as_str()
715716
.into(),
716717
Some("cwd") => JsValue::WellKnownFunction(WellKnownFunctionKind::ProcessCwd),
718+
Some("argv") => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessArgv),
717719
Some("env") => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessEnv),
718720
_ => JsValue::unknown(
719721
JsValue::member(

crates/turbopack-ecmascript/src/references/mod.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use constant_condition::{ConstantCondition, ConstantConditionValue};
2727
use constant_value::ConstantValue;
2828
use indexmap::IndexSet;
2929
use lazy_static::lazy_static;
30+
use num_traits::Zero;
3031
use parking_lot::Mutex;
3132
use regex::Regex;
3233
use swc_core::{
@@ -88,7 +89,8 @@ use super::{
8889
graph::{create_graph, Effect},
8990
linker::link,
9091
well_known::replace_well_known,
91-
JsValue, ObjectPart, WellKnownFunctionKind, WellKnownObjectKind,
92+
ConstantValue as JsConstantValue, JsValue, ObjectPart, WellKnownFunctionKind,
93+
WellKnownObjectKind,
9294
},
9395
errors,
9496
parse::{parse, ParseResult},
@@ -108,7 +110,7 @@ use crate::{
108110
imports::{ImportedSymbol, Reexport},
109111
parse_require_context,
110112
top_level_await::has_top_level_await,
111-
ModuleValue, RequireContextValue,
113+
ConstantNumber, ConstantString, ModuleValue, RequireContextValue,
112114
},
113115
chunk::EcmascriptExports,
114116
code_gen::{
@@ -1323,6 +1325,12 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
13231325
}
13241326
JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessSpawnMethod(name)) => {
13251327
let args = linked_args(args).await?;
1328+
1329+
// Is this specifically `spawn(process.argv[0], ['-e', ...])`?
1330+
if is_invoking_node_process_eval(&args) {
1331+
return Ok(());
1332+
}
1333+
13261334
if !args.is_empty() {
13271335
let mut show_dynamic_warning = false;
13281336
let pat = js_value_to_pattern(&args[0]);
@@ -2715,3 +2723,49 @@ fn detect_dynamic_export(p: &Program) -> DetectedDynamicExportType {
27152723
DetectedDynamicExportType::None
27162724
}
27172725
}
2726+
2727+
/// Detects whether a list of arguments is specifically
2728+
/// `(process.argv[0], ['-e', ...])`. This is useful for detecting if a node
2729+
/// process is being spawned to interpret a string of JavaScript code, and does
2730+
/// not require static analysis.
2731+
fn is_invoking_node_process_eval(args: &[JsValue]) -> bool {
2732+
if args.len() < 2 {
2733+
return false;
2734+
}
2735+
2736+
if let JsValue::Member(_, obj, constant) = &args[0] {
2737+
// Is the first argument to spawn `process.argv[]`?
2738+
if let (
2739+
box JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessArgv),
2740+
box JsValue::Constant(JsConstantValue::Num(ConstantNumber(num))),
2741+
) = (obj, constant)
2742+
{
2743+
// Is it specifically `process.argv[0]`?
2744+
if num.is_zero() {
2745+
if let JsValue::Array {
2746+
total_nodes: _,
2747+
items,
2748+
mutable: _,
2749+
} = &args[1]
2750+
{
2751+
// Is `-e` one of the arguments passed to the program?
2752+
if items.iter().any(|e| {
2753+
if let JsValue::Constant(JsConstantValue::Str(ConstantString::Word(arg))) =
2754+
e
2755+
{
2756+
arg == "-e"
2757+
} else {
2758+
false
2759+
}
2760+
}) {
2761+
// If so, this is likely spawning node to evaluate a string, and
2762+
// does not need to be statically analyzed.
2763+
return true;
2764+
}
2765+
}
2766+
}
2767+
}
2768+
}
2769+
2770+
false
2771+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { spawn } from "child_process";
2+
3+
let x = spawn(process.argv[0], ["-e", "console.log('foo');"]);

crates/turbopack-tests/tests/snapshot/node/spawn_node_eval/input/node_modules/child_process/index.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"environment": "NodeJs"
3+
}

crates/turbopack-tests/tests/snapshot/node/spawn_node_eval/output/134fc_child_process_index_64e768.js

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/node/spawn_node_eval/output/134fc_child_process_index_64e768.js.map

Lines changed: 6 additions & 0 deletions
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,12 @@
1+
(globalThis.TURBOPACK = globalThis.TURBOPACK || []).push([
2+
"output/crates_turbopack-tests_tests_snapshot_node_spawn_node_eval_input_index_a238f1.js",
3+
{},
4+
]);
5+
(globalThis.TURBOPACK_CHUNK_LISTS = globalThis.TURBOPACK_CHUNK_LISTS || []).push({
6+
"path": "output/crates_turbopack-tests_tests_snapshot_node_spawn_node_eval_input_index_a238f1.js",
7+
"chunks": [
8+
"output/crates_turbopack-tests_tests_snapshot_node_spawn_node_eval_input_index_b53fce.js",
9+
"output/134fc_child_process_index_64e768.js"
10+
],
11+
"source": "entry"
12+
});

crates/turbopack-tests/tests/snapshot/node/spawn_node_eval/output/crates_turbopack-tests_tests_snapshot_node_spawn_node_eval_input_index_b53fce.js

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/node/spawn_node_eval/output/crates_turbopack-tests_tests_snapshot_node_spawn_node_eval_input_index_b53fce.js.map

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/node/spawn_node_eval/output/crates_turbopack-tests_tests_snapshot_node_spawn_node_eval_input_index_e27a00.js

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turbopack-tests/tests/snapshot/node/spawn_node_eval/output/crates_turbopack-tests_tests_snapshot_node_spawn_node_eval_input_index_e27a00.js.map

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)