tracers_codegen/
argtypes.rs

1//! Almost all of the probing code takes advantage of Rust's type system to ensure any type passed
2//! to a probe can be represented as a C type, and that the appropriate conversion, if any, is
3//! applied before calling the C code.
4//!
5//! Except, unfortunately, in the code that operates on a Rust AST as represented by `syn`.  This
6//! isn't actually compiled Rust, it's only parsed Rust, so something like `Option<&str>` doesn't
7//! have a type, it's basically just a slightly more structured version of a string.
8//!
9//! Thus, in order to resolve a `syn::Type` to a corresponding Rust type requires some manual
10//! hard-coding work.  That work is done here.
11//!
12//! Anyone who is extending this crate to support additional types, or even just type aliases, must
13//! update the `from_syn_type` function accordingly
14use crate::serde_helpers;
15use serde::{Deserialize, Serialize};
16use std::ffi::{CStr, CString};
17#[cfg(unix)]
18use std::ffi::{OsStr, OsString};
19use syn::parse_quote;
20use tracers_core::argtypes::*;
21use tracers_core::{ProbeArgType, ProbeArgWrapper};
22
23macro_rules! maybe_type {
24    ($syn_t:expr, $rust_t:ty) => {
25        let rust_syn_t: syn::Type = parse_quote! { $rust_t };
26        if *$syn_t == rust_syn_t {
27            return Some(ArgTypeInfo::new::<$rust_t>());
28        }
29    };
30    (@naked $syn_t:expr, $rust_t:ty) => {
31        maybe_type!($syn_t, $rust_t);
32    };
33    (@ref $syn_t:expr, $rust_t:ty) => {
34        maybe_type!($syn_t, &$rust_t);
35    };
36    (@opt $syn_t:expr, $rust_t:ty) => {
37        maybe_type!($syn_t, &Option<$rust_t>);
38    };
39    (@opt_ref $syn_t:expr, $rust_t:ty) => {
40        maybe_type!($syn_t, &Option<&$rust_t>);
41    };
42    (@ptr $syn_t:expr, $rust_t:ty) => {
43        maybe_type!($syn_t, *const $rust_t);
44    };
45    (@primitive $syn_t:expr, $rust_t:ty) => {
46        maybe_type!(@naked $syn_t, $rust_t);
47        maybe_type!(@ref $syn_t, $rust_t);
48        maybe_type!(@opt $syn_t, $rust_t);
49        maybe_type!(@opt_ref $syn_t, $rust_t);
50        maybe_type!(@ptr $syn_t, $rust_t);
51    };
52    (@string $syn_t:expr, $rust_t:ty) => {
53        maybe_type!(@naked $syn_t, $rust_t);
54        maybe_type!(@opt $syn_t, $rust_t);
55    };
56}
57
58macro_rules! maybe_types {
59    ($syn_t:expr, $($rust_t:ty),+) => {
60        $(
61            maybe_type!($syn_t, $rust_t);
62        )*
63    };
64    (@$tag:ident $syn_t:expr, $($rust_t:ty),+) => {
65        $(
66            maybe_type!(@$tag $syn_t, $rust_t);
67        )*
68    };
69}
70
71/// Given a type expression from a Rust AST, tries to get the type information for that type.
72/// If it can't be resolved, returns `None`
73///
74/// This function has a massive cyclomatic complexity due to all of the macro-generated code, but
75/// in this case it's safe to ignore the clippy lint.
76#[allow(clippy::cognitive_complexity)]
77pub(crate) fn from_syn_type(ty: &syn::Type) -> Option<ArgTypeInfo> {
78    //TODO: There HAS to be a better and more performant way to do this, but working with the syn
79    //type hierarchy directly is just agony
80    maybe_types!(@primitive ty, i8, u8, i16, u16, i32, u32, i64, u64, usize, isize);
81    maybe_types!(@string ty, &str, &String);
82
83    #[cfg(unix)] // Only the unix impl of OsStr/OsString exposes the string as bytes
84    maybe_types!(@string ty, &OsStr, &OsString);
85    maybe_types!(@string ty, &CStr, &CString);
86
87    maybe_type!(@primitive ty, bool);
88
89    //Else, this isn't a type we recognize
90    None
91}
92
93#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
94pub(crate) struct ArgTypeInfo {
95    #[serde(with = "serde_helpers::string")]
96    c_type: CType,
97    c_type_str: String,
98    rust_type_str: String,
99}
100
101#[allow(dead_code)] //TODO: temporary
102impl ArgTypeInfo {
103    pub fn new<T: ProbeArgType<T>>() -> ArgTypeInfo {
104        ArgTypeInfo {
105            c_type: <<<T as ProbeArgType<T>>::WrapperType as ProbeArgWrapper>::CType as ProbeArgNativeTypeInfo>::get_c_type(),
106            c_type_str: <<<T as ProbeArgType<T>>::WrapperType as ProbeArgWrapper>::CType as ProbeArgNativeTypeInfo>::get_c_type_str().to_owned(),
107            rust_type_str: <<<T as ProbeArgType<T>>::WrapperType as ProbeArgWrapper>::CType as ProbeArgNativeTypeInfo>::get_rust_type_str().to_owned()
108        }
109    }
110
111    /// Gets the `CType` enum which corresponds to the C data type which is used to represent this
112    /// type when calling probes
113    pub fn get_c_type_enum(&self) -> CType {
114        self.c_type.clone()
115    }
116
117    /// Gets a string which contains the C type for use generating C/C++ code.  For example if the
118    /// `CType` is `VoidPtr`, this function returns `void *`
119    pub fn get_c_type_str(&self) -> &str {
120        &self.c_type_str
121    }
122
123    /// Gets a string containing the Rust type corresponding to the native C type.  This also is
124    /// used for generating code.  It corresponds directly to the type which the `ProbeArgWrapper`
125    /// returns and passes to the auto-generated Rust bindings.
126    ///
127    /// That means that as long as each of the C types in `get_c_type_str` correctly match the
128    /// corresponding Rust types returned by this function, the Rust type system will ensure there
129    /// are no errors.
130    ///
131    /// For example, if somewhere else in the code we have a bug whereby `&str` is passed as
132    /// `void*`, but this code thinks it should be `char*`, when Rust compiles the call to the
133    /// generated Rust bindings it will fail.
134    pub fn get_rust_type_str(&self) -> &str {
135        &self.rust_type_str
136    }
137}
138
139#[cfg(test)]
140mod test {
141    use super::*;
142    use crate::testdata::*;
143
144    macro_rules! test_type {
145        ($rust_t:ty, $c_type:expr, $rust_type_str:expr) => {
146            let syn_typ: syn::Type = parse_quote! { $rust_t };
147            assert_eq!(
148                Some(ArgTypeInfo {
149                    c_type: $c_type,
150                    c_type_str: $c_type.to_string(),
151                    rust_type_str: $rust_type_str.to_string(),
152                }),
153                from_syn_type(&syn_typ),
154                "Got unexpected arg type info for type expression '{}'", stringify!($rust_t)
155            );
156        };
157        (@naked $rust_t:ty, $c_type:expr, $rust_type_str:expr) => {
158            test_type!($rust_t, $c_type, $rust_type_str);
159        };
160        // Primitive types marshal refs the same as the value type
161        (@primitive_ref $rust_t:ty, $c_type:expr, $rust_type_str:expr) => {
162            test_type!(&$rust_t, $c_type, $rust_type_str);
163        };
164        // Primitive types marshal optionals the same as the value type (using the default value
165        // for None)
166        (@primitive_opt $rust_t:ty, $c_type:expr, $rust_type_str:expr) => {
167            test_type!(&Option<$rust_t>, $c_type, $rust_type_str);
168        };
169        // Primitive types marshal Option<&..> the same also
170        (@primitive_opt_ref $rust_t:ty, $c_type:expr, $rust_type_str:expr) => {
171            test_type!(&Option<&$rust_t>, $c_type, $rust_type_str);
172        };
173        // Primitive pointers are just cast to void pointers, except for the char types
174        (@primitive_ptr $rust_t:ty, $c_type:expr, $rust_type_str:expr) => {
175            test_type!(*const $rust_t, CType::VoidPtr, "*const std::os::raw::c_void");
176        };
177        (@primitive_ptr i8, $c_type:expr, $rust_type_str:expr) => {
178            test_type!(*const $rust_t, CType::CharPtr, "*const std::os::raw::c_char");
179        };
180        (@primitive_ptr u8, $c_type:expr, $rust_type_str:expr) => {
181            test_type!(*const $rust_t, CType::UCharPtr, "*const std::os::raw::c_uchar");
182        };
183        (@primitive $rust_t:ty, $c_type:expr, $rust_type_str:expr) => {
184            test_type!(@naked $rust_t, $c_type, $rust_type_str);
185            test_type!(@primitive_ptr $rust_t, $c_type, $rust_type_str);
186            test_type!(@primitive_ref $rust_t, $c_type, $rust_type_str);
187            test_type!(@primitive_opt $rust_t, $c_type, $rust_type_str);
188            test_type!(@primitive_opt_ref $rust_t, $c_type, $rust_type_str);
189        };
190        (@string $rust_t:ty, $c_type:expr, $rust_type_str:expr) => {
191            test_type!(@naked $rust_t, $c_type, $rust_type_str);
192            test_type!(@primitive_opt $rust_t, $c_type, $rust_type_str);
193        };
194    }
195
196    #[test]
197    fn test_type_support() {
198        test_type!(@primitive i8, CType::Char, "std::os::raw::c_char");
199        test_type!(@primitive u8, CType::UChar, "std::os::raw::c_uchar");
200        test_type!(@primitive i16, CType::Short, "std::os::raw::c_short");
201        test_type!(@primitive u16, CType::UShort, "std::os::raw::c_ushort");
202        test_type!(@primitive i32, CType::Int, "std::os::raw::c_int");
203        test_type!(@primitive u32, CType::UInt, "std::os::raw::c_uint");
204        test_type!(@primitive i64, CType::LongLong, "std::os::raw::c_longlong");
205        test_type!(@primitive u64, CType::ULongLong, "std::os::raw::c_ulonglong");
206        test_type!(@primitive usize, CType::SizeT, "libc::size_t");
207        test_type!(@primitive isize, CType::SSizeT, "libc::ssize_t");
208        test_type!(@primitive bool, CType::Int, "std::os::raw::c_int");
209
210        test_type!(@string &str, CType::CharPtr, "*const std::os::raw::c_char");
211        test_type!(@string &String, CType::CharPtr, "*const std::os::raw::c_char");
212
213        #[cfg(unix)] // Only the unix impl of OsStr/OsString exposes the string as bytes
214        test_type!(@string &OsStr, CType::CharPtr, "*const std::os::raw::c_char");
215        #[cfg(unix)] // Only the unix impl of OsStr/OsString exposes the string as bytes
216        test_type!(@string &OsString, CType::CharPtr, "*const std::os::raw::c_char");
217
218        test_type!(@string &CStr, CType::CharPtr, "*const std::os::raw::c_char");
219        test_type!(@string &CString, CType::CharPtr, "*const std::os::raw::c_char");
220    }
221
222    #[test]
223    fn test_support_for_all_test_traits() {
224        //Anything in our corpus of valid provider traits should correspond to a known type
225        //Plus, this way if I ever go and add support for a new type, if I add an example of it to
226        //the test cases, this test will fail, which will remind me I need to enable support for
227        //that type here.
228        for test_trait in
229            get_test_provider_traits(|t: &TestProviderTrait| t.expected_error.is_none()).into_iter()
230        {
231            //The test data don't tell us anything about what the expected C wrapper type of each
232            //arg will be.  Nor should they; that's what this module's tests are for.
233            //The purpose of this test is to ensure there are no probe args in any of the traits
234            //which are expected to be valid, that cannot be identified from their `syn::Type`
235            //representation
236            for probe in test_trait.probes.unwrap().into_iter() {
237                for (name, rust_syn_type, c_type) in probe.args.into_iter() {
238                    let arg_type_info = from_syn_type(&rust_syn_type);
239
240                    assert_ne!(None, arg_type_info,
241                               "test trait '{}' probe '{}' arg '{}' has a type which `from_syn_type` can't identify",
242                               test_trait.description,
243                               probe.name,
244                               name);
245
246                    let arg_type_info = arg_type_info.unwrap();
247                    assert_eq!(c_type, arg_type_info.get_c_type_enum(),
248                               "test trait '{}' probe '{}' arg '{}' has a type for which `from_syn_type` returned an incorrect `CType` (and, probably, other wrapper types also)",
249                               test_trait.description,
250                               probe.name,
251                               name);
252                }
253            }
254        }
255    }
256}