bugstalker 0.4.3

BugStalker is a modern and lightweight debugger for rust applications.
Documentation
use dap::types::{Variable, VariablePresentationHint};
use itertools::Itertools;

use crate::{
    debugger::{
        address::RelocatedAddress,
        variable::{
            render::{RenderValue, ValueLayout},
            value::Value,
        },
    },
    ui::dap::FrameInfo,
};

#[derive(Hash, PartialEq, Eq, Clone, Copy)]
pub struct Key {
    inner: usize,
}

impl Key {
    pub fn from_var_ref(var_ref: i64) -> Self {
        Key {
            inner: var_ref as usize,
        }
    }

    pub fn as_var_ref(&self) -> i64 {
        self.inner as i64
    }

    fn new_unstable(idx: usize) -> Self {
        assert!(idx < u32::MAX as usize);
        Self {
            inner: (idx | (1usize << 0x21)),
        }
    }

    #[allow(unused)]
    fn new_stable(idx: usize) -> Self {
        assert!(idx < u32::MAX as usize);
        Self { inner: idx }
    }

    pub fn is_stable(&self) -> bool {
        (self.inner & (1usize << 0x21)) == 0
    }

    pub fn idx(&self) -> usize {
        self.inner & (u32::MAX as usize)
    }
}

#[derive(Clone, PartialEq)]
pub enum Selector {
    Dqe(String),
    All,
}

#[derive(Clone)]
pub struct Var {
    pub scope: VarScope,
    pub frame: FrameInfo,
    pub selector: Selector,
}

#[derive(Default)]
pub struct VarRegistry {
    stable: Vec<Var>,
    unstable: Vec<Var>,
}

impl VarRegistry {
    pub fn insert_unstable(&mut self, var: Var) -> Key {
        self.unstable.push(var);
        Key::new_unstable(self.unstable.len() - 1)
    }

    #[allow(unused)]
    pub fn insert_stable(&mut self, var: Var) -> Key {
        self.stable.push(var);
        Key::new_stable(self.stable.len() - 1)
    }

    pub fn get_var(&self, key: Key) -> &Var {
        if key.is_stable() {
            &self.stable[key.idx()]
        } else {
            &self.unstable[key.idx()]
        }
    }

    pub fn release_unstable(&mut self) {
        self.unstable.clear();
    }
}

#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum VarScope {
    None = 0,
    Args = 1,
    Vars = 2,
}

impl TryFrom<u8> for VarScope {
    type Error = ();

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(VarScope::None),
            1 => Ok(VarScope::Args),
            2 => Ok(VarScope::Vars),
            _ => Err(()),
        }
    }
}

pub fn expand_and_collect(
    registry: &mut VarRegistry,
    scope: VarScope,
    frame_info: FrameInfo,
    path: &str,
    value: &Value,
) -> Vec<Variable> {
    let Some(layout) = value.value_layout() else {
        return vec![];
    };

    match layout {
        ValueLayout::PreRendered(_) => {
            into_dap_var_repr(registry, scope, frame_info, path, "", value)
                .map(|v| vec![v])
                .unwrap_or_default()
        }
        ValueLayout::Referential(_) => {
            into_dap_var_repr(registry, scope, frame_info, path, "", value)
                .map(|v| vec![v])
                .unwrap_or_default()
        }
        ValueLayout::Wrapped(inner) => expand_and_collect(registry, scope, frame_info, path, inner),
        ValueLayout::Structure(members) => members
            .iter()
            .filter_map(|member| {
                let name = member.field_name.as_ref()?;
                let path = format!("({path}).{name}");

                into_dap_var_repr(registry, scope, frame_info, &path, name, &member.value)
            })
            .collect_vec(),
        ValueLayout::IndexedList(array_items) => array_items
            .iter()
            .enumerate()
            .filter_map(|(i, item)| {
                let path = &format!("({path})[{i}]");
                into_dap_var_repr(
                    registry,
                    scope,
                    frame_info,
                    path,
                    &format!("{i}"),
                    &item.value,
                )
            })
            .collect_vec(),
        ValueLayout::NonIndexedList(values) => values
            .iter()
            .enumerate()
            .filter_map(|(i, value)| {
                let path = &format!("({path})[{i}]");
                into_dap_var_repr(registry, scope, frame_info, path, &format!("{i}"), value)
            })
            .collect_vec(),
        ValueLayout::Map(items) => items
            .iter()
            .filter_map(|(key, value)| {
                let name = format!("{key:?}");
                let path = &format!("({path})[{name}]");
                into_dap_var_repr(registry, scope, frame_info, path, &name, value)
            })
            .collect_vec(),
    }
}

pub fn into_dap_var_repr(
    registry: &mut VarRegistry,
    scope: VarScope,
    frame_info: FrameInfo,
    path: &str,
    name: &str,
    value: &Value,
) -> Option<Variable> {
    let layout = value.value_layout()?;

    let default_data_hint = VariablePresentationHint {
        kind: Some(dap::types::VariablePresentationHintKind::Data),
        ..Default::default()
    };

    let mut named_variables = None;
    let mut indexed_variables = None;
    let mut variables_reference = 0;
    let r_value;

    match layout {
        ValueLayout::IndexedList(items) => {
            indexed_variables = Some(items.len() as i64);
            let key = registry.insert_unstable(Var {
                scope,
                frame: frame_info,
                selector: Selector::Dqe(path.to_string()),
            });
            variables_reference = key.as_var_ref();
            r_value = format!("{value:?}");
        }
        ValueLayout::NonIndexedList(items) => {
            indexed_variables = Some(items.len() as i64);
            let key = registry.insert_unstable(Var {
                scope,
                frame: frame_info,
                selector: Selector::Dqe(path.to_string()),
            });
            variables_reference = key.as_var_ref();
            r_value = format!("{value:?}");
        }
        ValueLayout::Map(items) => {
            named_variables = Some(items.len() as i64);
            let key = registry.insert_unstable(Var {
                scope,
                frame: frame_info,
                selector: Selector::Dqe(path.to_string()),
            });
            variables_reference = key.as_var_ref();
            r_value = format!("{value:?}");
        }
        ValueLayout::Structure(members, ..) => {
            named_variables = Some(members.len() as i64);
            r_value = format!("{value:?}");
            let key = registry.insert_unstable(Var {
                scope,
                frame: frame_info,
                selector: Selector::Dqe(path.to_string()),
            });
            variables_reference = key.as_var_ref();
        }
        ValueLayout::PreRendered(render) => {
            r_value = render.to_string();
        }
        ValueLayout::Referential(ptr) => {
            let deref_path = &format!("*({path})");
            r_value = format!("{}", RelocatedAddress::from(ptr as usize));
            let key = registry.insert_unstable(Var {
                scope,
                frame: frame_info,
                selector: Selector::Dqe(deref_path.to_string()),
            });
            variables_reference = key.as_var_ref();
        }
        ValueLayout::Wrapped(inner) => {
            let mut var = into_dap_var_repr(registry, scope, frame_info, path, name, inner);

            if let Some(v) = var.as_mut() {
                v.value = format!("{}::{}", RenderValue::r#type(inner).name_fmt(), v.value);
            }

            return var;
        }
    };

    let var = Variable {
        name: name.to_string(),
        value: r_value,
        type_field: Some(value.r#type().name_fmt().to_string()),
        presentation_hint: Some(default_data_hint.clone()),
        indexed_variables,
        named_variables,
        variables_reference,
        ..Default::default()
    };

    Some(var)
}