mirror of https://github.com/rwf2/Rocket.git
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:
parent
5f04beaafc
commit
4eaf9ba9c5
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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!"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue