mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-27 20:29:39 +02:00
Prerelease cleanup (#46)
* feat: Add const_bound_vars tracking to prevent false positives in ownership checks
* feat: Introduce field interner and typed bounded vars for enhanced type tracking
* feat: Add typed_call_receivers and typed_bounded_dto_fields for enhanced type tracking
* feat: Centralize method name extraction with bare_method_name helper
* feat: Implement Phase-6 hierarchy fan-out for runtime virtual dispatch
* feat: Enhance C++ taint tracking with additional container operations and inline method resolution
* feat: Introduce field-sensitive points-to analysis for enhanced resource tracking
* feat: Implement Pointer-Phase 6 subscript handling for enhanced container analysis
* test: Add comprehensive tests for JavaScript control flow constructs and lattice operations
* docs: Update advanced analysis documentation with field-sensitive points-to and hierarchy fan-out details
* test: Add comprehensive tests for lattice algebra laws and SSA edge cases
* feat: Add destructured session user handling and safe user ID access patterns
* feat: Implement row-population reverse-walk for enhanced authorization checks
* feat: Enhance authorization checks with local alias chain for self-actor types
* feat: Introduce ActiveRecord query safety checks and enhance snippet extraction
* feat: Implement chained method call inner-gate rebinding for SSRF prevention
* feat: Add observability and error modules, enhance debug functionality, and implement theme context
* feat: Remove Auth Analysis page and update navigation to redirect to Explorer
* feat: Optimize SSA lowering by sharing results between taint engine and artifact extractor
* feat: Optimize SSA lowering by sharing results between taint engine and artifact extractor
* feat: Reset path-safe-suppressed spans before lowering to maintain analysis integrity
* fix(ssa): ungate debug_assert_bfs_ordering for release-tests build
The helper at src/ssa/lower.rs was gated `#[cfg(debug_assertions)]` while
the unit test at the bottom of the file was gated only `#[cfg(test)]`.
Since `cfg(test)` is set in release builds with `--tests` but
`cfg(debug_assertions)` is not, `cargo build --release --tests` failed
with E0425. Removing the gate fixes the build; the body is `debug_assert!`
only, so the helper is free in release. Also drop the gate at the call
site to avoid a `dead_code` warning when the lib is built without
`--tests`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(closure-capture): flip JS/TS fixtures to required-finding
The JS and TS closure-capture fixtures pinned the old broken behaviour
via `forbidden_findings: [{ "id_prefix": "taint-" }]`. The engine now
correctly traces taint through the closure boundary (env source captured
by an arrow function, sunk via `child_process.exec` inside the body), so
the formerly-forbidden finding is a true positive.
Match the Python sibling's shape — `required_findings` with
`id_prefix` + `min_count` plus a small `noise_budget` — and rewrite the
companion READMEs and the phase8_fragility_tests doc-comments from
"known gap" to "regression guard".
Verified:
- cargo test --release --test phase8_fragility_tests → 8/8 pass
- cargo test --release --lib bfs_assertion → pass
- corpus benchmark F1 = 0.9976 (TP=205, FP=1, FN=0) — unchanged
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: Add OWASP mapping and baseline mutation hooks for enhanced security analysis
* feat: Introduce health module and enhance health score computation with calibration tests
* feat: Add expectations configuration and cleanup .gitignore for log files
* feat: Implement theme selection and enhance settings panel for triage sync
* feat: Suppress false positives for strcpy calls with literal sources in AST
* feat: Update analyse_function_ssa to return body CFG for accurate analysis
* feat: Add bug report and feature request templates for improved issue tracking
* feat: removed dev scripts
* feat: update README.md for clarity and consistency in fixture descriptions
* feat: removed dev docs
* feat: clean up error handling and UI elements for improved user experience
* feat: adjust button sizes in HeaderBar for better UI consistency
* feat: enhance taint analysis with additional context for sanitizer and taint findings
* cargo fmt
* prettier
* refactor: simplify conditional checks and improve code readability in AST and screenshot capture scripts
* feat: add script to frame PNG screenshots with brand gradient
* feat: add fuzzing support with new targets and CI workflows
* refactor: streamline match expressions and improve formatting in CLI and output handling
* feat: enhance configuration display with detailed output options
* feat: stage demo configuration for improved CLI screenshot output
* feat: expose merge_configs function for user-configurable settings
* refactor: simplify code structure and improve readability in config handling
* refactor: improve descriptions for vulnerability patterns in various languages
* feat: update MIT License section with additional usage details and copyright information
* feat: update screenshots
* refactor: update build process and paths for frontend assets
* feat: add cross-file taint fuzzing target and supporting dictionary
* refactor: clean up formatting and comments in fuzz configuration and example files
* refactor: remove outdated comments and clean up CI configuration files
* chore: update changelog dates and improve formatting in documentation
* refactor: update Cargo.toml and CI configuration for improved packaging and build process
* refactor: enhance quote-stripping logic to prevent panics and add regression tests
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
79c29b394d
commit
82f18184b1
348 changed files with 48731 additions and 2925 deletions
|
|
@ -1379,6 +1379,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let empty_succs = HashMap::new();
|
||||
|
|
@ -1436,6 +1438,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let empty_succs = HashMap::new();
|
||||
|
|
@ -1566,6 +1570,8 @@ mod tests {
|
|||
value_defs: vec![make_value_def(b0, n0), make_value_def(b1, n1)],
|
||||
cfg_node_map: [(n0, SsaValue(0)), (n1, SsaValue(1))].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let finding = make_finding(n0, n1);
|
||||
|
|
@ -1671,6 +1677,8 @@ mod tests {
|
|||
.into_iter()
|
||||
.collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
// Finding path goes through B0 → B1 → B3
|
||||
|
|
@ -1814,6 +1822,8 @@ mod tests {
|
|||
.into_iter()
|
||||
.collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let finding = Finding {
|
||||
|
|
@ -1923,6 +1933,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![(b0, b2)],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let mut exc_succs: HashMap<BlockId, SmallVec<[BlockId; 2]>> = HashMap::new();
|
||||
|
|
@ -1987,6 +1999,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![(b0, b2)],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let mut exc_succs: HashMap<BlockId, SmallVec<[BlockId; 2]>> = HashMap::new();
|
||||
|
|
@ -2041,6 +2055,7 @@ mod tests {
|
|||
value: SsaValue(1),
|
||||
op: SsaOp::Call {
|
||||
callee: "JSON.parse".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -2091,6 +2106,8 @@ mod tests {
|
|||
.into_iter()
|
||||
.collect(),
|
||||
exception_edges: vec![(b1, b2)],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let finding = Finding {
|
||||
|
|
|
|||
|
|
@ -1094,6 +1094,7 @@ fn handle_nested_calls(
|
|||
callee,
|
||||
args,
|
||||
receiver,
|
||||
..
|
||||
} = &inst.op
|
||||
{
|
||||
// Only attempt if the current result is opaque
|
||||
|
|
|
|||
|
|
@ -387,6 +387,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -430,6 +432,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -509,6 +513,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -569,6 +575,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -647,6 +655,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -716,6 +726,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -748,6 +760,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -802,6 +816,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -880,6 +896,8 @@ mod tests {
|
|||
],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -929,6 +947,7 @@ mod tests {
|
|||
2,
|
||||
SsaOp::Call {
|
||||
callee: "f".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(1)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -955,6 +974,8 @@ mod tests {
|
|||
],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
@ -988,6 +1009,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let info = analyse_loops(&ssa);
|
||||
|
|
|
|||
|
|
@ -377,6 +377,8 @@ mod tests {
|
|||
value_defs: vec![make_value_def(b0, n0), make_value_def(b1, n1)],
|
||||
cfg_node_map: [(n0, SsaValue(0)), (n1, SsaValue(1))].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let finding = Finding {
|
||||
|
|
@ -447,6 +449,8 @@ mod tests {
|
|||
value_defs: vec![make_value_def(b0, n0), make_value_def(b1, n1)],
|
||||
cfg_node_map: [(n0, SsaValue(0)), (n1, SsaValue(1))].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let finding = Finding {
|
||||
|
|
@ -545,6 +549,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let ctx = SymexContext {
|
||||
|
|
@ -602,6 +608,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let ctx = SymexContext {
|
||||
|
|
|
|||
|
|
@ -350,6 +350,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: [(node, SsaValue(5))].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let witness = state.get_sink_witness(&finding, &ssa);
|
||||
|
|
@ -387,6 +389,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: [(node, SsaValue(5))].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
assert_eq!(state.get_sink_witness(&finding, &ssa), None);
|
||||
|
|
@ -421,6 +425,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
assert_eq!(state.get_sink_witness(&finding, &ssa), None);
|
||||
|
|
@ -459,6 +465,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
state.widen_at_loop_head(BlockId(0), &ssa);
|
||||
|
|
@ -500,6 +508,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
state.widen_at_loop_head(BlockId(0), &ssa);
|
||||
|
|
@ -541,6 +551,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
state.widen_at_loop_head(BlockId(0), &ssa);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
//! etc.) for witness enrichment and heuristic mismatch diagnostics. They do
|
||||
//! NOT affect taint semantics.
|
||||
|
||||
use crate::labels::Cap;
|
||||
use crate::labels::{Cap, bare_method_name};
|
||||
use crate::symbol::Lang;
|
||||
|
||||
use super::value::SymbolicValue;
|
||||
|
|
@ -155,7 +155,7 @@ pub fn classify_string_method(
|
|||
args: &[SymbolicValue],
|
||||
lang: Lang,
|
||||
) -> Option<StringMethodInfo> {
|
||||
let method = callee.rsplit('.').next().unwrap_or(callee);
|
||||
let method = bare_method_name(callee);
|
||||
|
||||
match lang {
|
||||
Lang::JavaScript | Lang::TypeScript => classify_js(method, args),
|
||||
|
|
@ -506,7 +506,7 @@ fn classify_transform_js(callee: &str) -> Option<TransformMethodInfo> {
|
|||
use StringOperandSource::*;
|
||||
use TransformKind::*;
|
||||
|
||||
let method = callee.rsplit('.').next().unwrap_or(callee);
|
||||
let method = bare_method_name(callee);
|
||||
match method {
|
||||
// URL encoding/decoding
|
||||
"encodeURIComponent" | "encodeURI" => Some(TransformMethodInfo {
|
||||
|
|
@ -622,7 +622,7 @@ fn classify_transform_java(callee: &str) -> Option<TransformMethodInfo> {
|
|||
// `URLEncoder.encode`, `Base64.getEncoder.encodeToString`). Match on
|
||||
// the suffix after the last `.` for the leaf method name, but also
|
||||
// examine the dotted callee for receiver-qualified disambiguation.
|
||||
let method = callee.rsplit('.').next().unwrap_or(callee);
|
||||
let method = bare_method_name(callee);
|
||||
|
||||
// URL encoding/decoding — `java.net.URLEncoder.encode` / `URLDecoder.decode`.
|
||||
if callee.ends_with("URLEncoder.encode") {
|
||||
|
|
@ -1039,7 +1039,7 @@ pub fn detect_replace_sanitizer(
|
|||
|
||||
/// Determine whether a replace call is global (replaces all occurrences).
|
||||
fn is_global_replace(callee: &str, lang: Lang) -> bool {
|
||||
let method = callee.rsplit('.').next().unwrap_or(callee);
|
||||
let method = bare_method_name(callee);
|
||||
match lang {
|
||||
// JS: replace() is NOT global; replaceAll() IS global
|
||||
Lang::JavaScript | Lang::TypeScript => method == "replaceAll",
|
||||
|
|
|
|||
|
|
@ -130,6 +130,25 @@ pub fn transfer_inst(
|
|||
state.set(inst.value, SymbolicValue::Unknown);
|
||||
}
|
||||
|
||||
SsaOp::FieldProj { receiver, .. } => {
|
||||
// Symbolic field read: model `obj.field` as an opaque value
|
||||
// tied to the projection's SsaValue, and propagate the
|
||||
// receiver's taint to the result so flat root-set tracking
|
||||
// continues to flow taint through chained accesses.
|
||||
//
|
||||
// Phase 4 deliberately keeps the opaque-Symbol model: without
|
||||
// a field-sensitive heap, a dedicated `Field { receiver, name }`
|
||||
// SymbolicValue variant cannot soundly carry concrete reads
|
||||
// across method boundaries — the witness pipeline already
|
||||
// reconstructs `obj.field` text from `ValueDef.var_name`
|
||||
// (populated by lower.rs to `"base.f1.f2"` for chain projections).
|
||||
// The structured variant is deferred to the field-sensitive
|
||||
// pointer analysis prompt, where heap loads consume `FieldProj`
|
||||
// directly.
|
||||
state.set(inst.value, SymbolicValue::Symbol(inst.value));
|
||||
state.propagate_taint(inst.value, std::slice::from_ref(receiver));
|
||||
}
|
||||
|
||||
SsaOp::Assign(uses) => {
|
||||
let uses_slice: &[_] = uses;
|
||||
match uses_slice.len() {
|
||||
|
|
@ -202,6 +221,7 @@ pub fn transfer_inst(
|
|||
callee,
|
||||
args,
|
||||
receiver,
|
||||
..
|
||||
} => {
|
||||
// Collect symbolic values for arguments
|
||||
let mut arg_syms: Vec<SymbolicValue> = Vec::new();
|
||||
|
|
@ -285,6 +305,11 @@ pub fn transfer_inst(
|
|||
}
|
||||
// Fall through to normal Call
|
||||
}
|
||||
ContainerOp::Writeback { .. } => {
|
||||
// Symex doesn't model writeback yet — taint
|
||||
// engine handles the destination-arg taint
|
||||
// directly. Fall through to normal Call.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -985,6 +1010,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: std::collections::HashMap::new(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1133,6 +1160,7 @@ mod tests {
|
|||
1,
|
||||
SsaOp::Call {
|
||||
callee: "parseInt".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -1159,6 +1187,7 @@ mod tests {
|
|||
2,
|
||||
SsaOp::Call {
|
||||
callee: "send".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(1)]],
|
||||
receiver: Some(SsaValue(0)),
|
||||
},
|
||||
|
|
@ -1255,6 +1284,7 @@ mod tests {
|
|||
4,
|
||||
SsaOp::Call {
|
||||
callee: "toString".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(3)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -1558,7 +1588,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
let ctx = make_summary_ctx(&gs);
|
||||
|
|
@ -1567,6 +1599,7 @@ mod tests {
|
|||
1,
|
||||
SsaOp::Call {
|
||||
callee: "passthrough".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -1623,7 +1656,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
let ctx = make_summary_ctx(&gs);
|
||||
|
|
@ -1632,6 +1667,7 @@ mod tests {
|
|||
2,
|
||||
SsaOp::Call {
|
||||
callee: "ambig".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)], smallvec![SsaValue(1)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -1688,7 +1724,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
let ctx = make_summary_ctx(&gs);
|
||||
|
|
@ -1697,6 +1735,7 @@ mod tests {
|
|||
1,
|
||||
SsaOp::Call {
|
||||
callee: "sanitize".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -1748,7 +1787,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
let ctx = make_summary_ctx(&gs);
|
||||
|
|
@ -1757,6 +1798,7 @@ mod tests {
|
|||
1,
|
||||
SsaOp::Call {
|
||||
callee: "enrich".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -1808,7 +1850,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
let ctx = make_summary_ctx(&gs);
|
||||
|
|
@ -1817,6 +1861,7 @@ mod tests {
|
|||
0,
|
||||
SsaOp::Call {
|
||||
callee: "readEnv".into(),
|
||||
callee_text: None,
|
||||
args: vec![],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -1855,6 +1900,7 @@ mod tests {
|
|||
1,
|
||||
SsaOp::Call {
|
||||
callee: "unknown_func".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -1892,6 +1938,7 @@ mod tests {
|
|||
1,
|
||||
SsaOp::Call {
|
||||
callee: "foo".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -2000,7 +2047,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -2017,6 +2066,7 @@ mod tests {
|
|||
2,
|
||||
SsaOp::Call {
|
||||
callee: "send".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: Some(SsaValue(1)),
|
||||
},
|
||||
|
|
@ -2075,7 +2125,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -2092,6 +2144,7 @@ mod tests {
|
|||
1,
|
||||
SsaOp::Call {
|
||||
callee: "passthrough".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: None,
|
||||
},
|
||||
|
|
@ -2151,7 +2204,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
// Second "send" — in ns B, also with same arity → ambiguous bare-name
|
||||
|
|
@ -2178,7 +2233,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
// Also register the type-qualified name so Attempt 1 can find it
|
||||
|
|
@ -2205,7 +2262,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -2222,6 +2281,7 @@ mod tests {
|
|||
2,
|
||||
SsaOp::Call {
|
||||
callee: "send".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: Some(SsaValue(1)),
|
||||
},
|
||||
|
|
@ -2280,7 +2340,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -2297,6 +2359,7 @@ mod tests {
|
|||
2,
|
||||
SsaOp::Call {
|
||||
callee: "send".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: Some(SsaValue(1)),
|
||||
},
|
||||
|
|
@ -2357,7 +2420,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
insert_java_summary(
|
||||
|
|
@ -2383,7 +2448,9 @@ mod tests {
|
|||
abstract_transfer: vec![],
|
||||
param_return_paths: vec![],
|
||||
points_to: Default::default(),
|
||||
field_points_to: Default::default(),
|
||||
return_path_facts: smallvec::SmallVec::new(),
|
||||
typed_call_receivers: vec![],
|
||||
},
|
||||
);
|
||||
// No "HttpClient.send" summary registered — disambiguation has 0 exact matches
|
||||
|
|
@ -2400,6 +2467,7 @@ mod tests {
|
|||
2,
|
||||
SsaOp::Call {
|
||||
callee: "send".into(),
|
||||
callee_text: None,
|
||||
args: vec![smallvec![SsaValue(0)]],
|
||||
receiver: Some(SsaValue(1)),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1265,4 +1265,39 @@ mod tests {
|
|||
let v = mk_encode(TransformKind::UrlEncode, inner);
|
||||
assert_eq!(v.depth(), 1);
|
||||
}
|
||||
|
||||
/// `mk_binop(Add, ConcreteStr, Concrete(int))` must not silently
|
||||
/// coerce types. The fold path only triggers when *both* operands
|
||||
/// are `Concrete(i64)`; mixed-type operands must build a symbolic
|
||||
/// `BinOp` so downstream witness rendering / type analysis can
|
||||
/// reject the bogus arithmetic.
|
||||
#[test]
|
||||
fn binop_mixed_str_int_does_not_coerce() {
|
||||
let v = mk_binop(
|
||||
Op::Add,
|
||||
SymbolicValue::ConcreteStr("price=".into()),
|
||||
SymbolicValue::Concrete(42),
|
||||
);
|
||||
assert!(
|
||||
matches!(v, SymbolicValue::BinOp(Op::Add, _, _)),
|
||||
"mixed-type Add must produce a symbolic BinOp, not silently fold"
|
||||
);
|
||||
}
|
||||
|
||||
/// `mk_phi` must not fold when operands have differing types
|
||||
/// (e.g. one branch returns a Concrete int, another returns
|
||||
/// ConcreteStr). The result is genuinely uncertain — a Phi node
|
||||
/// must be preserved to expose the type-conflict to downstream
|
||||
/// witness logic, not collapse to one operand.
|
||||
#[test]
|
||||
fn phi_mixed_types_keeps_phi() {
|
||||
let v = mk_phi(vec![
|
||||
(BlockId(0), SymbolicValue::Concrete(7)),
|
||||
(BlockId(1), SymbolicValue::ConcreteStr("x".into())),
|
||||
]);
|
||||
assert!(
|
||||
matches!(v, SymbolicValue::Phi(_)),
|
||||
"phi over mixed types must NOT fold to a single operand"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -772,6 +772,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: [(sink_node, sink_val)].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let finding = Finding {
|
||||
|
|
@ -824,6 +826,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: [(sink_node, SsaValue(5))].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
let cfg = Cfg::new();
|
||||
let finding = Finding {
|
||||
|
|
@ -882,6 +886,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: [(sink_node, sink_val)].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let finding = Finding {
|
||||
|
|
@ -941,6 +947,8 @@ mod tests {
|
|||
value_defs: vec![],
|
||||
cfg_node_map: [(sink_node, sink_val)].into_iter().collect(),
|
||||
exception_edges: vec![],
|
||||
field_interner: crate::ssa::ir::FieldInterner::default(),
|
||||
field_writes: std::collections::HashMap::new(),
|
||||
};
|
||||
|
||||
let finding = Finding {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue