mirsa-domains 0.2.0

Abstract interpretation domains for mirsa
use rustc_middle::mir::{Body, Terminator, TerminatorKind};
use rustc_middle::ty::TyCtxt;

use crate::contracts::finding::{Finding, Level};
use crate::internval::transfer::operand_known_len;
use crate::internval::{Internval, InternvalState};

use super::shared::{check_le, eval_call_arg};

fn check_side(count: Internval, available: Option<Internval>) -> Level {
    let Some(available) = available else {
        return Level::Safe;
    };
    check_le(count, available)
}

pub(crate) fn check<'tcx>(
    tcx: TyCtxt<'tcx>,
    body: &Body<'tcx>,
    term: &Terminator<'tcx>,
    state: &InternvalState<'tcx>,
) -> Option<Finding> {
    let TerminatorKind::Call { args, .. } = &term.kind else {
        return None;
    };
    if args.len() < 3 {
        return None;
    }

    let src_len = operand_known_len(state, &args[0].node);
    let dst_len = operand_known_len(state, &args[1].node);
    let count = eval_call_arg(tcx, body, state, &args[2].node);
    let src_level = check_side(count, src_len);
    let dst_level = check_side(count, dst_len);
    let level = src_level.combine(dst_level);
    let src_note = src_len
        .map(|len| format!("src_len = {len}"))
        .unwrap_or_else(|| "src_len = unknown".to_string());
    let dst_note = dst_len
        .map(|len| format!("dst_len = {len}"))
        .unwrap_or_else(|| "dst_len = unknown".to_string());
    Finding::for_level(
        level,
        term.source_info.span,
        "internval/definite-oob",
        "internval/possible-oob",
        "calling `ptr::copy_nonoverlapping` with a definitely out-of-bounds range",
        "calling `ptr::copy_nonoverlapping` with a range that may exceed the source or destination object",
        vec![format!("count = {count}"), src_note, dst_note],
    )
}