tracers_codegen/spec/
provider.rs

1//! This module provides functionality to scan the AST of a Rust source file and identify
2//! `tracers` provider traits therein, as well as analyze those traits and produce `ProbeSpec`s for
3//! each of the probes they contain.  Once the provider traits have been discovered, other modules
4//! in this crate can then process them in various ways
5use crate::hashing::HashCode;
6use crate::serde_helpers;
7use crate::spec::ProbeSpecification;
8use crate::{TracersError, TracersResult};
9use darling::FromMeta;
10use heck::SnakeCase;
11use proc_macro2::TokenStream;
12use quote::quote;
13use serde::{Deserialize, Serialize};
14use std::fmt;
15use syn::parse::{Parse, ParseStream, Result as ParseResult};
16use syn::visit::Visit;
17use syn::Token;
18use syn::{ItemTrait, TraitItem};
19
20/// Struct which contains the parsed and processed contents of the `tracer` attribute.
21#[derive(Debug, FromMeta, Clone, Serialize, Deserialize, Default)]
22pub(crate) struct TracerAttributeArgs {
23    #[darling(default)]
24    provider_name: Option<String>,
25}
26
27/// Implement parsing the arguments portion of a `#[tracer]` attribute.  This does _not_ parse the
28/// whole attribute.  It parses only the part after the `tracer` name, which contains optional
29/// parameters that control the behavior of the generated provider.
30impl Parse for TracerAttributeArgs {
31    fn parse(input: ParseStream) -> ParseResult<Self> {
32        //This implementation mostly copied from
33        //https://github.com/dtolnay/syn/blob/master/src/parse_macro_input.rs which for some reason
34        //is hidden and only works with `proc_macro` not `proc_macro2`
35        let mut metas: Vec<syn::NestedMeta> = vec![];
36
37        loop {
38            if input.is_empty() {
39                break;
40            }
41            let value = input.parse()?;
42            metas.push(value);
43            if input.is_empty() {
44                break;
45            }
46            input.parse::<Token![,]>()?;
47        }
48
49        TracerAttributeArgs::from_list(&metas).map_err(|e| input.error(e))
50    }
51}
52
53impl TracerAttributeArgs {
54    /// Parse the attribute args from a token stream.
55    ///
56    /// NB: This only works with the token  stream provided to an attribute-like proc macro.   The
57    /// assumption is that this token stream is the body of the attribute macro, without it's
58    /// actual name.  In other words, if you pass a token stream like `#[tracer(foo="bar")]` this
59    /// will fail.  To parse a full `tracer` attribute, use `syn::parse2` to parse a
60    /// `TracerAttribute`, and it will internally pick out the arguments and parse them into an
61    /// instance of this struct
62    pub(crate) fn from_token_stream(attr: TokenStream) -> TracersResult<Self> {
63        syn::parse2(attr).map_err(|e| TracersError::syn_error("Error parsing attribute args", e))
64    }
65
66    /// Parse the attribute args from an already-parsed `syn::Attribute`.
67    fn from_attribute(attr: syn::Attribute) -> TracersResult<Self> {
68        //The `syn::Attribute` struct by itself isn't usable to get TracerAttributeArgs,
69        //because it contains the ident `tracer` and in `tts` it contains everything after the
70        //ident.  So if the attribute is `#[tracer(foo = "bar")]`, then `tts` contains `(foo =
71        //"bar")`.  That won't parse with the `FromMeta` implementation Darling generates.  So
72        //we need to break upon those parens.
73        //
74        //We'll call `Attribute::parse_meta` to get the attribute as a `Meta`, and then if the
75        //meta looks like a list of values, then we'll call the Darling-generated code to parse
76        //those values
77        let meta = attr
78            .parse_meta()
79            .map_err(|e| TracersError::syn_error("Error parsing attribute metadata", e))?;
80
81        let args = match meta {
82            syn::Meta::Path(_) =>
83            //This attribute is just the ident, `#[tracer]`, with no additional attributes
84            {
85                Ok(TracerAttributeArgs::default())
86            }
87            syn::Meta::NameValue(_) => Err(TracersError::syn_like_error(
88                "Expected name/value pairs in ()",
89                attr,
90            )),
91            syn::Meta::List(list) => {
92                //There's a parens after the ident and zero or more attributes, eg
93                //`#[tracer(foo = "bar")`
94                TracerAttributeArgs::from_list(
95                    &list
96                        .nested
97                        .into_pairs()
98                        .map(syn::punctuated::Pair::into_value)
99                        .collect::<Vec<_>>(),
100                )
101                .map_err(TracersError::darling_error)
102            }
103        }?;
104
105        Ok(args)
106    }
107}
108
109/// A bit of a hack to work around a limitation in the `syn` crate.  It doesn't expose an API to
110/// parse an attribute directly; it expects you to implement the `Parse` trait to do it yourself.
111/// So this does that, though it assumes it's only ever used on a `#[tracer]` attribute because it
112/// attempts to parse possible `tracer` parameters in the attribute`
113pub(crate) struct TracerAttribute {
114    args: TracerAttributeArgs,
115}
116
117impl TracerAttribute {
118    fn from_attribute(attr: syn::Attribute) -> TracersResult<Self> {
119        Ok(TracerAttribute {
120            args: TracerAttributeArgs::from_attribute(attr)?,
121        })
122    }
123}
124
125impl Parse for TracerAttribute {
126    fn parse(input: ParseStream) -> ParseResult<Self> {
127        // We're expecting an attribute like `#[tracer]` or `#[tracer(foo = "bar")`
128        let mut attrs: Vec<syn::Attribute> = input.call(syn::Attribute::parse_outer)?;
129
130        //We're expecting exactly one such attribute
131        if let Some(tracer_attr) = attrs.pop() {
132            Ok(TracerAttribute {
133                args: TracerAttributeArgs::from_attribute(tracer_attr)
134                    .map_err(TracersError::into_syn_error)?,
135            })
136        } else {
137            Err(input.error("Expected exactly one attribute, `#[tracer]`"))
138        }
139    }
140}
141
142#[derive(Serialize, Deserialize, Clone)]
143pub struct ProviderSpecification {
144    name: String,
145    hash: HashCode,
146    #[serde(with = "serde_helpers::syn")]
147    item_trait: ItemTrait,
148    #[serde(with = "serde_helpers::token_stream")]
149    token_stream: TokenStream,
150    args: TracerAttributeArgs,
151    probes: Vec<ProbeSpecification>,
152}
153
154impl fmt::Debug for ProviderSpecification {
155    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156        writeln!(
157            f,
158            "ProviderSpecification(
159    name='{}',
160    probes:",
161            self.name
162        )?;
163
164        for probe in self.probes.iter() {
165            writeln!(f, "        {:?},", probe)?;
166        }
167
168        write!(f, ")")
169    }
170}
171
172impl ProviderSpecification {
173    fn new(
174        crate_name: &str,
175        args: TracerAttributeArgs,
176        item_trait: ItemTrait,
177    ) -> TracersResult<ProviderSpecification> {
178        let probes = find_probes(&item_trait)?;
179        let token_stream = quote! { #item_trait };
180        let hash = crate::hashing::hash(&item_trait);
181
182        //If the name was overridden by the attribute, use that override, otherwise generate a name
183        let name: String = match args.provider_name {
184            Some(ref name) => name.clone(),
185            None => Self::provider_name_from_trait(crate_name, &item_trait.ident),
186        };
187        Ok(ProviderSpecification {
188            name,
189            hash,
190            item_trait,
191            token_stream,
192            args,
193            probes,
194        })
195    }
196
197    pub(crate) fn from_token_stream(
198        crate_name: &str,
199        args: TracerAttributeArgs,
200        tokens: TokenStream,
201    ) -> TracersResult<ProviderSpecification> {
202        match syn::parse2::<syn::ItemTrait>(tokens) {
203            Ok(item_trait) => Self::new(crate_name, args, item_trait),
204            Err(e) => Err(TracersError::syn_error("Expected a trait", e)),
205        }
206    }
207
208    pub(crate) fn from_trait(
209        crate_name: &str,
210        attr: TracerAttribute,
211        item_trait: ItemTrait,
212    ) -> TracersResult<ProviderSpecification> {
213        Self::new(crate_name, attr.args, item_trait)
214    }
215
216    /// Computes the name of a provider given the name of the provider's trait.
217    pub(crate) fn provider_name_from_trait(crate_name: &str, ident: &syn::Ident) -> String {
218        // The provider name must be chosen carefully.  As of this writing (2019-04) the `bpftrace`
219        // and `bcc` tools have, shall we say, "evolving" support for USDT.  As of now, with the
220        // latest git version of `bpftrace`, the provider name can't have dots or colons.  For now,
221        // then, the provider name is just the name of the provider trait, converted into
222        // snake_case for consistency with USDT naming conventions.  If two modules in the same
223        // process have the same provider name, they will conflict and some unspecified `bad
224        // things` will happen.
225        format!("{}_{}", crate_name, ident.to_string().to_snake_case())
226    }
227
228    pub(crate) fn name(&self) -> &str {
229        &self.name
230    }
231
232    /// The name of this provider (in snake_case) combined with the hash of the provider's
233    /// contents.  Eg: `my_provider_deadc0de1918df`
234    pub(crate) fn name_with_hash(&self) -> String {
235        format!("{}_{:x}", self.name, self.hash)
236    }
237
238    pub(crate) fn hash(&self) -> HashCode {
239        self.hash
240    }
241
242    pub(crate) fn ident(&self) -> &syn::Ident {
243        &self.item_trait.ident
244    }
245
246    pub(crate) fn item_trait(&self) -> &syn::ItemTrait {
247        &self.item_trait
248    }
249
250    pub(crate) fn token_stream(&self) -> &TokenStream {
251        &self.token_stream
252    }
253
254    pub(crate) fn probes(&self) -> &Vec<ProbeSpecification> {
255        &self.probes
256    }
257
258    /// Consumes this spec and returns the same spec with all probes removed, and instead the
259    /// probes vector is returned separately.  This is a convenient way to wrap
260    /// ProviderSpecification in something else (in truth its designed for the
261    /// `ProviderTraitGenerator` implementation classes)
262    pub(crate) fn separate_probes(self) -> (ProviderSpecification, Vec<ProbeSpecification>) {
263        let probes = self.probes;
264        (
265            ProviderSpecification {
266                name: self.name,
267                hash: self.hash,
268                item_trait: self.item_trait,
269                token_stream: self.token_stream,
270                args: self.args,
271                probes: Vec::new(),
272            },
273            probes,
274        )
275    }
276}
277
278/// Scans the AST of a Rust source file, finding all traits marked with the `tracer` attribute,
279/// parses the contents of the trait, and deduces the provider spec from that.
280///
281/// Note that if any traits are encountered with the `tracer` attribute but which are in some way
282/// invalid as providers, those traits will be silently ignored.  At compile time the `tracer`
283/// attribute will cause a very detailed compile error so there's no chance the user will miss this
284/// mistake.
285pub(crate) fn find_providers(crate_name: &str, ast: &syn::File) -> Vec<ProviderSpecification> {
286    //Construct an implementation of the `syn` crate's `Visit` trait which will examine all trait
287    //declarations in the file looking for possible providers
288    struct Visitor<'a> {
289        crate_name: &'a str,
290        providers: Vec<ProviderSpecification>,
291    }
292
293    impl<'ast> Visit<'ast> for Visitor<'ast> {
294        fn visit_item_trait(&mut self, i: &'ast ItemTrait) {
295            //First pass through to the default impl
296            syn::visit::visit_item_trait(self, i);
297
298            fn is_tracer_attribute(attr: &syn::Attribute) -> bool {
299                match attr.path.segments.iter().last() {
300                    Some(syn::PathSegment { ident, .. }) if *ident == "tracer" => true,
301                    _ => false,
302                }
303            }
304
305            //Check for the `tracer` or `tracers::tracer` attribute, splitting it out from the rest
306            //of the attributes if present to ensure the
307            //hash matches the same hash computed by the proc macro when it's invoked on this
308            //trait during the compile stage
309            let mut i = i.clone();
310            let (mut tracer_attrs, other_attrs) = i
311                .attrs
312                .into_iter()
313                .partition::<Vec<syn::Attribute>, _>(is_tracer_attribute);
314            if let Some(tracer_attr) = tracer_attrs.pop() {
315                //This looks like a provider trait.
316                //Normally there should be only one `#[tracer]` but that's for the full compiler to
317                //sort out.  We'll just assume the first one is the one we want
318                i.attrs = other_attrs;
319                if let Ok(provider) = ProviderSpecification::from_trait(
320                    self.crate_name,
321                    TracerAttribute::from_attribute(tracer_attr)
322                        .expect("Failed parsing attribute metadata"),
323                    i,
324                ) {
325                    self.providers.push(provider)
326                }
327            }
328        }
329    }
330
331    let mut visitor = Visitor {
332        crate_name,
333        providers: Vec::new(),
334    };
335    visitor.visit_file(ast);
336
337    visitor.providers
338}
339
340/// Looking at the methods defined on the trait, deduce from those methods the probes that we will
341/// need to define, including their arg counts and arg types.
342///
343/// If the trait contains anything other than method declarations, or any of the declarations are
344/// not suitable as probes, an error is returned
345fn find_probes(item: &ItemTrait) -> TracersResult<Vec<ProbeSpecification>> {
346    if item.generics.type_params().next() != None || item.generics.lifetimes().next() != None {
347        return Err(TracersError::invalid_provider(
348            "Probe traits must not take any lifetime or type parameters",
349            item,
350        ));
351    }
352
353    // Look at the methods on the trait and translate each one into a probe specification
354    let mut specs: Vec<ProbeSpecification> = Vec::new();
355    for f in item.items.iter() {
356        match f {
357            TraitItem::Method(ref m) => {
358                specs.push(ProbeSpecification::from_method(item, m)?);
359            }
360            _ => {
361                return Err(TracersError::invalid_provider(
362                    "Probe traits must consist entirely of methods, no other contents",
363                    f,
364                ));
365            }
366        }
367    }
368
369    Ok(specs)
370}
371
372#[cfg(test)]
373mod test {
374    use super::*;
375    use crate::testdata::*;
376    use std::io::{BufReader, BufWriter};
377    use syn::parse_quote;
378
379    impl PartialEq<ProviderSpecification> for ProviderSpecification {
380        fn eq(&self, other: &ProviderSpecification) -> bool {
381            self.name == other.name && self.probes == other.probes
382        }
383    }
384
385    /// Allows tests to compare a test case directly to a ProviderSpecification to ensure they match
386    impl PartialEq<TestProviderTrait> for ProviderSpecification {
387        fn eq(&self, other: &TestProviderTrait) -> bool {
388            self.name == other.provider_name
389                && other
390                    .probes
391                    .as_ref()
392                    .map(|probes| &self.probes == probes)
393                    .unwrap_or(false)
394        }
395    }
396
397    fn get_filtered_test_traits(with_errors: bool) -> Vec<TestProviderTrait> {
398        get_test_provider_traits(|t: &TestProviderTrait| t.expected_error.is_some() == with_errors)
399    }
400
401    #[test]
402    fn find_providers_ignores_invalid_traits() {
403        for test_trait in get_filtered_test_traits(true) {
404            let trait_decl = test_trait.tokenstream;
405            let test_file: syn::File = parse_quote! {
406                #[tracer]
407                #trait_decl
408            };
409
410            assert_eq!(
411                None,
412                find_providers(TEST_CRATE_NAME, &test_file).first(),
413                "The invalid trait '{}' was returned by find_providers as valid",
414                test_trait.description
415            );
416        }
417    }
418
419    #[test]
420    fn find_providers_finds_valid_traits() {
421        for test_trait in get_filtered_test_traits(false) {
422            let trait_attr = test_trait.attr_tokenstream.clone();
423            let trait_decl = test_trait.tokenstream.clone();
424            let test_file: syn::File = parse_quote! {
425                #trait_attr
426                #trait_decl
427            };
428
429            let mut providers = find_providers(TEST_CRATE_NAME, &test_file);
430            assert_ne!(
431                0,
432                providers.len(),
433                "the test trait '{}' was not properly detected by find_provider",
434                test_trait.description
435            );
436
437            assert_eq!(providers.pop().unwrap(), test_trait);
438        }
439    }
440
441    #[test]
442    fn find_probes_fails_with_invalid_traits() {
443        for test_trait in get_filtered_test_traits(true) {
444            let trait_decl = test_trait.tokenstream;
445            let item_trait: syn::ItemTrait = parse_quote! {
446                #[tracer]
447                #trait_decl
448            };
449
450            let error = find_probes(&item_trait).err();
451            assert_ne!(
452                None, error,
453                "The invalid trait '{}' was returned by find_probes as valid",
454                test_trait.description
455            );
456
457            let expected_error_substring = test_trait.expected_error.unwrap();
458            let message = error.unwrap().to_string();
459            assert!(message.contains(expected_error_substring),
460                "The invalid trait '{}' should produce an error containing '{}' but instead it produced '{}'",
461                test_trait.description,
462                expected_error_substring,
463                message
464            );
465        }
466    }
467
468    #[test]
469    fn find_probes_succeeds_with_valid_traits() {
470        for test_trait in get_filtered_test_traits(false) {
471            let trait_decl = test_trait.tokenstream;
472            let item_trait: syn::ItemTrait = parse_quote! {
473                #[tracer]
474                #trait_decl
475            };
476
477            let probes = find_probes(&item_trait).unwrap();
478            assert_eq!(probes, test_trait.probes.unwrap_or(Vec::new()));
479        }
480    }
481
482    #[test]
483    fn found_providers_have_same_hash() {
484        //There are two ways for us to get a ProviderSpecification:
485        // The first is from a TokenStream in an attribute proc macro.  That's how the `#[tracer]`
486        // macro works.
487        // The second is in calling `find_providers` to discover providers in a source file
488        //
489        // A fundamental assumption made in this project is that the hashes of a given provider
490        // will be identical regardless of which of the two ways it was obtained.
491        //
492        // However, the attribute proc macro gets a token stream which does not include the
493        // `#[tracer]` attribute itself.  The `find_providers` code must see this attribute because
494        // that's how it identifies a provider trait.  This test ensures the hashes are always
495        // corrected to match
496        for test_trait in get_filtered_test_traits(false) {
497            //Note: we remove the `#[tracer]` attribute from the token stream because that's what
498            //the proc macro infrastructure does.  All other attributes are preserved, but the
499            //attribute that triggers the macro is provided in a separate token stream which we
500            //ignore
501            let provider_from_ts = ProviderSpecification::from_trait(
502                TEST_CRATE_NAME,
503                syn::parse2(test_trait.attr_tokenstream.clone()).unwrap(),
504                syn::parse2(test_trait.tokenstream.clone()).unwrap(),
505            )
506            .unwrap();
507
508            let tracer_attr = test_trait.attr_tokenstream;
509            let trait_decl = test_trait.tokenstream;
510            let file: syn::File = parse_quote! {
511                mod foo {
512                    fn useless_func() -> bool { false }
513                }
514
515                trait NotAProvider {
516                    fn probe0(not_a_probe_arg: usize);
517                }
518
519                #tracer_attr
520                #trait_decl
521            };
522
523            let providers = find_providers(TEST_CRATE_NAME, &file);
524
525            assert_eq!(1, providers.len());
526            let provider_from_file = providers.get(0).unwrap();
527
528            assert_eq!(provider_from_ts.name(), provider_from_file.name());
529            assert_eq!(provider_from_ts.hash(), provider_from_file.hash());
530        }
531    }
532
533    #[test]
534    fn provider_serde_test() {
535        //Go through all of the valid test traits, parse them in to a provider, then serialize and
536        //deserialize to json to make sure the round trip serialization works
537        for test_trait in get_filtered_test_traits(false) {
538            println!("Parsing attribute: {}", test_trait.attr_tokenstream);
539            let (attr, item_trait) = test_trait.get_attr_and_item_trait();
540            let provider =
541                ProviderSpecification::from_trait(TEST_CRATE_NAME, attr, item_trait).unwrap();
542            let mut buffer = Vec::new();
543            let writer = BufWriter::new(&mut buffer);
544            serde_json::to_writer(writer, &provider).unwrap();
545
546            let reader = BufReader::new(buffer.as_slice());
547
548            let rt_provider: ProviderSpecification = match serde_json::from_reader(reader) {
549                Ok(p) => p,
550                Err(e) => {
551                    panic!(
552                        r###"Error deserializing provider:
553                            Test case: {}
554                            JSON: {}
555                            Error: {}"###,
556                        test_trait.description,
557                        String::from_utf8(buffer).unwrap(),
558                        e
559                    );
560                }
561            };
562
563            assert_eq!(
564                provider, rt_provider,
565                "test case: {}",
566                test_trait.description
567            );
568        }
569    }
570
571    #[test]
572    fn parses_tracer_attributes_test() {
573        //Make sure the `#[tracer]` attribute on all valid test cases can parse correctly
574        for test_trait in get_filtered_test_traits(false) {
575            println!("Parsing attribute: {}", test_trait.attr_tokenstream);
576            let _attribute: TracerAttribute =
577                syn::parse2(test_trait.attr_tokenstream).expect("Expected valid tracer attribute");
578        }
579    }
580}