Skip to main content

Ctree

Struct Ctree 

Source
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

Source

pub fn to_pseudocode(&self) -> String

Render this function’s body as C-like pseudocode.

Examples found in repository?
examples/ctree_dump.rs (line 61)
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
Hide additional examples
examples/probe_ctree_counts.rs (line 62)
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

Source

pub fn root(&self) -> StatementId

The root statement (a block).

Examples found in repository?
examples/ctree_dump.rs (line 65)
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
Hide additional examples
examples/probe_ctree_counts.rs (line 66)
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

pub fn expression(&self, id: ExpressionId) -> &ExpressionNode

The expression node behind a handle.

Source

pub fn statement(&self, id: StatementId) -> &StatementNode

The statement node behind a handle.

Source

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?
examples/taint.rs (line 56)
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
Hide additional examples
examples/ctree_dump.rs (line 18)
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}
examples/probe_ctree_counts.rs (line 13)
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}
Source

pub fn statement_kind(&self, id: StatementId) -> &StatementKind

The statement kind behind a handle: the kind field of statement(id).

Examples found in repository?
examples/ctree_dump.rs (line 19)
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
Hide additional examples
examples/probe_ctree_counts.rs (line 14)
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}
Source

pub fn type_of(&self, id: TypeId) -> &TypeValue

The type behind a handle (e.g. an ExpressionNode::ty).

Source

pub fn local(&self, id: LocalId) -> &Local

The local variable a ExpressionKind::Var refers to.

Source

pub fn locals(&self) -> impl ExactSizeIterator<Item = &Local>

Every local variable of the function, in local-index order.

Examples found in repository?
examples/probe_argloc.rs (line 28)
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}
Source

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.

Source

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?
examples/taint.rs (line 172)
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
Hide additional examples
examples/probe_ctree_counts.rs (line 52)
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

pub fn statements( &self, ) -> impl ExactSizeIterator<Item = (StatementId, &StatementNode)>

Every statement node, flat, in allocation order.

Examples found in repository?
examples/taint.rs (line 172)
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}
Source

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?
examples/taint.rs (line 69)
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}
Source

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?
examples/taint.rs (line 95)
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}
Source

pub fn vars(&self) -> impl Iterator<Item = (ExpressionId, LocalId)>

Every local-variable reference in the tree as (node, local).

Source

pub fn types(&self) -> impl ExactSizeIterator<Item = (TypeId, &TypeValue)>

Every interned type, flat.

Source

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.

Source

pub fn statement_at(&self, address: Address) -> Option<StatementId>

The first statement node whose source address is address, or None.

Source

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.

Source

pub fn parent(&self, node: NodeRef) -> Option<NodeRef>

This node’s parent, or None for the root.

Source

pub fn children(&self, node: NodeRef) -> Vec<NodeRef>

This node’s direct children, in source order.

Examples found in repository?
examples/ctree_dump.rs (line 29)
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
Hide additional examples
examples/probe_ctree_counts.rs (line 24)
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}
Source

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.

Source

pub fn descendants(&self, node: NodeRef) -> Descendants<'_>

A pre-order walk of node and all its descendants (the node itself first).

Source

pub fn expression_descendants( &self, node: NodeRef, ) -> impl Iterator<Item = ExpressionId> + '_

Like descendants but yielding only the expression handles, skipping statements.

Examples found in repository?
examples/taint.rs (line 82)
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}

Trait Implementations§

Source§

impl Clone for Ctree

Source§

fn clone(&self) -> Ctree

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Ctree

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Ctree

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Display for Ctree

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Ctree

Source§

fn eq(&self, other: &Ctree) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for Ctree

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Ctree

Auto Trait Implementations§

§

impl Freeze for Ctree

§

impl RefUnwindSafe for Ctree

§

impl Send for Ctree

§

impl Sync for Ctree

§

impl Unpin for Ctree

§

impl UnsafeUnpin for Ctree

§

impl UnwindSafe for Ctree

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more