pub struct Ctree { /* private fields */ }Expand description
An owned, interned, Send syntax tree of a decompiled function, from
Database::decompile. The root is always a block statement.
Materialized on the kernel thread and then analyzed anywhere: a read-only snapshot with no in-place mutation. It does not track the live database, so it goes stale if the function is re-decompiled; writing back to IDA is a separate concern, not routed through these handles.
CtreeBuilder builds one directly, with no kernel, for testing matchers against a
known shape:
use idakit::Address;
use idakit::decompiler::ctree::{CtreeBuilder, Local, LocalLocation};
use idakit::types::{TypeShape, TypeValue};
let mut b = CtreeBuilder::new();
let ty = b.intern_type(TypeValue {
shape: TypeShape::Unknown,
size: None,
});
let arg = b.push_local(Local {
name: "a".into(),
ty,
is_arg: true,
is_result: false,
is_byref: false,
width: 8,
comment: None,
location: LocalLocation::Register(0),
});
// `foo(a);`
let a = b.var(ty, arg);
let foo = b.obj(ty, Address::new_const(0x1000), Some("foo"));
let call = b.call_expression(ty, foo, vec![a]);
let stmt = b.expression_statement(call);
let block = b.block(vec![stmt]);
let tree = b.finish(block);
// Whole-tree scans find the call and the local reference without walking the tree shape.
assert_eq!(
tree.calls().collect::<Vec<_>>(),
vec![(call, foo, [a].as_slice())]
);
assert_eq!(tree.vars().map(|(_, v)| v).collect::<Vec<_>>(), vec![arg]);Implementations§
Source§impl Ctree
impl Ctree
Sourcepub fn to_pseudocode(&self) -> String
pub fn to_pseudocode(&self) -> String
Render this function’s body as C-like pseudocode.
Examples found in repository?
34fn main() -> Result<(), Box<dyn std::error::Error>> {
35 let mut args = std::env::args().skip(1);
36 let bin = args
37 .next()
38 .expect("usage: ctree_dump <binary> [name-substring]");
39 let filter = args.next().unwrap_or_default();
40
41 Ida::run(move |ida| -> Result<(), Error> {
42 ida.call(move |idb| -> Result<(), Error> {
43 idb.open(&bin).run_auto(true).call()?;
44
45 let mut matched = 0;
46 let eas: Vec<_> = idb.functions().map(|f| (f.address(), f.name())).collect();
47 for (address, name) in eas {
48 let name = String::from(name);
49 if !filter.is_empty() && !name.contains(&filter) {
50 continue;
51 }
52 let Ok(cf) = idb.decompile(address) else {
53 continue;
54 };
55 let Ok(tree) = cf.ctree() else { continue };
56 matched += 1;
57 println!("\n========== {name} @ {address:#x} ==========");
58 if let Some(pc) = cf.pseudocode() {
59 println!("--- IDA ---\n{pc}");
60 }
61 println!("--- idakit ---\n{}", tree.to_pseudocode());
62 println!("--- structure ---");
63 dump(
64 &tree,
65 idakit::decompiler::ctree::NodeRef::Statement(tree.root()),
66 0,
67 );
68 }
69 println!("\n[ctree_dump] {matched} function(s) matched filter {filter:?}");
70
71 idb.close(false);
72 Ok(())
73 })?
74 })??;
75
76 Ok(())
77}More examples
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 let bin = std::env::args()
31 .nth(1)
32 .expect("usage: probe_ctree_counts <db.i64>");
33
34 Ida::run(move |ida| -> Result<(), Error> {
35 ida.call(move |idb| -> Result<(), Error> {
36 idb.open(&bin).run_auto(false).call()?;
37
38 let eas: Vec<_> = idb
39 .functions()
40 .map(|f| (f.address(), String::from(f.name())))
41 .collect();
42 let mut checked = 0usize;
43 let mut mismatches = 0usize;
44 let mut first_dumped = false;
45 for (address, name) in eas {
46 let Ok(cf) = idb.decompile(address) else {
47 continue;
48 };
49 let Ok(tree) = cf.ctree() else { continue };
50 checked += 1;
51 let (visitor_total, expected) = cf.expr_extraction_expectation();
52 let extracted = tree.expressions().count() as i32;
53 if extracted != expected {
54 mismatches += 1;
55 println!(
56 "MISMATCH {name} @ {address:#x}: extracted={extracted} expected={expected} \
57 visitor={visitor_total} (elided empties {})",
58 visitor_total - expected
59 );
60 if !first_dumped {
61 first_dumped = true;
62 println!("--- idakit render ---\n{}", tree.to_pseudocode());
63 println!("--- structure ---");
64 dump(
65 &tree,
66 idakit::decompiler::ctree::NodeRef::Statement(tree.root()),
67 0,
68 );
69 }
70 }
71 }
72 println!("\n[probe] {checked} decompiled, {mismatches} mismatched");
73 idb.close(false);
74 Ok(())
75 })?
76 })??;
77 Ok(())
78}Source§impl Ctree
impl Ctree
Sourcepub fn root(&self) -> StatementId
pub fn root(&self) -> StatementId
The root statement (a block).
Examples found in repository?
34fn main() -> Result<(), Box<dyn std::error::Error>> {
35 let mut args = std::env::args().skip(1);
36 let bin = args
37 .next()
38 .expect("usage: ctree_dump <binary> [name-substring]");
39 let filter = args.next().unwrap_or_default();
40
41 Ida::run(move |ida| -> Result<(), Error> {
42 ida.call(move |idb| -> Result<(), Error> {
43 idb.open(&bin).run_auto(true).call()?;
44
45 let mut matched = 0;
46 let eas: Vec<_> = idb.functions().map(|f| (f.address(), f.name())).collect();
47 for (address, name) in eas {
48 let name = String::from(name);
49 if !filter.is_empty() && !name.contains(&filter) {
50 continue;
51 }
52 let Ok(cf) = idb.decompile(address) else {
53 continue;
54 };
55 let Ok(tree) = cf.ctree() else { continue };
56 matched += 1;
57 println!("\n========== {name} @ {address:#x} ==========");
58 if let Some(pc) = cf.pseudocode() {
59 println!("--- IDA ---\n{pc}");
60 }
61 println!("--- idakit ---\n{}", tree.to_pseudocode());
62 println!("--- structure ---");
63 dump(
64 &tree,
65 idakit::decompiler::ctree::NodeRef::Statement(tree.root()),
66 0,
67 );
68 }
69 println!("\n[ctree_dump] {matched} function(s) matched filter {filter:?}");
70
71 idb.close(false);
72 Ok(())
73 })?
74 })??;
75
76 Ok(())
77}More examples
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 let bin = std::env::args()
31 .nth(1)
32 .expect("usage: probe_ctree_counts <db.i64>");
33
34 Ida::run(move |ida| -> Result<(), Error> {
35 ida.call(move |idb| -> Result<(), Error> {
36 idb.open(&bin).run_auto(false).call()?;
37
38 let eas: Vec<_> = idb
39 .functions()
40 .map(|f| (f.address(), String::from(f.name())))
41 .collect();
42 let mut checked = 0usize;
43 let mut mismatches = 0usize;
44 let mut first_dumped = false;
45 for (address, name) in eas {
46 let Ok(cf) = idb.decompile(address) else {
47 continue;
48 };
49 let Ok(tree) = cf.ctree() else { continue };
50 checked += 1;
51 let (visitor_total, expected) = cf.expr_extraction_expectation();
52 let extracted = tree.expressions().count() as i32;
53 if extracted != expected {
54 mismatches += 1;
55 println!(
56 "MISMATCH {name} @ {address:#x}: extracted={extracted} expected={expected} \
57 visitor={visitor_total} (elided empties {})",
58 visitor_total - expected
59 );
60 if !first_dumped {
61 first_dumped = true;
62 println!("--- idakit render ---\n{}", tree.to_pseudocode());
63 println!("--- structure ---");
64 dump(
65 &tree,
66 idakit::decompiler::ctree::NodeRef::Statement(tree.root()),
67 0,
68 );
69 }
70 }
71 }
72 println!("\n[probe] {checked} decompiled, {mismatches} mismatched");
73 idb.close(false);
74 Ok(())
75 })?
76 })??;
77 Ok(())
78}Sourcepub fn expression(&self, id: ExpressionId) -> &ExpressionNode
pub fn expression(&self, id: ExpressionId) -> &ExpressionNode
The expression node behind a handle.
Sourcepub fn statement(&self, id: StatementId) -> &StatementNode
pub fn statement(&self, id: StatementId) -> &StatementNode
The statement node behind a handle.
Sourcepub fn kind(&self, id: ExpressionId) -> &ExpressionKind
pub fn kind(&self, id: ExpressionId) -> &ExpressionKind
The expression kind behind a handle: the kind field of expression(id),
the form matchers want when projecting with the ExpressionKind as_* accessors.
Examples found in repository?
55fn callee_name(tree: &Ctree, callee: ExpressionId) -> Option<String> {
56 let kind = tree.kind(callee);
57 if let Some((_, name)) = kind.as_obj() {
58 return name.map(str::to_owned);
59 }
60 kind.as_helper().map(str::to_owned)
61}
62
63/// Builds the callee-name map for a tree.
64///
65/// The tree carries callee names directly, so this is pure: no `Database` access, no
66/// kernel-thread name resolution.
67fn resolve_callees(tree: &Ctree) -> HashMap<ExpressionId, String> {
68 let mut map = HashMap::new();
69 for (id, callee, _) in tree.calls() {
70 if let Some(name) = callee_name(tree, callee) {
71 map.insert(id, name);
72 }
73 }
74 map
75}
76
77/// Whether `e`'s subtree reads a tainted local or calls a source directly.
78///
79/// Flow-insensitive and deliberately crude, doing real work proportional to tree size.
80fn expression_tainted(img: &TaintInput, e: ExpressionId, tainted: &HashSet<u32>) -> bool {
81 img.tree
82 .expression_descendants(NodeRef::Expression(e))
83 .any(|id| match img.tree.kind(id).as_var() {
84 Some(LocalId(i)) => tainted.contains(&i),
85 None => img.callees.get(&id).is_some_and(|n| matches(n, SOURCES)),
86 })
87}
88
89/// The pure phase returns the number of source->sink flows found. No `Database` access,
90/// so this is exactly the work that could move to a worker thread.
91fn analyze(img: &TaintInput) -> usize {
92 // Collect `Var(i) = rhs` definitions once.
93 let defs: Vec<(u32, ExpressionId)> = img
94 .tree
95 .assigns()
96 .filter_map(|(_, _, x, y)| {
97 let LocalId(i) = img.tree.kind(x).as_var()?;
98 Some((i, y))
99 })
100 .collect();
101
102 // Fixpoint: a local is tainted once any of its defining RHS is tainted.
103 let mut tainted: HashSet<u32> = HashSet::new();
104 loop {
105 let before = tainted.len();
106 for &(lv, rhs) in &defs {
107 if !tainted.contains(&lv) && expression_tainted(img, rhs, &tainted) {
108 tainted.insert(lv);
109 }
110 }
111 if tainted.len() == before {
112 break;
113 }
114 }
115
116 // Sinks: a tainted argument to a dangerous call is a flow.
117 img.tree
118 .calls()
119 .filter(|(id, _, args)| {
120 img.callees.get(id).is_some_and(|n| matches(n, SINKS))
121 && args.iter().any(|a| expression_tainted(img, *a, &tainted))
122 })
123 .count()
124}More examples
14fn dump(tree: &Ctree, node: idakit::decompiler::ctree::NodeRef, depth: usize) {
15 use idakit::decompiler::ctree::NodeRef;
16 let pad = " ".repeat(depth);
17 let label = match node {
18 NodeRef::Expression(id) => format!("{:?}", tree.kind(id)),
19 NodeRef::Statement(id) => format!("{:?}", tree.statement_kind(id)),
20 };
21 let line: String = label
22 .lines()
23 .next()
24 .unwrap_or("")
25 .chars()
26 .take(90)
27 .collect();
28 println!("{pad}{line}");
29 for c in tree.children(node) {
30 dump(tree, c, depth + 1);
31 }
32}9fn dump(tree: &Ctree, node: idakit::decompiler::ctree::NodeRef, depth: usize) {
10 use idakit::decompiler::ctree::NodeRef;
11 let pad = " ".repeat(depth);
12 let label = match node {
13 NodeRef::Expression(id) => format!("{:?}", tree.kind(id)),
14 NodeRef::Statement(id) => format!("{:?}", tree.statement_kind(id)),
15 };
16 let line: String = label
17 .lines()
18 .next()
19 .unwrap_or("")
20 .chars()
21 .take(90)
22 .collect();
23 println!("{pad}{line}");
24 for c in tree.children(node) {
25 dump(tree, c, depth + 1);
26 }
27}Sourcepub fn statement_kind(&self, id: StatementId) -> &StatementKind
pub fn statement_kind(&self, id: StatementId) -> &StatementKind
The statement kind behind a handle: the kind field of statement(id).
Examples found in repository?
14fn dump(tree: &Ctree, node: idakit::decompiler::ctree::NodeRef, depth: usize) {
15 use idakit::decompiler::ctree::NodeRef;
16 let pad = " ".repeat(depth);
17 let label = match node {
18 NodeRef::Expression(id) => format!("{:?}", tree.kind(id)),
19 NodeRef::Statement(id) => format!("{:?}", tree.statement_kind(id)),
20 };
21 let line: String = label
22 .lines()
23 .next()
24 .unwrap_or("")
25 .chars()
26 .take(90)
27 .collect();
28 println!("{pad}{line}");
29 for c in tree.children(node) {
30 dump(tree, c, depth + 1);
31 }
32}More examples
9fn dump(tree: &Ctree, node: idakit::decompiler::ctree::NodeRef, depth: usize) {
10 use idakit::decompiler::ctree::NodeRef;
11 let pad = " ".repeat(depth);
12 let label = match node {
13 NodeRef::Expression(id) => format!("{:?}", tree.kind(id)),
14 NodeRef::Statement(id) => format!("{:?}", tree.statement_kind(id)),
15 };
16 let line: String = label
17 .lines()
18 .next()
19 .unwrap_or("")
20 .chars()
21 .take(90)
22 .collect();
23 println!("{pad}{line}");
24 for c in tree.children(node) {
25 dump(tree, c, depth + 1);
26 }
27}Sourcepub fn type_of(&self, id: TypeId) -> &TypeValue
pub fn type_of(&self, id: TypeId) -> &TypeValue
The type behind a handle (e.g. an ExpressionNode::ty).
Sourcepub fn local(&self, id: LocalId) -> &Local
pub fn local(&self, id: LocalId) -> &Local
The local variable a ExpressionKind::Var refers to.
Sourcepub fn locals(&self) -> impl ExactSizeIterator<Item = &Local>
pub fn locals(&self) -> impl ExactSizeIterator<Item = &Local>
Every local variable of the function, in local-index order.
Examples found in repository?
8fn main() -> Result<(), Box<dyn std::error::Error>> {
9 use idakit::decompiler::ctree::LocalLocation as L;
10 let mut args = std::env::args().skip(1);
11 let bin = args
12 .next()
13 .expect("usage: probe_argloc <db.i64> [max-funcs]");
14 let max: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(2000);
15
16 Ida::run(move |ida| -> Result<(), Error> {
17 ida.call(move |idb| -> Result<(), Error> {
18 idb.open(&bin).run_auto(false).call()?;
19
20 let mut n = [0usize; 8];
21 let mut decompiled = 0usize;
22 let mut examples: Vec<String> = Vec::new();
23 let eas: Vec<_> = idb.functions().take(max).map(|f| f.address()).collect();
24 for ea in eas {
25 let Ok(cf) = idb.decompile(ea) else { continue };
26 let Ok(tree) = cf.ctree() else { continue };
27 decompiled += 1;
28 for lv in tree.locals() {
29 let i = match &lv.location {
30 L::Register(_) => 0,
31 L::RegisterPair { .. } => 1,
32 L::Stack(_) => 2,
33 L::RegisterRelative { .. } => 3,
34 L::Static(_) => 4,
35 L::Scattered(_) => 5,
36 L::Custom => 6,
37 L::Unallocated => 7,
38 };
39 n[i] += 1;
40 if matches!(lv.location, L::Scattered(_) | L::RegisterPair { .. } | L::RegisterRelative { .. })
41 && examples.len() < 12
42 {
43 examples.push(format!(" {} {:?} = {:?}", lv.name, lv.width, lv.location));
44 }
45 }
46 }
47 println!(
48 "{decompiled} fns | reg={} pair={} stack={} rrel={} static={} scatter={} custom={} none={}",
49 n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]
50 );
51 if !examples.is_empty() {
52 println!("rich-variant examples:");
53 for e in &examples {
54 println!("{e}");
55 }
56 }
57 idb.close(false);
58 Ok(())
59 })?
60 })??;
61 Ok(())
62}Sourcepub fn this_local(&self) -> Option<LocalId>
pub fn this_local(&self) -> Option<LocalId>
The function’s first argument local: the implicit this in a member function, or
simply the first parameter otherwise. None if the function takes no arguments.
A pure structural accessor: it reads the local table’s argument flags and makes no assumption about calling convention.
Sourcepub fn expressions(
&self,
) -> impl ExactSizeIterator<Item = (ExpressionId, &ExpressionNode)>
pub fn expressions( &self, ) -> impl ExactSizeIterator<Item = (ExpressionId, &ExpressionNode)>
Every expression node, flat, in allocation order.
Useful for whole-tree scans, like “find all calls”, that don’t need the tree shape.
Examples found in repository?
139fn run(idb: &mut Database, db: &str) -> Result<(), Error> {
140 idb.open(db).call()?;
141
142 let limit = std::env::var("TAINT_LIMIT")
143 .ok()
144 .and_then(|s| s.parse::<usize>().ok())
145 .unwrap_or(usize::MAX);
146
147 let eas: Vec<Address> = idb.functions().map(|f| f.address()).take(limit).collect();
148 println!("[taint] sweeping {} functions", eas.len());
149
150 let mut t = Totals::default();
151 let wall = Instant::now();
152
153 for (i, &address) in eas.iter().enumerate() {
154 let started = Instant::now();
155 let Ok(cf) = idb.decompile(address) else {
156 t.decompile_failed += 1;
157 continue;
158 };
159 t.decompile += started.elapsed();
160
161 let started = Instant::now();
162 let Ok(tree) = cf.ctree() else {
163 t.extract_failed += 1;
164 continue;
165 };
166 t.extract += started.elapsed();
167
168 let started = Instant::now();
169 let callees = resolve_callees(&tree);
170 t.resolve += started.elapsed();
171
172 t.nodes += (tree.expressions().count() + tree.statements().count()) as u64;
173 let img = TaintInput { tree, callees };
174
175 let started = Instant::now();
176 t.flows += analyze(&img);
177 t.analyze += started.elapsed();
178
179 t.funcs += 1;
180 if (i + 1) % 5000 == 0 {
181 println!("[taint] {} / {} ...", i + 1, eas.len());
182 }
183 }
184
185 report(&t, wall.elapsed());
186 idb.close(false);
187 Ok(())
188}More examples
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 let bin = std::env::args()
31 .nth(1)
32 .expect("usage: probe_ctree_counts <db.i64>");
33
34 Ida::run(move |ida| -> Result<(), Error> {
35 ida.call(move |idb| -> Result<(), Error> {
36 idb.open(&bin).run_auto(false).call()?;
37
38 let eas: Vec<_> = idb
39 .functions()
40 .map(|f| (f.address(), String::from(f.name())))
41 .collect();
42 let mut checked = 0usize;
43 let mut mismatches = 0usize;
44 let mut first_dumped = false;
45 for (address, name) in eas {
46 let Ok(cf) = idb.decompile(address) else {
47 continue;
48 };
49 let Ok(tree) = cf.ctree() else { continue };
50 checked += 1;
51 let (visitor_total, expected) = cf.expr_extraction_expectation();
52 let extracted = tree.expressions().count() as i32;
53 if extracted != expected {
54 mismatches += 1;
55 println!(
56 "MISMATCH {name} @ {address:#x}: extracted={extracted} expected={expected} \
57 visitor={visitor_total} (elided empties {})",
58 visitor_total - expected
59 );
60 if !first_dumped {
61 first_dumped = true;
62 println!("--- idakit render ---\n{}", tree.to_pseudocode());
63 println!("--- structure ---");
64 dump(
65 &tree,
66 idakit::decompiler::ctree::NodeRef::Statement(tree.root()),
67 0,
68 );
69 }
70 }
71 }
72 println!("\n[probe] {checked} decompiled, {mismatches} mismatched");
73 idb.close(false);
74 Ok(())
75 })?
76 })??;
77 Ok(())
78}Sourcepub fn statements(
&self,
) -> impl ExactSizeIterator<Item = (StatementId, &StatementNode)>
pub fn statements( &self, ) -> impl ExactSizeIterator<Item = (StatementId, &StatementNode)>
Every statement node, flat, in allocation order.
Examples found in repository?
139fn run(idb: &mut Database, db: &str) -> Result<(), Error> {
140 idb.open(db).call()?;
141
142 let limit = std::env::var("TAINT_LIMIT")
143 .ok()
144 .and_then(|s| s.parse::<usize>().ok())
145 .unwrap_or(usize::MAX);
146
147 let eas: Vec<Address> = idb.functions().map(|f| f.address()).take(limit).collect();
148 println!("[taint] sweeping {} functions", eas.len());
149
150 let mut t = Totals::default();
151 let wall = Instant::now();
152
153 for (i, &address) in eas.iter().enumerate() {
154 let started = Instant::now();
155 let Ok(cf) = idb.decompile(address) else {
156 t.decompile_failed += 1;
157 continue;
158 };
159 t.decompile += started.elapsed();
160
161 let started = Instant::now();
162 let Ok(tree) = cf.ctree() else {
163 t.extract_failed += 1;
164 continue;
165 };
166 t.extract += started.elapsed();
167
168 let started = Instant::now();
169 let callees = resolve_callees(&tree);
170 t.resolve += started.elapsed();
171
172 t.nodes += (tree.expressions().count() + tree.statements().count()) as u64;
173 let img = TaintInput { tree, callees };
174
175 let started = Instant::now();
176 t.flows += analyze(&img);
177 t.analyze += started.elapsed();
178
179 t.funcs += 1;
180 if (i + 1) % 5000 == 0 {
181 println!("[taint] {} / {} ...", i + 1, eas.len());
182 }
183 }
184
185 report(&t, wall.elapsed());
186 idb.close(false);
187 Ok(())
188}Sourcepub fn calls(
&self,
) -> impl Iterator<Item = (ExpressionId, ExpressionId, &[ExpressionId])>
pub fn calls( &self, ) -> impl Iterator<Item = (ExpressionId, ExpressionId, &[ExpressionId])>
Every call in the tree as (node, callee, args).
The whole-tree scan behind “find every call”, without re-spelling the
as_call filter.
Examples found in repository?
67fn resolve_callees(tree: &Ctree) -> HashMap<ExpressionId, String> {
68 let mut map = HashMap::new();
69 for (id, callee, _) in tree.calls() {
70 if let Some(name) = callee_name(tree, callee) {
71 map.insert(id, name);
72 }
73 }
74 map
75}
76
77/// Whether `e`'s subtree reads a tainted local or calls a source directly.
78///
79/// Flow-insensitive and deliberately crude, doing real work proportional to tree size.
80fn expression_tainted(img: &TaintInput, e: ExpressionId, tainted: &HashSet<u32>) -> bool {
81 img.tree
82 .expression_descendants(NodeRef::Expression(e))
83 .any(|id| match img.tree.kind(id).as_var() {
84 Some(LocalId(i)) => tainted.contains(&i),
85 None => img.callees.get(&id).is_some_and(|n| matches(n, SOURCES)),
86 })
87}
88
89/// The pure phase returns the number of source->sink flows found. No `Database` access,
90/// so this is exactly the work that could move to a worker thread.
91fn analyze(img: &TaintInput) -> usize {
92 // Collect `Var(i) = rhs` definitions once.
93 let defs: Vec<(u32, ExpressionId)> = img
94 .tree
95 .assigns()
96 .filter_map(|(_, _, x, y)| {
97 let LocalId(i) = img.tree.kind(x).as_var()?;
98 Some((i, y))
99 })
100 .collect();
101
102 // Fixpoint: a local is tainted once any of its defining RHS is tainted.
103 let mut tainted: HashSet<u32> = HashSet::new();
104 loop {
105 let before = tainted.len();
106 for &(lv, rhs) in &defs {
107 if !tainted.contains(&lv) && expression_tainted(img, rhs, &tainted) {
108 tainted.insert(lv);
109 }
110 }
111 if tainted.len() == before {
112 break;
113 }
114 }
115
116 // Sinks: a tainted argument to a dangerous call is a flow.
117 img.tree
118 .calls()
119 .filter(|(id, _, args)| {
120 img.callees.get(id).is_some_and(|n| matches(n, SINKS))
121 && args.iter().any(|a| expression_tainted(img, *a, &tainted))
122 })
123 .count()
124}Sourcepub fn assigns(
&self,
) -> impl Iterator<Item = (ExpressionId, AssignmentOp, ExpressionId, ExpressionId)>
pub fn assigns( &self, ) -> impl Iterator<Item = (ExpressionId, AssignmentOp, ExpressionId, ExpressionId)>
Every assignment in the tree as (node, op, lhs, rhs).
Examples found in repository?
91fn analyze(img: &TaintInput) -> usize {
92 // Collect `Var(i) = rhs` definitions once.
93 let defs: Vec<(u32, ExpressionId)> = img
94 .tree
95 .assigns()
96 .filter_map(|(_, _, x, y)| {
97 let LocalId(i) = img.tree.kind(x).as_var()?;
98 Some((i, y))
99 })
100 .collect();
101
102 // Fixpoint: a local is tainted once any of its defining RHS is tainted.
103 let mut tainted: HashSet<u32> = HashSet::new();
104 loop {
105 let before = tainted.len();
106 for &(lv, rhs) in &defs {
107 if !tainted.contains(&lv) && expression_tainted(img, rhs, &tainted) {
108 tainted.insert(lv);
109 }
110 }
111 if tainted.len() == before {
112 break;
113 }
114 }
115
116 // Sinks: a tainted argument to a dangerous call is a flow.
117 img.tree
118 .calls()
119 .filter(|(id, _, args)| {
120 img.callees.get(id).is_some_and(|n| matches(n, SINKS))
121 && args.iter().any(|a| expression_tainted(img, *a, &tainted))
122 })
123 .count()
124}Sourcepub fn vars(&self) -> impl Iterator<Item = (ExpressionId, LocalId)>
pub fn vars(&self) -> impl Iterator<Item = (ExpressionId, LocalId)>
Every local-variable reference in the tree as (node, local).
Sourcepub fn types(&self) -> impl ExactSizeIterator<Item = (TypeId, &TypeValue)>
pub fn types(&self) -> impl ExactSizeIterator<Item = (TypeId, &TypeValue)>
Every interned type, flat.
Sourcepub fn expression_at(&self, address: Address) -> Option<ExpressionId>
pub fn expression_at(&self, address: Address) -> Option<ExpressionId>
The first expression node whose source address is address, or None if none is.
Several nodes can share one address; this returns the first in allocation order and
items_at yields them all.
Sourcepub fn statement_at(&self, address: Address) -> Option<StatementId>
pub fn statement_at(&self, address: Address) -> Option<StatementId>
The first statement node whose source address is address, or None.
Sourcepub fn items_at(&self, address: Address) -> impl Iterator<Item = NodeRef> + '_
pub fn items_at(&self, address: Address) -> impl Iterator<Item = NodeRef> + '_
Every node whose source address is address, in allocation order with expressions
before statements.
The flat, address-keyed counterpart to the structural descendants
walk, answering “what did the decompiler place at this instruction?” without
navigating the tree.
Sourcepub fn parent(&self, node: NodeRef) -> Option<NodeRef>
pub fn parent(&self, node: NodeRef) -> Option<NodeRef>
This node’s parent, or None for the root.
Sourcepub fn children(&self, node: NodeRef) -> Vec<NodeRef>
pub fn children(&self, node: NodeRef) -> Vec<NodeRef>
This node’s direct children, in source order.
Examples found in repository?
14fn dump(tree: &Ctree, node: idakit::decompiler::ctree::NodeRef, depth: usize) {
15 use idakit::decompiler::ctree::NodeRef;
16 let pad = " ".repeat(depth);
17 let label = match node {
18 NodeRef::Expression(id) => format!("{:?}", tree.kind(id)),
19 NodeRef::Statement(id) => format!("{:?}", tree.statement_kind(id)),
20 };
21 let line: String = label
22 .lines()
23 .next()
24 .unwrap_or("")
25 .chars()
26 .take(90)
27 .collect();
28 println!("{pad}{line}");
29 for c in tree.children(node) {
30 dump(tree, c, depth + 1);
31 }
32}More examples
9fn dump(tree: &Ctree, node: idakit::decompiler::ctree::NodeRef, depth: usize) {
10 use idakit::decompiler::ctree::NodeRef;
11 let pad = " ".repeat(depth);
12 let label = match node {
13 NodeRef::Expression(id) => format!("{:?}", tree.kind(id)),
14 NodeRef::Statement(id) => format!("{:?}", tree.statement_kind(id)),
15 };
16 let line: String = label
17 .lines()
18 .next()
19 .unwrap_or("")
20 .chars()
21 .take(90)
22 .collect();
23 println!("{pad}{line}");
24 for c in tree.children(node) {
25 dump(tree, c, depth + 1);
26 }
27}Sourcepub fn children_for_each(&self, node: NodeRef, f: impl FnMut(NodeRef))
pub fn children_for_each(&self, node: NodeRef, f: impl FnMut(NodeRef))
Visit each direct child without allocating. The push-based counterpart to
children, which buffers into a Vec.
Sourcepub fn descendants(&self, node: NodeRef) -> Descendants<'_> ⓘ
pub fn descendants(&self, node: NodeRef) -> Descendants<'_> ⓘ
A pre-order walk of node and all its descendants (the node itself first).
Sourcepub fn expression_descendants(
&self,
node: NodeRef,
) -> impl Iterator<Item = ExpressionId> + '_
pub fn expression_descendants( &self, node: NodeRef, ) -> impl Iterator<Item = ExpressionId> + '_
Like descendants but yielding only the expression handles,
skipping statements.