1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#![cfg_attr(rustc_is_unstable, feature(proc_macro_diagnostic, proc_macro_span))]

use {
    proc_macro::{Span, TokenStream},
    quote::{quote, quote_spanned, ToTokens},
    syn::{fold::Fold, parse_quote_spanned, spanned::Spanned, visit::Visit},
};

/// Runs one of two branches depending on whether we're running on a stable
/// version of the compiler (stable, beta), or an unstable version (nightly,
/// dev, or anywhere that `RUSTC_BOOTSTRAP=1`).
macro_rules! if_unstable {
    { then { $($then:tt)* } else { $($else:tt)* } } => {
        #[allow(unreachable_code)]
        if cfg!(rustc_is_unstable) {
            #[cfg(not(rustc_is_unstable))] {
                unreachable!();
            }
            #[cfg(rustc_is_unstable)] {
                $($then)*
            }
        } else {
            #[cfg(rustc_is_unstable)] {
                unreachable!();
            }
            $($else)*
        }
    }
}

#[proc_macro_attribute]
pub fn turn_off_the_borrow_checker(_attribute: TokenStream, input: TokenStream) -> TokenStream {
    let mut suppressor = BorrowCheckerSuppressor {
        suppressed_references: vec![],
    };

    let output = if let Ok(as_file) = syn::parse(input.clone()) {
        suppressor.fold_file(as_file).to_token_stream()
    } else if let Ok(as_expr) = syn::parse(input.clone()) {
        suppressor.fold_expr(as_expr).to_token_stream()
    } else if let Ok(as_stmt) = syn::parse(input) {
        suppressor.fold_stmt(as_stmt).to_token_stream()
    } else {
        return quote! { compile_error!("unsupported use of #[turn_off_the_borrow_checker]") }
            .into();
    };

    if_unstable! {
        then {
            proc_macro::Diagnostic::spanned(
                vec![Span::call_site().parent().unwrap_or_else(Span::call_site)],
                proc_macro::Level::Warning,
                "this suppresses the borrow checker in an unsafe, unsound, and unstable way \
                that produces undefined behaviour. this is not suitable for any purpose beyond \
                educational experimentation.",
            ).emit();

            if suppressor.suppressed_references.len() > 1 {
                proc_macro::Diagnostic::spanned(
                    suppressor.suppressed_references,
                    proc_macro::Level::Warning,
                    "the borrow checker is suppressed for these references.",
                ).emit();
            }

            output.into_token_stream().into()
        } else {
            static DANGER: std::sync::Once = std::sync::Once::new();
            DANGER.call_once(|| {
                eprintln!();
                eprintln!(" DANGER   This project is using the the #[you_can::turn_off_the_borrow_checker]");
                eprintln!(" DANGER   macro, which is inherently unsafe, unsound, and unstable. This is not");
                eprintln!(" DANGER   suitable for any purpose beyond educational experimentation.");
                eprintln!();
            });

            quote_spanned! {
                Span::call_site().into() =>
                #[warn(unsafe_code)]
                #output
            }.into_token_stream().into()
        }
    }
}

/// Replaces all references (&T or &mut T) with unbounded references by wrapping
/// them in calls to you_can::borrow_unchecked().
#[derive(Debug, Default)]
struct BorrowCheckerSuppressor {
    suppressed_references: Vec<Span>,
}

impl Fold for BorrowCheckerSuppressor {
    fn fold_expr(&mut self, node: syn::Expr) -> syn::Expr {
        match node {
            syn::Expr::Reference(node) => {
                let node = syn::fold::fold_expr_reference(self, node);
                self.suppressed_references.push(node.span().unwrap());
                syn::Expr::Block(parse_quote_spanned! { node.span() =>
                    {
                        let r#ref = #node;
                        unsafe { ::you_can::borrow_unchecked(r#ref) }
                    }
                })
            },
            _ => syn::fold::fold_expr(self, node),
        }
    }

    fn fold_expr_if(&mut self, mut node: syn::ExprIf) -> syn::ExprIf {
        if matches!(*node.cond, syn::Expr::Let(_)) {
            let mut ref_collector = RefCollector::default();
            ref_collector.visit_expr(&node.cond);
            let refs = ref_collector.refs;
            self.suppressed_references.extend(ref_collector.spans);
            let then_stmts = node.then_branch.stmts.clone();
            node.then_branch = parse_quote_spanned! { node.span() =>
                {
                    #(let #refs = unsafe { ::you_can::borrow_unchecked(#refs) };)*
                    #(#then_stmts)*
                }
            };
        }
        syn::fold::fold_expr_if(self, node)
    }

    fn fold_arm(&mut self, mut node: syn::Arm) -> syn::Arm {
        let mut ref_collector = RefCollector::default();
        ref_collector.visit_pat(&node.pat);
        let refs = ref_collector.refs;
        self.suppressed_references.extend(ref_collector.spans);
        let body = node.body.clone();
        node.body = parse_quote_spanned! { node.span() =>
            {
                #(let #refs = unsafe { ::you_can::borrow_unchecked(#refs) };)*
                #body
            }
        };
        syn::fold::fold_arm(self, node)
    }
}

#[derive(Debug, Default)]
struct RefCollector {
    refs: Vec<syn::Ident>,
    spans: Vec<Span>,
}

impl<'ast> Visit<'ast> for RefCollector {
    fn visit_pat_ident(&mut self, node: &'ast syn::PatIdent) {
        if node.by_ref.is_some() {
            self.refs.push(node.ident.clone());
            self.spans.push(node.span().unwrap());
        }
    }
}