Skip to content

Commit

Permalink
Turbopack: detect spawn(process.argv[0], ['-e', ...]) (#6118)
Browse files Browse the repository at this point in the history
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 vercel/next.js#54787

Test Plan:
- Added snapshot test
- Manual test with a project using socket.io-client
  • Loading branch information
wbinnssmith authored and Zertsov committed Oct 11, 2023
1 parent 9ccf304 commit f1ec483
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 2 deletions.
6 changes: 6 additions & 0 deletions crates/turbopack-ecmascript/src/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,10 @@ impl JsValue {
"process",
"The Node.js process module: https://nodejs.org/api/process.html",
),
WellKnownObjectKind::NodeProcessArgv => (
"process.argv",
"The Node.js process.argv property: https://nodejs.org/api/process.html#processargv",
),
WellKnownObjectKind::NodeProcessEnv => (
"process.env",
"The Node.js process.env property: https://nodejs.org/api/process.html#processenv",
Expand Down Expand Up @@ -2960,6 +2964,7 @@ pub enum WellKnownObjectKind {
OsModule,
OsModuleDefault,
NodeProcess,
NodeProcessArgv,
NodeProcessEnv,
NodePreGyp,
NodeExpressApp,
Expand All @@ -2978,6 +2983,7 @@ impl WellKnownObjectKind {
Self::ChildProcess => Some(&["child_process"]),
Self::OsModule => Some(&["os"]),
Self::NodeProcess => Some(&["process"]),
Self::NodeProcessArgv => Some(&["process", "argv"]),
Self::NodeProcessEnv => Some(&["process", "env"]),
Self::NodeBuffer => Some(&["Buffer"]),
Self::RequireCache => Some(&["require", "cache"]),
Expand Down
2 changes: 2 additions & 0 deletions crates/turbopack-ecmascript/src/analyzer/well_known.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ pub fn child_process_module_member(kind: WellKnownObjectKind, prop: JsValue) ->
(WellKnownObjectKind::ChildProcess, Some("default")) => {
JsValue::WellKnownObject(WellKnownObjectKind::ChildProcessDefault)
}

_ => JsValue::unknown(
JsValue::member(
Box::new(JsValue::WellKnownObject(WellKnownObjectKind::ChildProcess)),
Expand Down Expand Up @@ -714,6 +715,7 @@ async fn node_process_member(
.as_str()
.into(),
Some("cwd") => JsValue::WellKnownFunction(WellKnownFunctionKind::ProcessCwd),
Some("argv") => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessArgv),
Some("env") => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessEnv),
_ => JsValue::unknown(
JsValue::member(
Expand Down
58 changes: 56 additions & 2 deletions crates/turbopack-ecmascript/src/references/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use constant_condition::{ConstantCondition, ConstantConditionValue};
use constant_value::ConstantValue;
use indexmap::IndexSet;
use lazy_static::lazy_static;
use num_traits::Zero;
use parking_lot::Mutex;
use regex::Regex;
use swc_core::{
Expand Down Expand Up @@ -88,7 +89,8 @@ use super::{
graph::{create_graph, Effect},
linker::link,
well_known::replace_well_known,
JsValue, ObjectPart, WellKnownFunctionKind, WellKnownObjectKind,
ConstantValue as JsConstantValue, JsValue, ObjectPart, WellKnownFunctionKind,
WellKnownObjectKind,
},
errors,
parse::{parse, ParseResult},
Expand All @@ -108,7 +110,7 @@ use crate::{
imports::{ImportedSymbol, Reexport},
parse_require_context,
top_level_await::has_top_level_await,
ModuleValue, RequireContextValue,
ConstantNumber, ConstantString, ModuleValue, RequireContextValue,
},
chunk::EcmascriptExports,
code_gen::{
Expand Down Expand Up @@ -1323,6 +1325,12 @@ async fn handle_call<G: Fn(Vec<Effect>) + Send + Sync>(
}
JsValue::WellKnownFunction(WellKnownFunctionKind::ChildProcessSpawnMethod(name)) => {
let args = linked_args(args).await?;

// Is this specifically `spawn(process.argv[0], ['-e', ...])`?
if is_invoking_node_process_eval(&args) {
return Ok(());
}

if !args.is_empty() {
let mut show_dynamic_warning = false;
let pat = js_value_to_pattern(&args[0]);
Expand Down Expand Up @@ -2715,3 +2723,49 @@ fn detect_dynamic_export(p: &Program) -> DetectedDynamicExportType {
DetectedDynamicExportType::None
}
}

/// Detects whether a list of arguments is specifically
/// `(process.argv[0], ['-e', ...])`. This is useful for detecting if a node
/// process is being spawned to interpret a string of JavaScript code, and does
/// not require static analysis.
fn is_invoking_node_process_eval(args: &[JsValue]) -> bool {
if args.len() < 2 {
return false;
}

if let JsValue::Member(_, obj, constant) = &args[0] {
// Is the first argument to spawn `process.argv[]`?
if let (
box JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessArgv),
box JsValue::Constant(JsConstantValue::Num(ConstantNumber(num))),
) = (obj, constant)
{
// Is it specifically `process.argv[0]`?
if num.is_zero() {
if let JsValue::Array {
total_nodes: _,
items,
mutable: _,
} = &args[1]
{
// Is `-e` one of the arguments passed to the program?
if items.iter().any(|e| {
if let JsValue::Constant(JsConstantValue::Str(ConstantString::Word(arg))) =
e
{
arg == "-e"
} else {
false
}
}) {
// If so, this is likely spawning node to evaluate a string, and
// does not need to be statically analyzed.
return true;
}
}
}
}
}

false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { spawn } from "child_process";

let x = spawn(process.argv[0], ["-e", "console.log('foo');"]);

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"environment": "NodeJs"
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f1ec483

Please sign in to comment.