use proc_macro2::Span;
use proc_macro_warning::FormattedWarning;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AmbiguousPattern {
PossibleFfi,
PossibleUnion,
PossibleStatic,
PossibleSmartClone,
RawPointer,
Transmute,
}
impl AmbiguousPattern {
pub fn message(&self) -> &'static str {
match self {
AmbiguousPattern::PossibleFfi => "cannot determine if this is an FFI call",
AmbiguousPattern::PossibleUnion => "cannot determine if this is a union field access",
AmbiguousPattern::PossibleStatic => "cannot determine if this is a static variable",
AmbiguousPattern::PossibleSmartClone => {
"cannot determine if this is Rc::clone or Arc::clone"
}
AmbiguousPattern::RawPointer => "raw pointer operations require manual verification",
AmbiguousPattern::Transmute => "transmute type information unavailable at macro time",
}
}
pub fn hint(&self, name: &str) -> String {
match self {
AmbiguousPattern::PossibleFfi => {
format!("use #[trace_borrow(ffi = [\"{}\"])] to track as FFI", name)
}
AmbiguousPattern::PossibleUnion => {
format!(
"use #[trace_borrow(unions = [\"{}\"])] to track union access",
name
)
}
AmbiguousPattern::PossibleStatic => {
format!(
"use #[trace_borrow(statics = [\"{}\"])] to track static access",
name
)
}
AmbiguousPattern::PossibleSmartClone => {
"Rc::clone and Arc::clone are detected by method syntax; \
for turbofish syntax, tracking may be incomplete"
.to_string()
}
AmbiguousPattern::RawPointer => {
"ensure unsafe blocks are properly annotated for complete tracking".to_string()
}
AmbiguousPattern::Transmute => {
"transmute source and target types cannot be determined at macro expansion time"
.to_string()
}
}
}
pub fn note(&self) -> &'static str {
match self {
AmbiguousPattern::PossibleFfi => {
"proc macros cannot access type information to distinguish FFI from Rust functions"
}
AmbiguousPattern::PossibleUnion => {
"proc macros cannot determine if a type is a union or struct"
}
AmbiguousPattern::PossibleStatic => {
"proc macros cannot distinguish static variables from local bindings"
}
AmbiguousPattern::PossibleSmartClone => {
"Clone::clone() syntax doesn't reveal if the type is Rc or Arc"
}
AmbiguousPattern::RawPointer => {
"raw pointer safety cannot be verified at macro expansion time"
}
AmbiguousPattern::Transmute => {
"generic type parameters are not resolved during macro expansion"
}
}
}
pub fn code(&self) -> &'static str {
match self {
AmbiguousPattern::PossibleFfi => "borrowscope::ffi",
AmbiguousPattern::PossibleUnion => "borrowscope::union",
AmbiguousPattern::PossibleStatic => "borrowscope::static",
AmbiguousPattern::PossibleSmartClone => "borrowscope::clone",
AmbiguousPattern::RawPointer => "borrowscope::rawptr",
AmbiguousPattern::Transmute => "borrowscope::transmute",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct DiagnosticConfig {
pub warn_ambiguous: bool,
pub known_ffi: Vec<String>,
pub known_unions: Vec<String>,
pub known_statics: Vec<String>,
pub suppress_warnings: bool,
}
impl DiagnosticConfig {
pub fn with_warnings() -> Self {
Self {
warn_ambiguous: true,
..Default::default()
}
}
pub fn is_known_ffi(&self, name: &str) -> bool {
self.known_ffi.iter().any(|f| f == name)
}
pub fn is_known_union(&self, name: &str) -> bool {
self.known_unions.iter().any(|u| u == name)
}
pub fn is_known_static(&self, name: &str) -> bool {
self.known_statics.iter().any(|s| s == name)
}
}
static WARNING_COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub fn create_ambiguous_warning(
pattern: AmbiguousPattern,
name: &str,
span: Span,
) -> proc_macro2::TokenStream {
let message = format!(
"{} Hint: {}",
pattern.message(),
pattern.hint(name)
);
let counter = WARNING_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let warning_name = format!("BorrowScope{}Warning{}", pattern.code().replace("::", "_"), counter);
let warning = FormattedWarning::new_deprecated(&warning_name, &message, span);
quote::quote!(#warning)
}
pub fn looks_like_ffi(name: &str) -> bool {
let ffi_prefixes = [
"c_", "C_", "ffi_", "FFI_", "sys_", "libc_", "extern_", "native_",
];
let ffi_patterns = [
"malloc", "free", "realloc", "calloc", "memcpy", "memset", "memmove", "strlen", "strcpy",
"strcmp", "printf", "sprintf", "fprintf", "scanf", "fopen", "fclose", "fread", "fwrite",
"pthread_", "socket", "bind", "listen", "accept", "connect", "send", "recv", "read",
"write", "open", "close", "ioctl", "fcntl", "fork", "exec", "wait", "kill", "signal",
];
if ffi_prefixes.iter().any(|p| name.starts_with(p)) {
return true;
}
if ffi_patterns.iter().any(|p| name.starts_with(p)) {
return true;
}
if name.len() > 2 && name.chars().all(|c| c.is_uppercase() || c == '_' || c.is_numeric()) {
return true;
}
false
}
pub fn looks_like_static(name: &str) -> bool {
if name.len() < 2 {
return false;
}
let has_uppercase = name.chars().any(|c| c.is_uppercase());
let no_lowercase = !name.chars().any(|c| c.is_lowercase());
let valid_chars = name.chars().all(|c| c.is_uppercase() || c == '_' || c.is_numeric());
has_uppercase && no_lowercase && valid_chars
}
pub fn looks_like_union(name: &str) -> bool {
let union_hints = [
"Union",
"union",
"Raw",
"Packed",
"Repr",
"Bits",
"Data",
"Value",
];
union_hints.iter().any(|h| name.contains(h))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_looks_like_ffi() {
assert!(looks_like_ffi("c_read"));
assert!(looks_like_ffi("ffi_call"));
assert!(looks_like_ffi("malloc"));
assert!(looks_like_ffi("pthread_create"));
assert!(looks_like_ffi("SOME_C_MACRO"));
assert!(!looks_like_ffi("my_function"));
assert!(!looks_like_ffi("process_data"));
assert!(!looks_like_ffi("Vec"));
}
#[test]
fn test_looks_like_static() {
assert!(looks_like_static("GLOBAL_CONFIG"));
assert!(looks_like_static("MAX_SIZE"));
assert!(looks_like_static("HTTP_200"));
assert!(!looks_like_static("globalConfig"));
assert!(!looks_like_static("max_size"));
assert!(!looks_like_static("X")); }
#[test]
fn test_looks_like_union() {
assert!(looks_like_union("DataUnion"));
assert!(looks_like_union("RawValue"));
assert!(looks_like_union("PackedData"));
assert!(!looks_like_union("MyStruct"));
assert!(!looks_like_union("Config"));
}
#[test]
fn test_ambiguous_pattern_messages() {
let pattern = AmbiguousPattern::PossibleFfi;
assert!(pattern.message().contains("FFI"));
assert!(pattern.hint("my_func").contains("ffi"));
assert!(pattern.note().contains("type information"));
}
#[test]
fn test_diagnostic_config() {
let mut config = DiagnosticConfig::with_warnings();
config.known_ffi.push("c_read".to_string());
config.known_statics.push("GLOBAL".to_string());
assert!(config.is_known_ffi("c_read"));
assert!(!config.is_known_ffi("c_write"));
assert!(config.is_known_static("GLOBAL"));
}
#[test]
fn test_create_ambiguous_warning() {
let warning = create_ambiguous_warning(
AmbiguousPattern::PossibleFfi,
"c_read",
proc_macro2::Span::call_site(),
);
let warning_str = warning.to_string();
assert!(warning_str.contains("deprecated"));
}
#[test]
fn test_pattern_codes() {
assert_eq!(AmbiguousPattern::PossibleFfi.code(), "borrowscope::ffi");
assert_eq!(AmbiguousPattern::PossibleStatic.code(), "borrowscope::static");
assert_eq!(AmbiguousPattern::PossibleUnion.code(), "borrowscope::union");
assert_eq!(AmbiguousPattern::Transmute.code(), "borrowscope::transmute");
}
}