use std::collections::{HashMap, HashSet};
use super::taint::{TaintInfo, TaintSink, TaintSinkType, TaintSourceType};
use super::taint::compute_taint;
use crate::types::{BlockType, CfgBlock, CfgEdge, CfgInfo, EdgeType, RefType, VarRef};
use crate::Language;
mod fixtures {
use super::*;
pub fn make_block(id: usize, start: u32, end: u32) -> CfgBlock {
CfgBlock {
id,
block_type: BlockType::Body,
lines: (start, end),
calls: Vec::new(),
}
}
pub fn make_def(name: &str, line: u32) -> VarRef {
VarRef {
name: name.to_string(),
ref_type: RefType::Definition,
line,
column: 0,
context: None,
group_id: None,
}
}
pub fn make_use(name: &str, line: u32) -> VarRef {
VarRef {
name: name.to_string(),
ref_type: RefType::Use,
line,
column: 0,
context: None,
group_id: None,
}
}
pub fn linear_cfg() -> CfgInfo {
CfgInfo {
function: "linear".to_string(),
blocks: vec![
make_block(0, 1, 2),
make_block(1, 3, 4),
make_block(2, 5, 6),
],
edges: vec![
CfgEdge {
from: 0,
to: 1,
edge_type: EdgeType::Unconditional,
condition: None,
},
CfgEdge {
from: 1,
to: 2,
edge_type: EdgeType::Unconditional,
condition: None,
},
],
entry_block: 0,
exit_blocks: vec![2],
cyclomatic_complexity: 1,
nested_functions: HashMap::new(),
}
}
pub fn loop_cfg() -> CfgInfo {
CfgInfo {
function: "loop".to_string(),
blocks: vec![
make_block(0, 1, 2), make_block(1, 3, 4), make_block(2, 5, 6), make_block(3, 7, 8), ],
edges: vec![
CfgEdge {
from: 0,
to: 1,
edge_type: EdgeType::Unconditional,
condition: None,
},
CfgEdge {
from: 1,
to: 2,
edge_type: EdgeType::True,
condition: Some("i < n".to_string()),
},
CfgEdge {
from: 1,
to: 3,
edge_type: EdgeType::False,
condition: Some("i < n".to_string()),
},
CfgEdge {
from: 2,
to: 1,
edge_type: EdgeType::BackEdge,
condition: None,
},
],
entry_block: 0,
exit_blocks: vec![3],
cyclomatic_complexity: 2,
nested_functions: HashMap::new(),
}
}
pub fn empty_cfg() -> CfgInfo {
CfgInfo {
function: "empty".to_string(),
blocks: vec![make_block(0, 1, 1)],
edges: vec![],
entry_block: 0,
exit_blocks: vec![0],
cyclomatic_complexity: 1,
nested_functions: HashMap::new(),
}
}
}
#[test]
fn test_taint_source_type_variants() {
let variants = [
TaintSourceType::UserInput,
TaintSourceType::Stdin,
TaintSourceType::HttpParam,
TaintSourceType::HttpBody,
TaintSourceType::EnvVar,
TaintSourceType::FileRead,
];
assert_eq!(variants.len(), 6);
}
#[test]
fn test_taint_sink_type_variants() {
let variants = [
TaintSinkType::SqlQuery,
TaintSinkType::CodeEval,
TaintSinkType::CodeExec,
TaintSinkType::CodeCompile,
TaintSinkType::ShellExec,
TaintSinkType::FileWrite,
TaintSinkType::HtmlOutput,
TaintSinkType::FileOpen,
TaintSinkType::HttpRequest,
TaintSinkType::Deserialize,
];
assert_eq!(variants.len(), 10);
}
#[test]
fn test_taint_info_struct_fields() {
let info = TaintInfo::new("test_func");
assert_eq!(info.function_name, "test_func");
assert!(info.tainted_vars.is_empty());
assert!(info.sources.is_empty());
assert!(info.sinks.is_empty());
assert!(info.flows.is_empty());
assert!(info.sanitized_vars.is_empty());
}
#[test]
fn test_taint_info_is_tainted() {
let mut info = TaintInfo::new("test");
let mut block_taint = HashSet::new();
block_taint.insert("user_input".to_string());
info.tainted_vars.insert(0, block_taint);
assert!(info.is_tainted(0, "user_input"));
assert!(!info.is_tainted(0, "other_var"));
assert!(!info.is_tainted(1, "user_input")); }
#[test]
fn test_taint_info_is_tainted_nonexistent_block() {
let info = TaintInfo::new("test");
assert!(!info.is_tainted(999, "any_var"));
}
#[test]
fn test_taint_info_get_vulnerabilities() {
let mut info = TaintInfo::new("test");
info.sinks.push(TaintSink {
var: "query".to_string(),
line: 5,
sink_type: TaintSinkType::SqlQuery,
tainted: true,
statement: Some("cursor.execute(query)".to_string()),
});
info.sinks.push(TaintSink {
var: "safe_query".to_string(),
line: 10,
sink_type: TaintSinkType::SqlQuery,
tainted: false,
statement: Some("cursor.execute(safe_query)".to_string()),
});
let vulns = info.get_vulnerabilities();
assert_eq!(vulns.len(), 1);
assert_eq!(vulns[0].var, "query");
}
#[test]
fn test_taint_info_default_values() {
let info = TaintInfo::default();
assert!(info.function_name.is_empty());
assert!(info.tainted_vars.is_empty());
assert!(info.sources.is_empty());
assert!(info.sinks.is_empty());
assert!(info.flows.is_empty());
assert!(info.sanitized_vars.is_empty());
}
#[test]
fn test_propagate_through_assignment() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("x", 1),
make_use("x", 3),
make_def("y", 3),
make_use("y", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "x = input()".to_string());
statements.insert(3, "y = x".to_string());
statements.insert(5, "print(y)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(result.is_tainted(0, "x"), "x should be tainted at block 0");
assert!(result.is_tainted(1, "x"), "x should be tainted at block 1");
assert!(
result.is_tainted(1, "y"),
"y should be tainted at block 1 (via x)"
);
assert!(result.is_tainted(2, "y"), "y should be tainted at block 2");
}
#[test]
fn test_propagate_through_concatenation() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("user", 1),
make_use("user", 3),
make_def("query", 3),
];
let mut statements = HashMap::new();
statements.insert(1, "user = input()".to_string());
statements.insert(
3,
"query = \"SELECT * FROM users WHERE name = '\" + user + \"'\"".to_string(),
);
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(
result.is_tainted(1, "query"),
"query should be tainted via concatenation"
);
}
#[test]
fn test_propagate_across_blocks() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("x", 1),
make_use("x", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "x = input()".to_string());
statements.insert(5, "print(x)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(
result.is_tainted(2, "x"),
"taint should propagate to block 2"
);
}
#[test]
fn test_taint_does_not_propagate_backward() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_use("x", 1),
make_def("x", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "print(x)".to_string());
statements.insert(5, "x = input()".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(
!result.is_tainted(0, "x"),
"taint should not propagate backward"
);
}
#[test]
fn test_propagate_through_loop() {
use fixtures::*;
let cfg = loop_cfg();
let refs = vec![
make_def("data", 1),
make_use("data", 5),
make_def("result", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "data = input()".to_string());
statements.insert(5, "result = process(data)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(
result.is_tainted(2, "data"),
"data should be tainted in loop body"
);
assert!(result.is_tainted(2, "result"), "result should be tainted");
}
#[test]
fn test_sanitizer_removes_taint() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("x", 1),
make_use("x", 3),
make_def("y", 3),
make_use("y", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "x = input()".to_string());
statements.insert(3, "y = int(x)".to_string());
statements.insert(5, "print(y)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(result.is_tainted(0, "x"), "x should be tainted");
assert!(result.is_tainted(1, "x"), "x should still be tainted");
assert!(
!result.is_tainted(1, "y"),
"y should NOT be tainted (sanitized)"
);
assert!(
result.sanitized_vars.contains("y"),
"y should be in sanitized_vars"
);
}
#[test]
fn test_multiple_sources_merge() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("x", 1),
make_def("y", 2),
make_use("x", 3),
make_use("y", 3),
make_def("z", 3),
];
let mut statements = HashMap::new();
statements.insert(1, "x = input()".to_string());
statements.insert(2, "y = os.environ['KEY']".to_string());
statements.insert(3, "z = x + y".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(
result.is_tainted(1, "z"),
"z should be tainted from multiple sources"
);
assert_eq!(result.sources.len(), 2);
}
#[test]
fn test_convergence_with_cycles() {
use fixtures::*;
let cfg = loop_cfg();
let refs = vec![
make_def("x", 1),
make_use("x", 3),
make_use("x", 5),
make_def("x", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "x = input()".to_string());
statements.insert(3, "while x < 10:".to_string());
statements.insert(5, "x = x + 1".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(result.is_tainted(1, "x"));
assert!(result.is_tainted(2, "x"));
}
#[test]
fn test_detect_sql_injection() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("user_input", 1),
make_use("user_input", 3),
make_def("query", 3),
make_use("query", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "user_input = input()".to_string());
statements.insert(
3,
"query = \"SELECT * FROM users WHERE id = \" + user_input".to_string(),
);
statements.insert(5, "cursor.execute(query)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
let vulns = result.get_vulnerabilities();
assert_eq!(vulns.len(), 1, "Should detect 1 SQL injection");
assert!(matches!(vulns[0].sink_type, TaintSinkType::SqlQuery));
assert_eq!(result.flows.len(), 1);
assert_eq!(result.flows[0].source.var, "user_input");
assert_eq!(result.flows[0].sink.var, "query");
}
#[test]
fn test_detect_command_injection() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("cmd", 1),
make_use("cmd", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "cmd = input()".to_string());
statements.insert(5, "os.system(cmd)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
let vulns = result.get_vulnerabilities();
assert_eq!(vulns.len(), 1, "Should detect 1 command injection");
assert!(matches!(vulns[0].sink_type, TaintSinkType::ShellExec));
}
#[test]
fn test_detect_code_injection() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("code", 1),
make_use("code", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "code = request.json['code']".to_string());
statements.insert(5, "eval(code)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
let vulns = result.get_vulnerabilities();
assert_eq!(vulns.len(), 1, "Should detect 1 code injection");
assert!(matches!(vulns[0].sink_type, TaintSinkType::CodeEval));
}
#[test]
fn test_no_vulnerability_when_sanitized() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("user_id", 1),
make_use("user_id", 3),
make_def("safe_id", 3),
make_use("safe_id", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "user_id = input()".to_string());
statements.insert(3, "safe_id = int(user_id)".to_string());
statements.insert(
5,
"cursor.execute(\"SELECT * FROM users WHERE id = \" + str(safe_id))".to_string(),
);
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
let vulns = result.get_vulnerabilities();
assert!(
vulns.is_empty(),
"Should NOT detect vulnerability (sanitized)"
);
assert!(result.sanitized_vars.contains("safe_id"));
}
#[test]
fn test_no_vulnerability_when_untainted() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("query", 1),
make_use("query", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "query = \"SELECT * FROM users\"".to_string());
statements.insert(5, "cursor.execute(query)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
let vulns = result.get_vulnerabilities();
assert!(
vulns.is_empty(),
"Should NOT detect vulnerability (untainted)"
);
assert!(result.sources.is_empty(), "Should have no sources");
}
#[test]
fn test_multiple_vulnerabilities() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("data", 1),
make_use("data", 3),
make_use("data", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "data = input()".to_string());
statements.insert(3, "cursor.execute(data)".to_string());
statements.insert(5, "os.system(data)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
let vulns = result.get_vulnerabilities();
assert_eq!(vulns.len(), 2, "Should detect 2 vulnerabilities");
}
#[test]
fn test_empty_function() {
use fixtures::*;
let cfg = empty_cfg();
let refs: Vec<VarRef> = vec![];
let result = compute_taint(&cfg, &refs, &HashMap::new(), Language::Python).unwrap();
assert_eq!(result.function_name, "empty");
assert!(result.sources.is_empty());
assert!(result.sinks.is_empty());
assert!(result.flows.is_empty());
assert!(result.get_vulnerabilities().is_empty());
}
#[test]
fn test_no_sources_in_function() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("x", 1),
make_use("x", 3),
make_def("y", 3),
];
let mut statements = HashMap::new();
statements.insert(1, "x = 42".to_string());
statements.insert(3, "y = x".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(result.sources.is_empty(), "Should have no sources");
for vars in result.tainted_vars.values() {
assert!(vars.is_empty(), "No variables should be tainted");
}
}
#[test]
fn test_no_sinks_in_function() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("x", 1),
make_use("x", 3),
make_def("y", 3),
make_use("y", 5),
];
let mut statements = HashMap::new();
statements.insert(1, "x = input()".to_string());
statements.insert(3, "y = x".to_string());
statements.insert(5, "print(y)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(!result.sources.is_empty(), "Should have sources");
assert!(result.sinks.is_empty(), "Should have no sinks");
assert!(
result.get_vulnerabilities().is_empty(),
"Should have no vulns"
);
}
#[test]
fn test_unreachable_code() {
use fixtures::*;
let mut cfg = linear_cfg();
cfg.blocks.push(make_block(3, 7, 8));
let refs = vec![
make_def("x", 1),
make_use("x", 7),
make_def("y", 7),
];
let mut statements = HashMap::new();
statements.insert(1, "x = input()".to_string());
statements.insert(7, "y = x".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(!result.is_tainted(3, "y"));
}
#[test]
fn test_indirect_taint_through_function_call() {
use fixtures::*;
let cfg = linear_cfg();
let refs = vec![
make_def("x", 1),
make_use("x", 3),
make_def("y", 3),
];
let mut statements = HashMap::new();
statements.insert(1, "x = input()".to_string());
statements.insert(3, "y = unknown_func(x)".to_string());
let result = compute_taint(&cfg, &refs, &statements, Language::Python).unwrap();
assert!(
result.is_tainted(1, "y"),
"Conservative: function result is tainted"
);
}
#[test]
#[ignore = "Phase 7: TaintInfo::to_json_value not implemented"]
fn test_taint_info_to_json() {
todo!("Implement TaintInfo::to_json_value");
}
#[test]
fn test_taint_source_type_serialization() {
let source_type = TaintSourceType::HttpParam;
let json = serde_json::to_string(&source_type).unwrap();
assert_eq!(json, "\"http_param\"");
let source_type = TaintSourceType::UserInput;
let json = serde_json::to_string(&source_type).unwrap();
assert_eq!(json, "\"user_input\"");
}
#[test]
fn test_taint_sink_type_serialization() {
let sink_type = TaintSinkType::SqlQuery;
let json = serde_json::to_string(&sink_type).unwrap();
assert_eq!(json, "\"sql_query\"");
let sink_type = TaintSinkType::ShellExec;
let json = serde_json::to_string(&sink_type).unwrap();
assert_eq!(json, "\"shell_exec\"");
}
use super::taint::{compute_taint_with_tree, detect_sinks_ast, detect_sources_ast};
use crate::ast::parser::ParserPool;
#[test]
fn test_ast_python_eval_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "# eval(user_code) - dangerous, don't use\nx = 1";
let tree = pool.parse(source, Language::Python).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Python, None);
assert!(
sinks.is_empty(),
"eval in comment should NOT be detected as sink, got: {:?}",
sinks
);
}
#[test]
fn test_ast_python_input_in_string_not_source() {
let pool = ParserPool::new();
let source = "msg = \"use input() to get data\"";
let tree = pool.parse(source, Language::Python).unwrap();
let root = tree.root_node();
let sources = detect_sources_ast(&root, source.as_bytes(), Language::Python, None);
assert!(
sources.is_empty(),
"input() in string should NOT be detected as source, got: {:?}",
sources
);
}
#[test]
fn test_ast_python_eval_in_code_is_sink() {
let pool = ParserPool::new();
let source = "result = eval(user_code)";
let tree = pool.parse(source, Language::Python).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Python, None);
assert!(
!sinks.is_empty(),
"eval in actual code should be detected as sink"
);
assert!(
sinks.iter().any(|s| s.sink_type == TaintSinkType::CodeEval),
"eval should be CodeEval, got: {:?}",
sinks
);
}
#[test]
fn test_ast_python_input_in_code_is_source() {
let pool = ParserPool::new();
let source = "user_input = input()";
let tree = pool.parse(source, Language::Python).unwrap();
let root = tree.root_node();
let sources = detect_sources_ast(&root, source.as_bytes(), Language::Python, None);
assert!(
!sources.is_empty(),
"input() in code should be detected as source"
);
assert!(
sources
.iter()
.any(|s| s.source_type == TaintSourceType::UserInput),
"input should be UserInput, got: {:?}",
sources
);
assert_eq!(sources[0].var, "user_input");
}
#[test]
fn test_ast_python_os_system_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "# os.system(cmd) is dangerous\nresult = 42";
let tree = pool.parse(source, Language::Python).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Python, None);
assert!(
sinks.is_empty(),
"os.system in comment should NOT be detected as sink"
);
}
#[test]
fn test_ast_typescript_eval_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "// eval(code) - never do this\nconst x = 1;";
let tree = pool.parse(source, Language::TypeScript).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::TypeScript, None);
assert!(
sinks.is_empty(),
"eval in TS comment should NOT be detected as sink"
);
}
#[test]
fn test_ast_typescript_eval_in_code_is_sink() {
let pool = ParserPool::new();
let source = "eval(userInput);";
let tree = pool.parse(source, Language::TypeScript).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::TypeScript, None);
assert!(
!sinks.is_empty(),
"eval in TS code should be detected as sink"
);
}
#[test]
fn test_ast_go_exec_command_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "package main\n// exec.Command(cmd) is dangerous\nfunc main() {}";
let tree = pool.parse(source, Language::Go).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Go, None);
assert!(
sinks.is_empty(),
"exec.Command in Go comment should NOT be detected as sink"
);
}
#[test]
fn test_ast_c_system_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "// system(cmd) is dangerous\nint main() { return 0; }";
let tree = pool.parse(source, Language::C).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::C, None);
assert!(
sinks.is_empty(),
"system() in C comment should NOT be detected as sink"
);
}
#[test]
fn test_ast_c_system_in_code_is_sink() {
let pool = ParserPool::new();
let source = "int main() { system(cmd); return 0; }";
let tree = pool.parse(source, Language::C).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::C, None);
assert!(
!sinks.is_empty(),
"system() in C code should be detected as sink"
);
}
#[test]
fn test_ast_java_runtime_exec_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "class Main {\n// Runtime.getRuntime().exec(cmd)\nvoid f() {} }";
let tree = pool.parse(source, Language::Java).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Java, None);
assert!(
sinks.is_empty(),
"Runtime.exec in Java comment should NOT be detected as sink"
);
}
#[test]
fn test_ast_rust_command_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "fn main() {\n// Command::new(cmd).spawn()\nlet x = 1;\n}";
let tree = pool.parse(source, Language::Rust).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Rust, None);
assert!(
sinks.is_empty(),
"Command::new in Rust comment should NOT be detected as sink"
);
}
#[test]
fn test_ast_ruby_eval_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "# eval(code) is dangerous\nx = 1";
let tree = pool.parse(source, Language::Ruby).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Ruby, None);
assert!(
sinks.is_empty(),
"eval in Ruby comment should NOT be detected as sink"
);
}
#[test]
fn test_ast_php_eval_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "<?php\n// eval($code) is dangerous\n$x = 1;\n?>";
let tree = pool.parse(source, Language::Php).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Php, None);
assert!(
sinks.is_empty(),
"eval in PHP comment should NOT be detected as sink"
);
}
#[test]
fn test_ast_lua_loadstring_in_comment_not_sink() {
let pool = ParserPool::new();
let source = "-- loadstring(code) is dangerous\nlocal x = 1";
let tree = pool.parse(source, Language::Lua).unwrap();
let root = tree.root_node();
let sinks = detect_sinks_ast(&root, source.as_bytes(), Language::Lua, None);
assert!(
sinks.is_empty(),
"loadstring in Lua comment should NOT be detected as sink"
);
}
#[test]
fn test_compute_taint_with_tree_sql_injection() {
use fixtures::*;
let pool = ParserPool::new();
let source_code = "user_input = input()\nquery = \"SELECT * FROM users WHERE id = \" + user_input\ncursor.execute(query)";
let tree = pool.parse(source_code, Language::Python).unwrap();
let cfg = linear_cfg();
let refs = vec![
make_def("user_input", 1),
make_use("user_input", 2),
make_def("query", 2),
make_use("query", 3),
];
let mut statements = HashMap::new();
statements.insert(1, "user_input = input()".to_string());
statements.insert(
2,
"query = \"SELECT * FROM users WHERE id = \" + user_input".to_string(),
);
statements.insert(3, "cursor.execute(query)".to_string());
let result = compute_taint_with_tree(
&cfg,
&refs,
&statements,
Some(&tree),
Some(source_code.as_bytes()),
Language::Python,
None,
)
.unwrap();
assert!(
!result.sources.is_empty(),
"Should detect input() as source"
);
}