use super::*;
fn network_tainted_param_indices(call_name: &str) -> Vec<usize> {
match call_name {
"request" => vec![1, 2],
_ => vec![0],
}
}
fn unwrap_expr(e: &Expr) -> &Expr {
match e {
Expr::Paren(p) => unwrap_expr(&p.expr),
_ => e,
}
}
impl JsParser {
fn callback_param_sources_for_argument(
&mut self,
call_name: &str,
prop_name: Option<&str>,
receiver_obj: Option<NodeId>,
arg_index: usize,
is_fn_arg: bool,
) -> Option<Vec<Option<NodeId>>> {
if !is_fn_arg {
return None;
}
if matches!(prop_name, Some("on" | "addEventListener")) && arg_index == 1 {
return Some(vec![receiver_obj]);
}
if matches!(prop_name, Some("then" | "catch")) && arg_index == 0 {
return Some(vec![receiver_obj]);
}
if let Some(receiver) = receiver_obj {
match prop_name {
Some("map" | "filter" | "forEach" | "find" | "some" | "every" | "flatMap") if arg_index == 0 => {
return Some(vec![Some(self.array_element_binding_source(receiver)), None, Some(receiver)]);
}
Some("reduce") if arg_index == 0 => {
let element = self.array_element_binding_source(receiver);
return Some(vec![None, Some(element), Some(receiver)]);
}
_ => {}
}
}
if matches!(
call_name,
"https.get"
| "http.get"
| "https.request"
| "http.request"
| "net.connect"
| "net.createConnection"
| "request"
) && arg_index >= 1
{
let mut sources = Vec::new();
let tainted = network_tainted_param_indices(call_name);
let max_index = tainted.iter().copied().max().unwrap_or(0);
sources.resize(max_index + 1, None);
for idx in tainted {
sources[idx] = None;
}
return Some(sources);
}
Some(vec![None])
}
fn apply_network_param_taint(
&mut self,
call_name: &str,
call_result: NodeId,
arg_index: usize,
is_fn_arg: bool,
) {
if !is_fn_arg || arg_index < 1 {
return;
}
let tainted = match call_name {
"https.get"
| "http.get"
| "https.request"
| "http.request"
| "net.connect"
| "net.createConnection"
| "request" => network_tainted_param_indices(call_name),
_ => return,
};
let Some(params) = self.last_fn_params.take() else {
return;
};
for (index, param_node) in params.iter().enumerate() {
if tainted.contains(&index) {
self.flow(call_result, *param_node);
}
}
self.last_fn_params = Some(params);
}
pub(super) fn eval_call_expr(&mut self, call: &CallExpr) -> NodeId {
let (receiver_obj, member_name) = match &call.callee {
Callee::Expr(e) => {
if let Expr::Member(m) = &**e {
let obj = self.eval_expr(&m.obj);
let name = match &m.prop {
MemberProp::Ident(i) => Some(i.sym.to_string()),
MemberProp::Computed(c) => self.try_infer_string_literal(&c.expr),
_ => None,
};
(Some(obj), name)
} else {
(None, None)
}
}
_ => (None, None),
};
let callee = match &call.callee {
Callee::Super(_) => self.graph.add_node(NodeKind::Variable, "super".into(), None),
Callee::Import(_) => self.graph.add_node(NodeKind::Variable, "import".into(), None),
Callee::Expr(e) => self.eval_expr(e),
};
let callee_name = self.name(callee);
let callee_alias = self
.graph
.node(callee)
.and_then(|n| n.alias.as_ref())
.map(|s| s.as_str());
let is_require = callee_name == "require" || callee_alias == Some("require");
let is_reflect_apply =
callee_name == "Reflect.apply" || callee_alias == Some("Reflect.apply");
let is_reflect_construct =
callee_name == "Reflect.construct" || callee_alias == Some("Reflect.construct");
let mut call_name = if is_require {
self.infer_require(call)
} else {
self.graph
.node(callee)
.and_then(|n| n.alias.as_ref())
.cloned()
.unwrap_or_else(|| callee_name.clone())
};
let id = self.graph.add_node(NodeKind::Call, call_name.clone(), None);
if is_require {
self.require_calls.push((id, call_name.clone()));
}
if let Some(node) = self.graph.node_mut(id) {
node.alias = Some(call_name.clone());
}
self.graph.add_edge(callee, id, EdgeKind::Call);
self.graph.add_edge(callee, id, EdgeKind::Assignment);
if let Some(receiver) = receiver_obj {
self.graph.add_edge(receiver, id, EdgeKind::Assignment);
}
let callee_is_fn_expr = matches!(
&call.callee,
Callee::Expr(e) if matches!(unwrap_expr(e), Expr::Fn(_) | Expr::Arrow(_))
);
let iife_params = if callee_is_fn_expr {
self.last_fn_params.take()
} else {
None
};
let mut arg_nodes = Vec::new();
for (index, arg) in call.args.iter().enumerate() {
let is_fn_arg = matches!(&*arg.expr, Expr::Arrow(_) | Expr::Fn(_));
self.pending_callback_param_sources = self.callback_param_sources_for_argument(
&call_name,
member_name.as_deref(),
receiver_obj,
index,
is_fn_arg,
);
let arg_node = self.eval_expr(&arg.expr);
arg_nodes.push(arg_node);
self.graph.add_edge(arg_node, id, EdgeKind::Argument);
self.apply_network_param_taint(&call_name, id, index, is_fn_arg);
if index == 1 && matches!(member_name.as_deref(), Some("on" | "addEventListener")) {
if let Some(params) = self.last_fn_params.clone() {
if let Some(obj) = receiver_obj {
self.event_handlers.push((obj, params));
}
}
}
if is_fn_arg {
let _ = self.last_fn_params.take();
}
if member_name.as_deref() == Some("set")
&& index == call.args.len().saturating_sub(1)
{
if let Some(obj) = receiver_obj {
self.graph.add_edge(arg_node, obj, EdgeKind::Assignment);
}
}
}
self.pending_callback_param_sources = None;
if matches!(member_name.as_deref(), Some("then" | "catch")) {
if let Some(first_arg) = arg_nodes.first() {
if let Some(params) = self.node_to_params.get(first_arg).cloned() {
if let (Some(¶m), Some(receiver)) = (params.first(), receiver_obj) {
self.flow(receiver, param);
}
} else if let Some(first_arg_name) = self.graph.node(*first_arg).map(|n| n.name.clone()) {
if let Some(params) = self.function_params.get(&first_arg_name).cloned() {
if let (Some(¶m), Some(receiver)) = (params.first(), receiver_obj) {
self.flow(receiver, param);
}
}
}
}
}
if is_reflect_apply {
if let Some(&target_node) = arg_nodes.first() {
call_name = self
.graph
.node(target_node)
.and_then(|n| n.alias.as_ref())
.cloned()
.unwrap_or_else(|| self.name(target_node));
if let Some(node) = self.graph.node_mut(id) {
node.name = call_name.clone();
node.alias = Some(call_name.clone());
}
}
} else if is_reflect_construct {
if let Some(&target_node) = arg_nodes.first() {
let target_name = self
.graph
.node(target_node)
.and_then(|n| n.alias.as_ref())
.cloned()
.unwrap_or_else(|| self.name(target_node));
call_name = format!("new {}", target_name);
if let Some(node) = self.graph.node_mut(id) {
node.name = call_name.clone();
node.alias = Some(call_name.clone());
}
}
}
if member_name.as_deref() == Some("emit") {
if let Some(obj) = receiver_obj {
for (handler_obj, params) in &self.event_handlers {
if *handler_obj == obj {
for (param_idx, param) in params.iter().enumerate() {
if let Some(&arg) = arg_nodes.get(param_idx + 1) {
self.graph.add_edge(arg, *param, EdgeKind::Assignment);
}
}
}
}
}
}
if member_name.as_deref() == Some("pipe") {
if let Some(obj) = receiver_obj {
for arg in &arg_nodes {
self.graph.add_edge(obj, *arg, EdgeKind::Assignment);
}
}
}
let callee_is_simple_ident = matches!(
&call.callee,
Callee::Expr(e) if matches!(e.as_ref(), Expr::Ident(_))
);
if callee_is_simple_ident {
if let Some(params) = self.function_params.get(&callee_name).cloned() {
for (param_idx, param_node) in params.iter().enumerate() {
if let Some(&arg_node) = arg_nodes.get(param_idx) {
self.graph.add_edge(arg_node, *param_node, EdgeKind::Assignment);
}
}
}
if let Some(fn_node) = self.resolve(&callee_name) {
self.graph.add_edge(fn_node, id, EdgeKind::Return);
}
}
if let Some(params) = iife_params {
for (param_idx, param_node) in params.iter().enumerate() {
if let Some(&arg_node) = arg_nodes.get(param_idx) {
self.graph.add_edge(arg_node, *param_node, EdgeKind::Assignment);
}
}
}
self.graph.add_edge(callee, id, EdgeKind::Assignment);
if let Callee::Expr(e) = &call.callee {
if let Expr::Ident(i) = &**e {
if let Some(resolver_node) = self.resolve(&i.sym.to_string()) {
for (param_node, promise_node) in &self.promise_resolvers {
if *param_node == resolver_node {
if let Some(&arg_node) = arg_nodes.first() {
self.graph.add_edge(arg_node, *promise_node, EdgeKind::Return);
}
}
}
}
}
}
let is_define_property =
call_name == "Object.defineProperty" || call_name == "defineProperty";
let is_define_properties =
call_name == "Object.defineProperties" || call_name == "defineProperties";
if is_define_property {
if let (Some(&obj_arg), Some(prop_arg)) = (arg_nodes.first(), call.args.get(1)) {
if let Some(property) = self.try_infer_string_literal(&prop_arg.expr) {
let prop_node = self.member_binding_source(obj_arg, &property);
if let Some(desc_arg) = call.args.get(2) {
if let Expr::Object(o) = &*desc_arg.expr {
for p in &o.props {
if let PropOrSpread::Prop(pp) = p {
if let Prop::KeyValue(kv) = &**pp {
if let Some(key) = prop_ident(&kv.key) {
if key.sym == "get" {
let getter_node = self.eval_expr(&kv.value);
self.graph.add_edge(
getter_node,
prop_node,
EdgeKind::Assignment,
);
}
}
}
}
}
}
}
}
}
}
if is_define_properties {
if let (Some(&obj_arg), Some(desc_arg)) = (arg_nodes.first(), call.args.get(1)) {
if let Expr::Object(o) = &*desc_arg.expr {
for p in &o.props {
if let PropOrSpread::Prop(pp) = p {
if let Prop::KeyValue(kv) = &**pp {
let property = prop_name(&kv.key)
.unwrap_or_else(|| "<unknown>".to_string());
let prop_node = self.member_binding_source(obj_arg, &property);
if let Expr::Object(desc_obj) = &*kv.value {
for dp in &desc_obj.props {
if let PropOrSpread::Prop(dpp) = dp {
if let Prop::KeyValue(dkv) = &**dpp {
if let Some(dkey) = prop_ident(&dkv.key) {
if dkey.sym == "get" {
let getter_node = self.eval_expr(&dkv.value);
self.graph.add_edge(
getter_node,
prop_node,
EdgeKind::Assignment,
);
}
}
}
}
}
}
}
}
}
}
}
}
id
}
pub(super) fn eval_new_expr(&mut self, new_expr: &NewExpr) -> NodeId {
let callee = self.eval_expr(&new_expr.callee);
let callee_name = self
.graph
.node(callee)
.and_then(|n| n.alias.as_ref())
.cloned()
.unwrap_or_else(|| self.name(callee));
let call_name = format!("new {}", callee_name);
let id = self.graph.add_node(NodeKind::Call, call_name.clone(), None);
if let Some(node) = self.graph.node_mut(id) {
node.alias = Some(call_name);
}
self.graph.add_edge(callee, id, EdgeKind::Call);
let is_promise = callee_name == "Promise";
let is_proxy = callee_name == "Proxy";
let mut proxy_target_node = None;
for (index, arg) in new_expr.args.as_deref().unwrap_or(&[]).iter().enumerate() {
if is_proxy && index == 0 {
let target = self.eval_expr(&arg.expr);
proxy_target_node = Some(target);
self.graph.add_edge(target, id, EdgeKind::Argument);
continue;
}
if is_proxy && index == 1 {
self.pending_callback_param_sources = proxy_target_node.map(|t| vec![Some(t), None, None]);
}
if is_promise && matches!(&*arg.expr, Expr::Arrow(_) | Expr::Fn(_)) {
self.pending_promise_node = Some(id);
}
let arg_node = self.eval_expr(&arg.expr);
self.graph.add_edge(arg_node, id, EdgeKind::Argument);
if is_promise && matches!(&*arg.expr, Expr::Arrow(_) | Expr::Fn(_)) {
self.pending_promise_node = None;
}
if is_proxy && index == 1 {
self.pending_callback_param_sources = None;
}
}
if is_proxy {
if let Some(handler_arg) = new_expr.args.as_deref().unwrap_or(&[]).get(1) {
if let Expr::Ident(i) = &*handler_arg.expr {
if let Some(handler_node) = self.resolve(&i.sym.to_string()) {
if let Some(bindings) = self.composite_bindings.get(&handler_node).cloned() {
for (suffix, prop_node) in bindings {
let trap = suffix.trim_start_matches('.');
if matches!(trap, "get" | "set" | "apply" | "construct" | "has" | "ownKeys" | "defineProperty" | "deleteProperty" | "getOwnPropertyDescriptor" | "getPrototypeOf" | "isExtensible" | "preventExtensions" | "setPrototypeOf") {
if let Some(params) = self.node_to_params.get(&prop_node).cloned() {
if let (Some(&target_param), Some(target_node)) = (params.first(), proxy_target_node) {
self.flow(target_node, target_param);
}
}
}
}
}
}
}
}
}
id
}
}