Generate deterministic names for 'uri' macros.

This commit is contained in:
Cormac Relf 2024-05-29 14:55:18 -05:00 committed by Sergio Benitez
parent 2a1afd12f5
commit 120d1e78da
3 changed files with 86 additions and 9 deletions

View File

@ -209,9 +209,11 @@ fn internal_uri_macro_decl(route: &Route) -> TokenStream {
// Generate a unique macro name based on the route's metadata. // Generate a unique macro name based on the route's metadata.
let macro_name = route.handler.sig.ident.prepend(crate::URI_MACRO_PREFIX); let macro_name = route.handler.sig.ident.prepend(crate::URI_MACRO_PREFIX);
let inner_macro_name = macro_name.uniqueify_with(|mut hasher| { let inner_macro_name = macro_name.uniqueify_with(|mut hasher| {
route.handler.sig.ident.hash(&mut hasher); route.attr.method.0.hash(&mut hasher);
route.attr.uri.path().hash(&mut hasher); route.attr.uri.path().hash(&mut hasher);
route.attr.uri.query().hash(&mut hasher) route.attr.uri.query().hash(&mut hasher);
route.attr.data.as_ref().map(|d| d.value.hash(&mut hasher));
route.attr.format.as_ref().map(|f| f.0.hash(&mut hasher));
}); });
let route_uri = route.attr.uri.to_string(); let route_uri = route.attr.uri.to_string();

View File

@ -80,18 +80,21 @@ impl IdentExt for syn::Ident {
self.prepend(crate::ROCKET_IDENT_PREFIX) self.prepend(crate::ROCKET_IDENT_PREFIX)
} }
/// Create a unqiue version of the ident `self` based on the hash of `self`,
/// its span, the current call site, and any additional information provided
/// by the closure `f`.
///
/// Span::source_file() / line / col are not stable, but the byte span and
/// some kind of scope identifier do appear in the `Debug` representation
/// for `Span`. And they seem to be consistent across compilations: "#57
/// bytes(106..117)" at the time of writing. So we use that.
fn uniqueify_with<F: FnMut(&mut dyn Hasher)>(&self, mut f: F) -> syn::Ident { fn uniqueify_with<F: FnMut(&mut dyn Hasher)>(&self, mut f: F) -> syn::Ident {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
// Keep a global counter (+ thread ID later) to generate unique ids.
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
self.hash(&mut hasher); self.hash(&mut hasher);
std::process::id().hash(&mut hasher); format!("{:?}", self.span()).hash(&mut hasher);
std::thread::current().id().hash(&mut hasher); format!("{:?}", Span::call_site()).hash(&mut hasher);
COUNTER.fetch_add(1, Ordering::AcqRel).hash(&mut hasher);
f(&mut hasher); f(&mut hasher);
self.append(&format!("_{}", hasher.finish())) self.append(&format!("_{}", hasher.finish()))

View File

@ -0,0 +1,72 @@
#[macro_use] extern crate rocket;
#[get("/")]
fn index() { }
mod module {
// This one has all the same macro inputs, and we need it to
// generate a crate-wide unique identifier for the macro it
// defines.
#[get("/")]
pub fn index() { }
}
// Makes sure that the hashing of the proc macro's call site span
// is enough, even if we're inside a declarative macro
macro_rules! gen_routes {
() => {
#[get("/")]
pub fn index() { }
pub mod two {
#[get("/")]
pub fn index() { }
}
}
}
mod module2 {
gen_routes!();
pub mod module3 {
gen_routes!();
}
}
#[test]
fn test_uri_reachability() {
use rocket::http::Status;
use rocket::local::blocking::Client;
let rocket = rocket::build()
.mount("/", routes![index])
.mount("/module", routes![module::index])
.mount("/module2", routes![module2::index])
.mount("/module2/two", routes![module2::two::index])
.mount("/module2/module3", routes![module2::module3::index])
.mount("/module2/module3/two", routes![module2::module3::two::index]);
let uris = rocket.routes()
.map(|r| r.uri.base().to_string())
.collect::<Vec<_>>();
let client = Client::debug(rocket).unwrap();
for uri in uris {
let response = client.get(uri).dispatch();
assert_eq!(response.status(), Status::Ok);
}
}
#[test]
fn test_uri_calls() {
let uris = [
uri!(index()),
uri!(module::index()),
uri!(module2::index()),
uri!(module2::two::index()),
uri!(module2::module3::index()),
uri!(module2::module3::two::index()),
];
assert!(uris.iter().all(|uri| uri == "/"));
}