1use std::{
2 collections::hash_map::DefaultHasher,
3 hash::{Hash as _, Hasher as _},
4};
5
6use proc_macro::Span;
7use proc_macro2::{Ident as Ident2, Span as Span2, TokenStream as TokenStream2};
8use quote::{format_ident, quote};
9use syn::{parse_quote, Expr, Ident, LitStr};
10
11pub(crate) use symbol::mangled as mangled_symbol_name;
12
13mod symbol;
14
15pub(crate) fn crate_local_disambiguator() -> u64 {
16 // We want a deterministic, but unique-per-macro-invocation identifier. For that we
17 // hash the call site `Span`'s debug representation, which contains a counter that
18 // should disambiguate macro invocations within a crate.
19 hash(&format!("{:?}", Span::call_site()))
20}
21
22pub(crate) fn escaped_expr_string(expr: &Expr) -> String {
23 quote!(#expr)
24 .to_string()
25 .replace('{', "{{")
26 .replace(from:'}', to:"}}")
27}
28
29pub(crate) fn interned_string(
30 string: &str,
31 tag: &str,
32 is_log_statement: bool,
33 prefix: Option<&str>,
34 defmt_path: &syn::Path,
35) -> TokenStream2 {
36 // NOTE we rely on this variable name when extracting file location information from the DWARF
37 // without it we have no other mean to differentiate static variables produced by `info!` vs
38 // produced by `intern!` (or `internp`)
39 let var_name: Ident = if is_log_statement {
40 format_ident!("DEFMT_LOG_STATEMENT")
41 } else {
42 format_ident!("S")
43 };
44
45 let var_addr: TokenStream = if cfg!(feature = "unstable-test") {
46 quote!({ #defmt_path::export::fetch_add_string_index() })
47 } else {
48 let var_item: TokenStream = static_variable(&var_name, data:string, tag, prefix);
49 quote!({
50 #var_item
51 &#var_name as *const u8 as u16
52 })
53 };
54
55 quote!({
56 #defmt_path::export::make_istr(#var_addr)
57 })
58}
59
60/// work around restrictions on length and allowed characters imposed by macos linker
61/// returns (note the comma character for macos):
62/// under macos: ".defmt," + 16 character hex digest of symbol's hash
63/// otherwise: ".defmt." + prefix + symbol
64pub(crate) fn linker_section(for_macos: bool, prefix: Option<&str>, symbol: &str) -> String {
65 let mut sub_section: String = if let Some(prefix: &str) = prefix {
66 format!(".{prefix}.{symbol}")
67 } else {
68 format!(".{symbol}")
69 };
70
71 if for_macos {
72 sub_section = format!(",{:x}", hash(&sub_section));
73 }
74
75 format!(".defmt{sub_section}")
76}
77
78pub(crate) fn static_variable(
79 name: &Ident2,
80 data: &str,
81 tag: &str,
82 prefix: Option<&str>,
83) -> TokenStream2 {
84 let sym_name: String = mangled_symbol_name(tag, data);
85 let section: String = linker_section(for_macos:false, prefix, &sym_name);
86 let section_for_macos: String = linker_section(for_macos:true, prefix, &sym_name);
87
88 quote!(
89 #[cfg_attr(target_os = "macos", link_section = #section_for_macos)]
90 #[cfg_attr(not(target_os = "macos"), link_section = #section)]
91 #[export_name = #sym_name]
92 static #name: u8 = 0;
93 )
94}
95
96pub(crate) fn string_literal(content: &str) -> LitStr {
97 LitStr::new(value:content, span:Span2::call_site())
98}
99
100pub(crate) fn variable(name: &str) -> Expr {
101 let ident: Ident = Ident::new(string:name, span:Span2::call_site());
102 parse_quote!(#ident)
103}
104
105fn hash(string: &str) -> u64 {
106 let mut hasher: DefaultHasher = DefaultHasher::new();
107 string.hash(&mut hasher);
108 hasher.finish()
109}
110