1#![cfg_attr(feature = "nightly", feature(fmt_helpers_for_derive))]
2
3use std::fmt::{Formatter, Result, Debug};
14
15macro_rules! hook {
16 ($var:ident : $t:ty, $a:expr, $b:expr) => {
17 {
18 #[allow(non_upper_case_globals)]
19 static mut $var: $t = $a;
20 static HOOK: $t = $b;
21 let detour = retour::RawDetour::new($var as *const (), HOOK as *const ()).unwrap();
22 detour.enable().unwrap();
23 $var = std::mem::transmute::<*const (), $t>(detour.trampoline());
24 std::mem::forget(detour);
25 }
26 }
27}
28
29macro_rules! hook_fmt {
30 ($color:literal $(,$ty:ty)*) => {
31 $(hook! {
32 func: for<'a, 'b, 'c> fn(&$ty, &'b mut Formatter<'c>) -> Result,
33 <$ty as Debug>::fmt,
34 |this, fmt| {
35 color(fmt, $color)?;
36 unsafe { func(this, fmt)? };
37 uncolor(fmt)?;
38 Ok(())
39 }
40 })*
41 }
42}
43
44macro_rules! hook_fmt_ref {
46 ($color:literal $(,$ty:ty)*) => {
47 hook_fmt!($color $(,$ty)*);
48 hook_fmt!($color $(,&'static $ty)*);
49 hook_fmt!($color $(,&'static &'static $ty)*);
50 }
51}
52
53pub unsafe fn enable() {
58 unsafe {
59 hook_fmt_ref! { 1, u8, u16, u32, u64, u128 };
60 hook_fmt_ref! { 1, i8, i16, i32, i64, i128 };
61 hook_fmt! { 1, f32, f64 };
62 hook_fmt! { 1, bool };
63 hook_fmt! { 2, char, str };
64
65 hook! {
66 hook: for<'a> fn(&'a mut std::fmt::DebugStruct<'static, 'static>, &str, &dyn Debug) -> &'a mut std::fmt::DebugStruct<'static, 'static>,
67 std::fmt::DebugStruct::field,
68 |fmt, name, val| unsafe { hook(fmt, &colored(name, 5), val) }
69 };
70
71 #[cfg(feature = "nightly")]
74 structs();
75 #[cfg(feature = "nightly")]
76 tuples();
77 }
78}
79
80#[cfg(feature = "nightly")]
81unsafe fn structs() {
82 macro_rules! hook_struct {
83 ($name:ident $(,$a:ident $b:ident)*) => {
84 hook! {
85 func: fn(&mut Formatter<'static>, &str $(, $a: &str, $b: &dyn Debug)*) -> Result,
86 Formatter::$name,
87 |fmt, name $(, $a, $b)*| unsafe { func(fmt, &colored(name, 4) $(, $a, $b)*) }
88 }
89 }
90 }
91
92 hook! {
93 hook: for<'b> fn(&'b mut Formatter<'static>, &str) -> std::fmt::DebugStruct<'b, 'static>,
94 Formatter::debug_struct,
95 |fmt, name| unsafe { hook(fmt, &colored(name, 4)) }
96 };
97
98 hook_struct!(debug_struct_field1_finish, n1 v1);
99 hook_struct!(debug_struct_field2_finish, n1 v1, n2 v2);
100 hook_struct!(debug_struct_field3_finish, n1 v1, n2 v2, n3 v3);
101 hook_struct!(debug_struct_field4_finish, n1 v1, n2 v2, n3 v3, n4 v4);
102 hook_struct!(debug_struct_field5_finish, n1 v1, n2 v2, n3 v3, n4 v4, n5 v5);
103
104 hook! {
105 hook: for<'b> fn(&'b mut Formatter<'static>, &str, &[&str], &[&dyn Debug]) -> Result,
106 Formatter::debug_struct_fields_finish,
107 |fmt, name, fields, values| unsafe { hook(fmt, &colored(name, 4), fields, values) }
108 };
109}
110
111#[cfg(feature = "nightly")]
112unsafe fn tuples() {
113 macro_rules! hook_tuple {
114 ($name:ident $(,$a:ident)*) => {
115 hook! {
116 func: fn(&mut Formatter<'static>, &str $(, $a: &dyn Debug)*) -> Result,
117 Formatter::$name,
118 |fmt, name $(, $a)*| unsafe { func(fmt, &colored(name, 4) $(, $a)*) }
119 }
120 }
121 }
122
123 hook! {
124 hook: for<'b> fn(&'b mut Formatter<'static>, &str) -> std::fmt::DebugTuple<'b, 'static>,
125 Formatter::debug_tuple,
126 |fmt, name| unsafe { hook(fmt, &colored(name, 4)) }
127 };
128
129 hook_tuple!(debug_tuple_field1_finish, v1);
130 hook_tuple!(debug_tuple_field2_finish, v1, v2);
131 hook_tuple!(debug_tuple_field3_finish, v1, v2, v3);
132 hook_tuple!(debug_tuple_field4_finish, v1, v2, v3, v4);
133 hook_tuple!(debug_tuple_field5_finish, v1, v2, v3, v4, v5);
134
135 hook! {
136 hook: for<'b> fn(&'b mut Formatter<'static>, &str, &[&dyn Debug]) -> Result,
137 Formatter::debug_tuple_fields_finish,
138 |fmt, name, values| unsafe { hook(fmt, &colored(name, 4), values) }
139 };
140}
141
142fn color(fmt: &mut Formatter, color: u8) -> Result {
143 write!(fmt, "\x1b[3{}m", color)
144}
145
146fn uncolor(fmt: &mut Formatter) -> Result {
147 write!(fmt, "\x1b[39m")
148}
149
150fn colored(name: &str, color: u8) -> String {
151 format!("\x1b[3{}m{}\x1b[39m", color, name)
152}
153
154#[test]
155fn test() {
156 unsafe {
157 enable();
158 }
159
160 #[derive(Debug)]
161 #[allow(dead_code)]
162 struct Nested {
163 name: String,
164 age: i32,
165 things: Vec<String>,
166 other: Option<Box<Nested>>,
167 }
168
169 let val = Nested {
170 name: "Alice".to_string(),
171 age: 42,
172 things: vec!["one".to_string(), "two".to_string()],
173 other: Some(Box::new(Nested {
174 name: "Bob".to_string(),
175 age: 24,
176 things: vec!["three".to_string(), "four".to_string()],
177 other: None,
178 })),
179 };
180
181 println!("{val:#?}");
182}