Add lints to catch unmanaged state and unmounted routes.

* The `unmanaged_state` lint emits a warning when a `State<T>` request
    guard is used without an accompanying `manage` call for `T`.

  * The `unmounted_route` lint emits a warning when a route declared via
    a Rocket attribute is not mounted via a call to `mount`.

There is one known shortcoming of these lints at present: _any_ call to
`manage` or `mount` marks state/routes as managed/mounted. This can be
an issue when an application uses more than one `Rocket` instance, with
different calls to `mount` and `manage` in each. The lints should
perform their analyses on a per-instance basis.
This commit is contained in:
Sergio Benitez 2017-01-29 01:13:52 -08:00
parent 5f04beaafc
commit 4eaf9ba9c5
8 changed files with 500 additions and 36 deletions

View File

@ -1,12 +1,12 @@
use ::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX, CATCHER_ATTR};
use parser::ErrorParams;
use utils::*; use utils::*;
use ::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX};
use syntax::codemap::{Span}; use syntax::codemap::{Span};
use syntax::ast::{MetaItem, Ident, TyKind}; use syntax::ast::{MetaItem, Ident, TyKind};
use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::tokenstream::TokenTree; use syntax::tokenstream::TokenTree;
use syntax::parse::token; use syntax::parse::token;
use parser::ErrorParams;
const ERR_PARAM: &'static str = "_error"; const ERR_PARAM: &'static str = "_error";
const REQ_PARAM: &'static str = "_request"; const REQ_PARAM: &'static str = "_request";
@ -46,17 +46,27 @@ impl ErrorGenerateExt for ErrorParams {
} }
} }
pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, pub fn error_decorator(ecx: &mut ExtCtxt,
annotated: &Annotatable, push: &mut FnMut(Annotatable)) { sp: Span,
let error = ErrorParams::from(ecx, sp, meta_item, annotated); meta_item: &MetaItem,
annotated: Annotatable)
-> Vec<Annotatable>
{
let mut output = Vec::new();
// Parse the parameters from the error annotation.
let error = ErrorParams::from(ecx, sp, meta_item, &annotated);
// Get all of the information we learned from the attribute + function.
let user_fn_name = error.annotated_fn.ident(); let user_fn_name = error.annotated_fn.ident();
let catch_fn_name = user_fn_name.prepend(CATCH_FN_PREFIX); let catch_fn_name = user_fn_name.prepend(CATCH_FN_PREFIX);
let code = error.code.node; let code = error.code.node;
let (err_ident, req_ident) = (Ident::from_str(ERR_PARAM), Ident::from_str(REQ_PARAM)); let err_ident = Ident::from_str(ERR_PARAM);
let req_ident = Ident::from_str(REQ_PARAM);
let fn_arguments = error.generate_fn_arguments(ecx, err_ident, req_ident); let fn_arguments = error.generate_fn_arguments(ecx, err_ident, req_ident);
emit_item(push, quote_item!(ecx, // Push the Rocket generated catch function.
emit_item(&mut output, quote_item!(ecx,
fn $catch_fn_name<'_b>($err_ident: ::rocket::Error, fn $catch_fn_name<'_b>($err_ident: ::rocket::Error,
$req_ident: &'_b ::rocket::Request) $req_ident: &'_b ::rocket::Request)
-> ::rocket::response::Result<'_b> { -> ::rocket::response::Result<'_b> {
@ -67,8 +77,9 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
} }
).expect("catch function")); ).expect("catch function"));
// Push the static catch info. This is what the errors! macro refers to.
let struct_name = user_fn_name.prepend(CATCH_STRUCT_PREFIX); let struct_name = user_fn_name.prepend(CATCH_STRUCT_PREFIX);
emit_item(push, quote_item!(ecx, emit_item(&mut output, quote_item!(ecx,
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub static $struct_name: ::rocket::StaticCatchInfo = pub static $struct_name: ::rocket::StaticCatchInfo =
::rocket::StaticCatchInfo { ::rocket::StaticCatchInfo {
@ -76,4 +87,11 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
handler: $catch_fn_name handler: $catch_fn_name
}; };
).expect("catch info struct")); ).expect("catch info struct"));
// Attach a `rocket_catcher` attribute to the user's function and emit it.
let attr_name = Ident::from_str(CATCHER_ATTR);
let catcher_attr = quote_attr!(ecx, #[$attr_name($struct_name)]);
attach_and_emit(&mut output, catcher_attr, annotated);
output
} }

View File

@ -2,9 +2,9 @@ use std::collections::HashSet;
use std::fmt::Display; use std::fmt::Display;
use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX}; use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX};
use utils::{emit_item, span, sep_by_tok, option_as_expr, strip_ty_lifetimes}; use ::{ROUTE_ATTR, ROUTE_INFO_ATTR};
use utils::{SpanExt, IdentExt, ArgExt};
use parser::{Param, RouteParams}; use parser::{Param, RouteParams};
use utils::*;
use syntax::codemap::{Span, Spanned}; use syntax::codemap::{Span, Spanned};
use syntax::tokenstream::TokenTree; use syntax::tokenstream::TokenTree;
@ -169,7 +169,7 @@ impl RouteGenerateExt for RouteParams {
!a.named(&p.node.name) !a.named(&p.node.name)
}) })
} else { } else {
ecx.span_err(a.pat.span, "argument names must be identifiers"); ecx.span_err(a.pat.span, "route argument names must be identifiers");
false false
} }
}; };
@ -180,6 +180,7 @@ impl RouteGenerateExt for RouteParams {
let ident = arg.ident().unwrap().prepend(PARAM_PREFIX); let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
let ty = strip_ty_lifetimes(arg.ty.clone()); let ty = strip_ty_lifetimes(arg.ty.clone());
fn_param_statements.push(quote_stmt!(ecx, fn_param_statements.push(quote_stmt!(ecx,
#[allow(non_snake_case)]
let $ident: $ty = match let $ident: $ty = match
::rocket::request::FromRequest::from_request(_req) { ::rocket::request::FromRequest::from_request(_req) {
::rocket::outcome::Outcome::Success(v) => v, ::rocket::outcome::Outcome::Success(v) => v,
@ -220,10 +221,13 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
ecx: &mut ExtCtxt, ecx: &mut ExtCtxt,
sp: Span, sp: Span,
meta_item: &MetaItem, meta_item: &MetaItem,
annotated: &Annotatable, annotated: Annotatable)
push: &mut FnMut(Annotatable)) { -> Vec<Annotatable>
{
let mut output = Vec::new();
// Parse the route and generate the code to create the form and param vars. // Parse the route and generate the code to create the form and param vars.
let route = RouteParams::from(ecx, sp, known_method, meta_item, annotated); let route = RouteParams::from(ecx, sp, known_method, meta_item, &annotated);
debug!("Route params: {:?}", route); debug!("Route params: {:?}", route);
let param_statements = route.generate_param_statements(ecx); let param_statements = route.generate_param_statements(ecx);
@ -234,7 +238,7 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
// Generate and emit the wrapping function with the Rocket handler signature. // Generate and emit the wrapping function with the Rocket handler signature.
let user_fn_name = route.annotated_fn.ident(); let user_fn_name = route.annotated_fn.ident();
let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX); let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX);
emit_item(push, quote_item!(ecx, emit_item(&mut output, quote_item!(ecx,
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data) fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
-> ::rocket::handler::Outcome<'_b> { -> ::rocket::handler::Outcome<'_b> {
$param_statements $param_statements
@ -249,7 +253,7 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
// function as its handler. A proper Rocket route will be created from this. // function as its handler. A proper Rocket route will be created from this.
let struct_name = user_fn_name.prepend(ROUTE_STRUCT_PREFIX); let struct_name = user_fn_name.prepend(ROUTE_STRUCT_PREFIX);
let (path, method, content_type, rank) = route.explode(ecx); let (path, method, content_type, rank) = route.explode(ecx);
emit_item(push, quote_item!(ecx, let static_route_info_item = quote_item!(ecx,
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub static $struct_name: ::rocket::StaticRouteInfo = pub static $struct_name: ::rocket::StaticRouteInfo =
::rocket::StaticRouteInfo { ::rocket::StaticRouteInfo {
@ -259,24 +263,35 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
format: $content_type, format: $content_type,
rank: $rank, rank: $rank,
}; };
).unwrap()); ).expect("static route info");
// Attach a `rocket_route_info` attribute to the route info and emit it.
let attr_name = Ident::from_str(ROUTE_INFO_ATTR);
let info_attr = quote_attr!(ecx, #[$attr_name]);
attach_and_emit(&mut output, info_attr, Annotatable::Item(static_route_info_item));
// Attach a `rocket_route` attribute to the user's function and emit it.
let attr_name = Ident::from_str(ROUTE_ATTR);
let route_attr = quote_attr!(ecx, #[$attr_name($struct_name)]);
attach_and_emit(&mut output, route_attr, annotated);
output
} }
pub fn route_decorator(ecx: &mut ExtCtxt, pub fn route_decorator(
sp: Span, ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: Annotatable
meta_item: &MetaItem, ) -> Vec<Annotatable> {
annotated: &Annotatable, generic_route_decorator(None, ecx, sp, meta_item, annotated)
push: &mut FnMut(Annotatable)) {
generic_route_decorator(None, ecx, sp, meta_item, annotated, push);
} }
macro_rules! method_decorator { macro_rules! method_decorator {
($name:ident, $method:ident) => ( ($name:ident, $method:ident) => (
pub fn $name(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, pub fn $name(
annotated: &Annotatable, push: &mut FnMut(Annotatable)) { ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: Annotatable
) -> Vec<Annotatable> {
let i_sp = meta_item.span.shorten_to(stringify!($method).len()); let i_sp = meta_item.span.shorten_to(stringify!($method).len());
let method = Some(span(Method::$method, i_sp)); let method = Some(span(Method::$method, i_sp));
generic_route_decorator(method, ecx, sp, meta_item, annotated, push); generic_route_decorator(method, ecx, sp, meta_item, annotated)
} }
) )
} }

View File

@ -97,9 +97,9 @@
#![allow(deprecated)] #![allow(deprecated)]
#[macro_use] extern crate log; #[macro_use] extern crate log;
#[macro_use] extern crate rustc;
extern crate syntax; extern crate syntax;
extern crate syntax_ext; extern crate syntax_ext;
extern crate rustc;
extern crate rustc_plugin; extern crate rustc_plugin;
extern crate rocket; extern crate rocket;
@ -107,6 +107,7 @@ extern crate rocket;
mod parser; mod parser;
mod macros; mod macros;
mod decorators; mod decorators;
mod lints;
use std::env; use std::env;
use rustc_plugin::Registry; use rustc_plugin::Registry;
@ -121,10 +122,15 @@ const CATCH_STRUCT_PREFIX: &'static str = "static_rocket_catch_info_for_";
const ROUTE_FN_PREFIX: &'static str = "rocket_route_fn_"; const ROUTE_FN_PREFIX: &'static str = "rocket_route_fn_";
const CATCH_FN_PREFIX: &'static str = "rocket_catch_fn_"; const CATCH_FN_PREFIX: &'static str = "rocket_catch_fn_";
const ROUTE_ATTR: &'static str = "rocket_route";
const ROUTE_INFO_ATTR: &'static str = "rocket_route_info";
const CATCHER_ATTR: &'static str = "rocket_catcher";
macro_rules! register_decorators { macro_rules! register_decorators {
($registry:expr, $($name:expr => $func:ident),+) => ( ($registry:expr, $($name:expr => $func:ident),+) => (
$($registry.register_syntax_extension(Symbol::intern($name), $($registry.register_syntax_extension(Symbol::intern($name),
SyntaxExtension::MultiDecorator(Box::new(decorators::$func))); SyntaxExtension::MultiModifier(Box::new(decorators::$func)));
)+ )+
) )
} }
@ -137,6 +143,12 @@ macro_rules! register_derives {
) )
} }
macro_rules! register_lints {
($registry:expr, $($item:ident),+) => ($(
$registry.register_late_lint_pass(Box::new(lints::$item::default()));
)+)
}
/// Compiler hook for Rust to register plugins. /// Compiler hook for Rust to register plugins.
#[plugin_registrar] #[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) { pub fn plugin_registrar(reg: &mut Registry) {
@ -164,4 +176,6 @@ pub fn plugin_registrar(reg: &mut Registry) {
// TODO: Allow this once Diesel incompatibility is fixed. Fix docs too. // TODO: Allow this once Diesel incompatibility is fixed. Fix docs too.
// "options" => options_decorator // "options" => options_decorator
); );
register_lints!(reg, ManagedStateLint);
} }

221
codegen/src/lints/mod.rs Normal file
View File

@ -0,0 +1,221 @@
extern crate syntax_pos;
mod utils;
use self::utils::*;
use ::{ROUTE_ATTR, ROUTE_INFO_ATTR};
use std::mem::transmute;
use std::collections::{HashSet, HashMap};
use rustc::lint::Level;
use rustc::lint::{LateContext, LintContext, LintPass, LateLintPass, LintArray};
use rustc::hir::{Item, Expr, Crate};
use rustc::hir::def::Def;
use rustc::hir::def_id::DefId;
use rustc::ty::Ty;
use rustc::hir::intravisit::{FnKind};
use rustc::hir::{FnDecl, Body};
use rustc::hir::Ty_::TyPath;
use self::syntax_pos::Span;
use syntax::symbol::Symbol as Name;
use syntax::ast::NodeId;
const STATE_TYPE: &'static [&'static str] = &["rocket", "request", "state", "State"];
#[derive(Debug, Default)]
pub struct ManagedStateLint {
// All of the types that were requested as managed state.
// (fn_name, fn_span, info_struct_def_id, req_type, req_param_span)
requested: Vec<(Name, Span, DefId, Ty<'static>, Span)>,
// The DefId of all of the route infos for the mounted routes.
mounted: HashSet<DefId>,
// The names of all of the routes that were declared.
info_structs: HashMap<Name, DefId>,
// The name, span, and info DefId for all declared route functions.
declared: Vec<(Name, Span, DefId)>,
// The expressions that were passed into a `.manage` call.
managed: Vec<(Ty<'static>, Span)>,
// Span for rocket::ignite() or rocket::custom().
start_call: Vec<Span>,
}
declare_lint!(UNMOUNTED_ROUTE, Warn, "Warn on routes that are unmounted.");
declare_lint!(UNMANAGED_STATE, Warn, "Warn on declared use on unmanaged state.");
impl<'tcx> LintPass for ManagedStateLint {
fn get_lints(&self) -> LintArray {
lint_array!(UNMANAGED_STATE, UNMOUNTED_ROUTE)
}
}
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ManagedStateLint {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
if let Some(args) = rocket_method_call("manage", cx, expr) {
let expr = &args[0];
if let Some(ty) = cx.tables.expr_ty_opt(expr) {
let casted = unsafe { transmute(ty) };
self.managed.push((casted, expr.span));
}
}
if let Some(args) = rocket_method_call("mount", cx, expr) {
for def_id in extract_mount_fn_def_ids(cx, &args[1]) {
self.mounted.insert(def_id);
}
}
if is_rocket_start_call(cx, expr) {
self.start_call.push(expr.span);
}
}
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
// Return early if this is not a route info structure.
if !item.attrs.iter().any(|attr| attr.check_name(ROUTE_INFO_ATTR)) {
return;
}
if let Some(def_id) = cx.tcx.map.opt_local_def_id(item.id) {
self.info_structs.insert(item.name, def_id);
}
}
fn check_fn(&mut self,
cx: &LateContext<'a, 'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl,
_: &'tcx Body,
fn_sp: Span,
fn_id: NodeId)
{
// Get the name of the function, if any.
let fn_name = match kind {
FnKind::ItemFn(name, ..) => name,
_ => return
};
// Figure out if this is a route function by trying to find the
// `ROUTE_ATTR` attribute and extracing the info struct's name from it.
let attr_value = kind.attrs().iter().filter_map(|attr| {
if !attr.check_name(ROUTE_ATTR) {
None
} else {
attr.value.meta_item_list().and_then(|list| list[0].name())
}
}).next();
// Try to get the DEF_ID using the info struct's name. Return early if
// anything goes awry.
let def_id = match attr_value {
Some(val) if self.info_structs.contains_key(&val) => {
self.info_structs.get(&val).unwrap()
}
_ => return
};
// Add this to the list of declared routes to check for mounting later
// unless unmounted routes were explicitly allowed for this function.
if cx.current_level(UNMOUNTED_ROUTE) != Level::Allow {
self.declared.push((fn_name, fn_sp, def_id.clone()));
}
// If unmanaged state was explicitly allowed for this function, don't
// record any additional information. Just return now.
if cx.current_level(UNMANAGED_STATE) == Level::Allow {
return;
}
// Collect all of the `State` types into `tys`.
let mut tys: Vec<Ty<'static>> = vec![];
if let Some(sig) = cx.tables.liberated_fn_sigs.get(&fn_id) {
for input_ty in sig.inputs() {
let def_id = match input_ty.ty_to_def_id() {
Some(id) => id,
None => continue
};
if !match_def_path(cx.tcx, def_id, STATE_TYPE) {
continue;
}
if let Some(inner_type) = input_ty.walk_shallow().next() {
let casted = unsafe { transmute(inner_type) };
tys.push(casted);
}
}
}
// Collect all of the spans for the `State` parameters.
let mut spans = vec![];
for input in decl.inputs.iter() {
let id = input.id;
if let TyPath(ref qpath) = input.node {
if let Def::Struct(defid) = cx.tables.qpath_def(qpath, id) {
if match_def_path(cx.tcx, defid, STATE_TYPE) {
spans.push(input.span);
}
}
}
}
// Sanity check: we should have as many spans as types.
if tys.len() != spans.len() {
panic!("Internal lint error: mismatched type/spans: {}/{}",
tys.len(), spans.len());
}
// Insert the information we've collected.
for (ty, span) in tys.into_iter().zip(spans.into_iter()) {
self.requested.push((fn_name, fn_sp, def_id.clone(), ty, span));
}
}
fn check_crate_post(&mut self, cx: &LateContext<'a, 'tcx>, _: &'tcx Crate) {
// Record the start function span if we found one, and only one.
// TODO: Try to find the _right_ one using some heuristics.
let start_call_sp = match self.start_call.len() == 1 {
true => Some(self.start_call[0]),
false => None
};
// Emit a warning for all unmounted, declared routes.
for &(route_name, fn_sp, info_def_id) in self.declared.iter() {
if !self.mounted.contains(&info_def_id) {
let msg = format!("the '{}' route is not mounted", route_name);
let mut b = cx.struct_span_lint(UNMOUNTED_ROUTE, fn_sp, &msg);
b.note("Rocket will not dispatch requests to unmounted routes");
if let Some(start_sp) = start_call_sp {
b.span_help(start_sp, "maybe missing a call to 'mount' here?");
}
b.emit();
}
}
let managed_types: HashSet<Ty> = self.managed.iter()
.map(|&(ty, _)| ty)
.collect();
for &(_, _, info_def_id, ty, sp) in self.requested.iter() {
// Don't warn on unmounted routes.
if !self.mounted.contains(&info_def_id) {
continue
}
if !managed_types.contains(&ty) {
let m = format!("'{}' is not currently being managed by Rocket", ty);
let mut b = cx.struct_span_lint(UNMANAGED_STATE, sp, &m);
b.note("this 'State' request guard will always fail");
if let Some(start_sp) = start_call_sp {
let msg = format!("maybe missing a call to 'manage' here?");
b.span_help(start_sp, &msg);
}
b.emit()
}
}
}
}

127
codegen/src/lints/utils.rs Normal file
View File

@ -0,0 +1,127 @@
use rustc::ty;
use rustc::hir::def_id::DefId;
use rustc::lint::LateContext;
use rustc::hir::Expr_::*;
use rustc::hir::Expr;
use syntax::symbol;
const ROCKET_TYPE: &'static [&'static str] = &["rocket", "rocket", "Rocket"];
const ROCKET_IGNITE_FN: &'static [&'static str] = &["rocket", "ignite"];
const ROCKET_IGNITE_STATIC: &'static [&'static str]
= &["rocket", "rocket", "Rocket", "ignite"];
const ROCKET_CUSTOM_FN: &'static [&'static str] = &["rocket", "custom"];
const ROCKET_CUSTOM_STATIC: &'static [&'static str]
= &["rocket", "rocket", "Rocket", "custom"];
const ABSOLUTE: &'static ty::item_path::RootMode = &ty::item_path::RootMode::Absolute;
/// Check if a `DefId`'s path matches the given absolute type path usage.
///
/// # Examples
/// ```rust,ignore
/// match_def_path(cx.tcx, id, &["core", "option", "Option"])
/// ```
///
/// See also the `paths` module.
pub fn match_def_path(tcx: ty::TyCtxt, def_id: DefId, path: &[&str]) -> bool {
struct AbsolutePathBuffer {
names: Vec<symbol::InternedString>,
}
impl ty::item_path::ItemPathBuffer for AbsolutePathBuffer {
fn root_mode(&self) -> &ty::item_path::RootMode {
ABSOLUTE
}
fn push(&mut self, text: &str) {
self.names.push(symbol::Symbol::intern(text).as_str());
}
}
let mut apb = AbsolutePathBuffer { names: vec![] };
tcx.push_item_path(&mut apb, def_id);
apb.names.len() == path.len() &&
apb.names.iter().zip(path.iter()).all(|(a, &b)| &**a == b)
}
/// Check if the method call given in `expr` belongs to given type.
pub fn is_impl_method(cx: &LateContext, expr: &Expr, path: &[&str]) -> bool {
let method_call = ty::MethodCall::expr(expr.id);
let trt_id = cx.tables
.method_map
.get(&method_call)
.and_then(|callee| cx.tcx.impl_of_method(callee.def_id));
if let Some(trt_id) = trt_id {
match_def_path(cx.tcx, trt_id, path)
} else {
false
}
}
pub fn rocket_method_call<'e>(
method: &str, cx: &LateContext, expr: &'e Expr
) -> Option<&'e [Expr]> {
if let ExprMethodCall(ref name, _, ref exprs) = expr.node {
if &*name.node.as_str() == method && is_impl_method(cx, expr, ROCKET_TYPE) {
return Some(&exprs[1..]);
}
}
None
}
pub fn is_rocket_start_call(cx: &LateContext, expr: &Expr) -> bool {
if let ExprCall(ref expr, ..) = expr.node {
if let ExprPath(ref qpath) = expr.node {
let def_id = cx.tables.qpath_def(qpath, expr.id).def_id();
if match_def_path(cx.tcx, def_id, ROCKET_IGNITE_FN) {
return true
} else if match_def_path(cx.tcx, def_id, ROCKET_IGNITE_STATIC) {
return true
} else if match_def_path(cx.tcx, def_id, ROCKET_CUSTOM_FN) {
return true
} else if is_impl_method(cx, expr, ROCKET_CUSTOM_STATIC) {
return true
}
}
}
false
}
pub fn extract_mount_fn_def_ids(cx: &LateContext, expr: &Expr) -> Vec<DefId> {
let mut output = Vec::new();
// Call to into_vec
if let ExprCall(_, ref args) = expr.node {
if let Some(&ExprBox(ref expr)) = args.iter().next().map(|e| &e.node) {
// Array of routes.
if let ExprArray(ref members) = expr.node {
for expr in members.iter() {
// Route::From call
if let ExprCall(_, ref args) = expr.node {
if args.len() < 1 {
continue;
}
// address of info struct
if let ExprAddrOf(_, ref expr) = args[0].node {
// path to info_struct
if let ExprPath(ref qpath) = expr.node {
let def = cx.tables.qpath_def(qpath, expr.id);
output.push(def.def_id());
}
}
}
}
}
}
}
output
}

View File

@ -12,6 +12,7 @@ pub use self::span_ext::SpanExt;
use std::convert::AsRef; use std::convert::AsRef;
use syntax;
use syntax::parse::token::Token; use syntax::parse::token::Token;
use syntax::tokenstream::TokenTree; use syntax::tokenstream::TokenTree;
use syntax::ast::{Item, Expr}; use syntax::ast::{Item, Expr};
@ -21,14 +22,13 @@ use syntax::ext::quote::rt::ToTokens;
use syntax::print::pprust::item_to_string; use syntax::print::pprust::item_to_string;
use syntax::ptr::P; use syntax::ptr::P;
use syntax::fold::Folder; use syntax::fold::Folder;
use syntax::ast::{Lifetime, LifetimeDef, Ty}; use syntax::ast::{Attribute, Lifetime, LifetimeDef, Ty};
use syntax::attr::HasAttrs;
#[inline]
pub fn span<T>(t: T, span: Span) -> Spanned<T> { pub fn span<T>(t: T, span: Span) -> Spanned<T> {
spanned(span.lo, span.hi, t) spanned(span.lo, span.hi, t)
} }
#[inline]
pub fn sep_by_tok<T>(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec<TokenTree> pub fn sep_by_tok<T>(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec<TokenTree>
where T: ToTokens where T: ToTokens
{ {
@ -43,7 +43,6 @@ pub fn sep_by_tok<T>(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec<TokenTree
output output
} }
#[inline]
pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> { pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> {
match *opt { match *opt {
Some(ref item) => quote_expr!(ecx, Some($item)), Some(ref item) => quote_expr!(ecx, Some($item)),
@ -51,10 +50,24 @@ pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> {
} }
} }
#[inline] pub fn emit_item(items: &mut Vec<Annotatable>, item: P<Item>) {
pub fn emit_item(push: &mut FnMut(Annotatable), item: P<Item>) { debug!("Emitting item:\n{}", item_to_string(&item));
debug!("Emitting item: {}", item_to_string(&item)); items.push(Annotatable::Item(item));
push(Annotatable::Item(item)); }
pub fn attach_and_emit(out: &mut Vec<Annotatable>, attr: Attribute, to: Annotatable) {
syntax::attr::mark_used(&attr);
syntax::attr::mark_known(&attr);
// Attach the attribute to the user's function and emit it.
if let Annotatable::Item(user_item) = to {
let item = user_item.map_attrs(|mut attrs| {
attrs.push(attr);
attrs
});
emit_item(out, item);
}
} }
macro_rules! quote_enum { macro_rules! quote_enum {

View File

@ -0,0 +1,55 @@
#![feature(plugin)]
#![plugin(rocket_codegen)]
#![allow(dead_code)]
#![deny(unmanaged_state)]
extern crate rocket;
use rocket::State;
struct MyType;
struct MySecondType;
mod external {
#[get("/state/extern")]
fn unmanaged(_c: ::State<i32>) { }
//~^ ERROR not currently being managed
#[get("/state/extern")]
fn managed(_c: ::State<u32>) { }
//~^ WARN is not mounted
#[get("/state/extern")]
fn unmanaged_unmounted(_c: ::State<u8>) { }
//~^ WARN is not mounted
}
#[get("/state/bad")]
fn unmanaged(_b: State<MySecondType>) { }
//~^ ERROR not currently being managed
#[get("/state/ok")]
fn managed(_a: State<u32>) { }
#[get("/state/bad")]
fn managed_two(_b: State<MyType>) { }
#[get("/state/ok")]
fn unmounted_doesnt_error(_a: State<i8>) { }
//~^ WARN is not mounted
#[get("/ignored")]
#[allow(unmanaged_state)]
fn ignored(_b: State<u16>) { }
#[get("/unmounted/ignored")]
#[allow(unmounted_route)]
fn unmounted_ignored() { }
fn main() {
rocket::ignite()
.mount("/", routes![managed, unmanaged, external::unmanaged])
.mount("/", routes![managed_two, ignored])
.manage(MyType)
.manage(100u32);
}

View File

@ -4,6 +4,7 @@
extern crate rocket; extern crate rocket;
#[get("/")] #[get("/")]
#[allow(unmounted_route)]
pub fn hello() -> &'static str { pub fn hello() -> &'static str {
"Hello, world!" "Hello, world!"
} }