use serde::{Deserialize, Serialize};
pub type ObjectId = u64;
pub type ThreadId = ObjectId;
pub type ThreadGroupId = ObjectId;
pub type StringId = ObjectId;
pub type ClassLoaderId = ObjectId;
pub type ClassObjectId = ObjectId;
pub type ArrayId = ObjectId;
pub type ReferenceTypeId = u64;
pub type ClassId = ReferenceTypeId;
pub type InterfaceId = ReferenceTypeId;
pub type ArrayTypeId = ReferenceTypeId;
pub type MethodId = u64;
pub type FieldId = u64;
pub type FrameId = u64;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location {
pub type_tag: u8, pub class_id: ReferenceTypeId,
pub method_id: MethodId,
pub index: u64, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u32)]
pub enum ThreadStatus {
Zombie = 0,
Running = 1,
Sleeping = 2,
Monitor = 3,
Wait = 4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u32)]
pub enum SuspendStatus {
Running = 0,
Suspended = 1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TypeTag {
Array = 91, Byte = 66, Char = 67, Object = 76, Float = 70, Double = 68, Int = 73, Long = 74, Short = 83, Void = 86, Boolean = 90, String = 115, Thread = 116, ThreadGroup = 103, ClassLoader = 108, ClassObject = 99, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Value {
pub tag: u8,
pub data: ValueData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ValueData {
Byte(i8),
Char(u16),
Float(f32),
Double(f64),
Int(i32),
Long(i64),
Short(i16),
Boolean(bool),
Object(ObjectId),
Void,
}
impl ValueData {
#[must_use]
pub fn format_primitive(&self) -> Option<String> {
Some(match self {
Self::Byte(v) => format!("(byte) {v}"),
Self::Char(v) => format_char(*v),
Self::Float(v) => format!("(float) {v}"),
Self::Double(v) => format!("(double) {v}"),
Self::Int(v) => format!("(int) {v}"),
Self::Long(v) => format!("(long) {v}"),
Self::Short(v) => format!("(short) {v}"),
Self::Boolean(v) => format!("(boolean) {v}"),
Self::Void => "(void)".to_string(),
Self::Object(_) => return None,
})
}
}
fn format_char(unit: u16) -> String {
char::from_u32(u32::from(unit)).map_or_else(
|| format!("(char) '\\u{unit:04X}' (unpaired surrogate, not a character)"),
|c| format!("(char) '{c}'"),
)
}
impl Value {
#[must_use]
pub fn format(&self) -> String {
match &self.data {
ValueData::Object(0) => "(object) null".to_string(),
ValueData::Object(id) => format!("(object) @{id:x}"),
primitive => primitive.format_primitive().unwrap_or_default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Variable {
pub code_index: u64,
pub name: String,
pub signature: String,
pub generic_signature: Option<String>,
pub length: u32,
pub slot: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrameInfo {
pub frame_id: FrameId,
pub location: Location,
}
#[cfg(test)]
mod tests {
use super::{Value, ValueData};
#[test]
fn an_unpaired_surrogate_is_rendered_apart_from_a_real_question_mark() {
let surrogate = ValueData::Char(0xD800).format_primitive().expect("a char is a primitive");
let question = ValueData::Char(u16::from(b'?')).format_primitive().expect("so is this one");
assert_eq!(question, "(char) '?'", "a real question mark still renders as itself");
assert_ne!(surrogate, question, "the two must not be the same bytes: {surrogate}");
assert!(surrogate.contains("\\uD800"), "the code unit itself is shown: {surrogate}");
assert!(surrogate.contains("unpaired surrogate"), "and what it is: {surrogate}");
let low = ValueData::Char(0xDFFF).format_primitive().expect("still a char");
assert!(low.contains("\\uDFFF"), "the low half of the range is covered too: {low}");
assert!(low.contains("unpaired surrogate"), "{low}");
assert_eq!(ValueData::Char(0xD7FF).format_primitive().unwrap(), "(char) '\u{d7ff}'");
assert_eq!(ValueData::Char(0xE000).format_primitive().unwrap(), "(char) '\u{e000}'");
}
#[test]
fn only_a_reference_declines_to_render_without_asking_the_debuggee() {
assert_eq!(ValueData::Int(-2_147_483_648).format_primitive().unwrap(), "(int) -2147483648");
assert_eq!(ValueData::Boolean(true).format_primitive().unwrap(), "(boolean) true");
assert_eq!(ValueData::Byte(-7).format_primitive().unwrap(), "(byte) -7");
assert_eq!(ValueData::Short(-300).format_primitive().unwrap(), "(short) -300");
assert_eq!(ValueData::Long(9_000_000_000).format_primitive().unwrap(), "(long) 9000000000");
assert_eq!(ValueData::Float(1.5).format_primitive().unwrap(), "(float) 1.5");
assert_eq!(ValueData::Double(-2.25).format_primitive().unwrap(), "(double) -2.25");
assert!(
ValueData::Object(0x2b).format_primitive().is_none(),
"a reference needs a round trip to describe, and this crate is not the side that pays it"
);
assert_eq!(Value { tag: 76, data: ValueData::Object(0x2b) }.format(), "(object) @2b");
assert_eq!(Value { tag: 76, data: ValueData::Object(0) }.format(), "(object) null");
assert_eq!(Value { tag: 67, data: ValueData::Char(0xD800) }.format(), surrogate_rendering());
}
fn surrogate_rendering() -> String {
ValueData::Char(0xD800).format_primitive().expect("a char is a primitive")
}
}