Skip to content

Analyze a Simple Rust Program

jingling edited this page May 31, 2024 · 2 revisions

An Example

We use a simple Rust program shape.rs to explain the points-to analysis results of RUPTA:

trait Shape {
    fn area(&self) {}
}

struct Circle<'a> { r: &'a f32 }
struct Square<'a> { s: &'a f32 } 

impl<'a> Shape for Circle<'a> {
    fn area(&self) { let _ = self.r * self.r * 3.14; }
}

impl<'a> Shape for Square<'a> {
    fn area(&self) { let _ = self.s * self.s; }
}

fn shape_area_static<T: Shape>(s: &T) {
    s.area();
}

fn shape_area_dynamic(s: &dyn Shape) {
    s.area();
}

fn main() {
    let x = 3.0;
    let y = 2.0;
    let c = Circle { r: &x };
    let r = Square { s: &y };
    
    shape_area_static(&c);
    shape_area_static(&r);

    let s: &dyn Shape = &c;
    shape_area_dynamic(s);
}

Run RUPTA

Use pta to analyze a single Rust file. By default, 1-callsite-sensitive pointer analysis is performed.

pta shape.rs

Dump the MIR

pta shape.rs --dump-mir mir.txt

RUPTA plugs into the nightly Rust compiler rustc. The output MIR may vary depending on the version of rustc. The MIR below is based on rustc 1.77.0-nightly (2024-02-01).

Generic types persist in Rust MIR, for example, T in the function shape_area_static. RUPTA monomorphizes generic code during analysis, treating a generic function with different specialized types as distinct functions and analyzing them separately. As shown below, shape_area_static is analyzed twice in this example, corresponding to FuncId(1) and FuncId(2).

[FuncId(0) - "shape::main"]
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn main() -> () {
    let mut _0: ();
    let _1: f32;
    let _4: &f32;
    let _6: &f32;
    let _7: ();
    let _8: &Circle<'_>;
    let _9: ();
    let _10: &Square<'_>;
    let _12: &Circle<'_>;
    let _13: ();
    scope 1 {
        debug x => const 3f32;
        let _2: f32;
        scope 2 {
            debug y => const 2f32;
            let _3: Circle<'_>;
            scope 3 {
                debug c => _3;
                let _5: Square<'_>;
                scope 4 {
                    debug r => _5;
                    let _11: &dyn Shape;
                    scope 5 {
                        debug s => _11;
                    }
                }
            }
        }
    }

    bb0: {
        _1 = const 3f32;
        _2 = const 2f32;
        _4 = &_1;
        _3 = Circle::<'_> { r: _4 };
        _6 = &_2;
        _5 = Square::<'_> { s: _6 };
        _8 = &_3;
        _7 = shape_area_static::<Circle<'_>>(_8) -> [return: bb1, unwind continue];
    }

    bb1: {
        _10 = &_5;
        _9 = shape_area_static::<Square<'_>>(_10) -> [return: bb2, unwind continue];
    }

    bb2: {
        _12 = &_3;
        _11 = _12 as &dyn Shape (PointerCoercion(Unsize));
        _13 = shape_area_dynamic(_11) -> [return: bb3, unwind continue];
    }

    bb3: {
        return;
    }
}

[FuncId(1) - "shape::shape_area_static<Circle<ReErased>>"]
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn shape_area_static(_1: &T) -> () {
    debug s => _1;
    let mut _0: ();
    let _2: ();

    bb0: {
        _2 = <T as Shape>::area(_1) -> [return: bb1, unwind continue];
    }

    bb1: {
        return;
    }
}

[FuncId(2) - "shape::shape_area_static<Square<ReErased>>"]
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn shape_area_static(_1: &T) -> () {
    debug s => _1;
    let mut _0: ();
    let _2: ();

    bb0: {
        _2 = <T as Shape>::area(_1) -> [return: bb1, unwind continue];
    }

    bb1: {
        return;
    }
}

[FuncId(3) - "shape::shape_area_dynamic"]
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn shape_area_dynamic(_1: &dyn Shape) -> () {
    debug s => _1;
    let mut _0: ();
    let _2: ();

    bb0: {
        _2 = <dyn Shape as Shape>::area(_1) -> [return: bb1, unwind continue];
    }

    bb1: {
        return;
    }
}

[FuncId(4) - "shape::{impl#0}::area<>"]
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn <impl at shape.rs:8:1: 8:30>::area(_1: &Circle<'_>) -> () {
    debug self => _1;
    let mut _0: ();
    let mut _2: f32;
    let mut _3: &f32;
    let mut _4: &f32;
    scope 1 {
    }

    bb0: {
        _3 = ((*_1).0: &f32);
        _4 = ((*_1).0: &f32);
        _2 = <&f32 as std::ops::Mul>::mul(move _3, move _4) -> [return: bb1, unwind continue];
    }

    bb1: {
        return;
    }
}

[FuncId(5) - "shape::{impl#1}::area<>"]
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn <impl at shape.rs:12:1: 12:30>::area(_1: &Square<'_>) -> () {
    debug self => _1;
    let mut _0: ();
    let mut _2: f32;
    let mut _3: &f32;
    let mut _4: &f32;
    scope 1 {
    }

    bb0: {
        _3 = ((*_1).0: &f32);
        _4 = ((*_1).0: &f32);
        _2 = <&f32 as std::ops::Mul>::mul(move _3, move _4) -> [return: bb1, unwind continue];
    }

    bb1: {
        return;
    }
}

[FuncId(6) - "core::ops::arith::{impl#164}::mul<>"]
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn <&f32 as std::ops::Mul<&f32>>::mul(_1: &f32, _2: &f32) -> f32 {
    debug self => _1;
    debug other => _2;
    let mut _0: f32;
    let mut _3: f32;
    let mut _4: f32;
    scope 1 (inlined #[track_caller] <f32 as std::ops::Mul>::mul) {
        debug self => _3;
        debug other => _4;
    }

    bb0: {
        StorageLive(_3);
        _3 = (*_1);
        StorageLive(_4);
        _4 = (*_2);
        _0 = Mul(_3, _4);
        StorageDead(_4);
        StorageDead(_3);
        return;
    }
}

Dump the Call Graph

pta shape.rs --dump-call-graph call_graph.dot

The content of call_graph.dot is given below:

digraph {
    0 [ label = "shape::main" ]
    1 [ label = "shape::shape_area_dynamic" ]
    2 [ label = "shape::{impl#0}::area<>" ]
    3 [ label = "core::ops::arith::{impl#164}::mul<>" ]
    4 [ label = "shape::shape_area_static<Square<ReErased>>" ]
    5 [ label = "shape::{impl#1}::area<>" ]
    6 [ label = "shape::shape_area_static<Circle<ReErased>>" ]
    0 -> 1 [ label = "bb2[2]" ]
    2 -> 3 [ label = "bb0[2]" ]
    1 -> 2 [ label = "bb0[0]" ]
    4 -> 5 [ label = "bb0[0]" ]
    6 -> 2 [ label = "bb0[0]" ]
    0 -> 6 [ label = "bb0[7]" ]
    0 -> 4 [ label = "bb1[1]" ]
    5 -> 3 [ label = "bb0[2]" ]
}

The callgraph looks like this:

shape_call_graph

Dump the Points-to Relations

pta shape.rs -dump-pts pts.txt

The content of pts.txt is given below:

FuncId(0) - "shape::main"
	FuncId(0)::local_6 (1) ==> { FuncId(0)::local_2 }
	FuncId(0)::local_10 (1) ==> { FuncId(0)::local_5 }
	FuncId(0)::local_4 (1) ==> { FuncId(0)::local_1 }
	FuncId(0)::local_3.0 (1) ==> { FuncId(0)::local_1 }
	FuncId(0)::local_5.0 (1) ==> { FuncId(0)::local_2 }
	FuncId(0)::local_11 (1) ==> { FuncId(0)::local_3 }
	FuncId(0)::local_8 (1) ==> { FuncId(0)::local_3 }
	FuncId(0)::local_12 (1) ==> { FuncId(0)::local_3 }
FuncId(1) - "shape::shape_area_static<Circle<ReErased>>"
	FuncId(1)::param_1 (1) ==> { FuncId(0)::local_3 }
FuncId(2) - "shape::shape_area_static<Square<ReErased>>"
	FuncId(2)::param_1 (1) ==> { FuncId(0)::local_5 }
FuncId(3) - "shape::shape_area_dynamic"
	FuncId(3)::param_1 (1) ==> { FuncId(0)::local_3 }
FuncId(4) - "shape::{impl#0}::area<>"
	FuncId(4)::param_1 (1) ==> { FuncId(0)::local_3 }
	FuncId(4)::local_4 (1) ==> { FuncId(0)::local_1 }
	FuncId(4)::local_3 (1) ==> { FuncId(0)::local_1 }
FuncId(5) - "shape::{impl#1}::area<>"
	FuncId(5)::local_4 (1) ==> { FuncId(0)::local_2 }
	FuncId(5)::local_3 (1) ==> { FuncId(0)::local_2 }
	FuncId(5)::param_1 (1) ==> { FuncId(0)::local_5 }
FuncId(6) - "core::ops::arith::{impl#164}::mul<>"
	FuncId(6)::param_1 (2) ==> { FuncId(0)::local_2 FuncId(0)::local_1 }
	FuncId(6)::param_2 (2) ==> { FuncId(0)::local_2 FuncId(0)::local_1 }

Clone this wiki locally