140: fix soundness issue: forbid early returns in init r=japaric a=japaric

TL;DR

1. v0.4.1 will be published once this PR lands

2. v0.4.0 will be yanked once v0.4.1 is out

3. v0.4.1 will reject code that contains early returns in `init` *and* contains
   late resources. Yes, this is a breaking change but such code is unsound /
   has undefined behavior.

4. as of v0.4.1 users are encouraged to use `fn init() -> init::LateResources`
   instead of `fn init()` when they make use of late resources.

---

This PR fixes a soundness issue reported by @RalfJung. Basically, early
returning from `init` leaves *late resources* (runtime initialized statics)
uninitialized, and this produces undefined behavior as tasks rely on those
statics being initialized. The example below showcases a program that runs into
this soundness issue.

``` rust
 #[rtfm::app(device = lm3s6965)]
const APP: () = {
    // this is actually `static mut UNINITIALIZED: MaybeUninit<bool> = ..`
    static mut UNINITIALIZED: bool = ();

    #[init]
    fn init() {
        // early return
        return;

        // this is translated into `UNINITIALIZED.set(true)`
        UNINITIALIZED = true; // the DSL forces you to write this at the end
    }

    #[interrupt(resources = [UNINITIALIZED])]
    fn UART0() {
        // `resources.UNINITIALIZED` is basically `UNINITIALIZED.get_mut()`

        if resources.UNINITIALIZED {
            // undefined behavior
        }
    }
};
```

The fix consists of two parts. The first part is producing a compiler error
whenever the `app` procedural macro finds a `return` expression in `init`. This
covers most cases, except for macros (e.g. `ret!()` expands into `return`) which
cannot be instrospected by procedural macros. This fix is technically a
breaking change (though unlikely to affect real code out there) but as per our
SemVer policy (which follows rust-lang/rust's) we are allowed to make breaking
changes to fix soundness bugs.

The second part of the fix consists of extending the `init` syntax to let the
user return the initial values of late resources in a struct. Namely, `fn() ->
init::LateResources` will become a valid signature for `init` (we allowed this
signature back in v0.3.x). Thus the problematic code shown above can be
rewritten as:

``` rust
 #[rtfm::app(device = lm3s6965)]
const APP: () = {
    static mut UNINITIALIZED: bool = ();

    #[init]
    fn init() -> init::LateResources {
        // rejected by the compiler
        // return; //~ ERROR expected `init::LateResources`, found `()`

        // initialize late resources
        init::LateResources {
            UNINITIALIZED: true,
        }
    }

    #[interrupt(resources = [UNINITIALIZED])]
    fn UART0() {
        if resources.UNINITIALIZED {
            // OK
        }
    }
};
```

Attempting to early return without giving the initial values for late resources
will produce a compiler error.

~~Additionally, we'll emit warnings if the `init: fn()` signature is used to
encourage users to switch to the alternative `init: fn() -> init::LateResources`
signature.~~ Turns out we can't do this on stable. Bummer.

The book and examples have been updated to make use of `init::LateResources`.

In the next minor version release we'll reject `fn init()` if late resources
are declared. `fn init() -> init::LateResources` will become the only way to
initialize late resources.

This PR also prepares release v0.4.1. Once that version is published the unsound
version v0.4.0 will be yanked.


Co-authored-by: Jorge Aparicio <jorge@japaric.io>
This commit is contained in:
bors[bot] 2019-02-12 14:28:42 +00:00
commit ed6460f6dc
20 changed files with 549 additions and 57 deletions

View file

@ -5,10 +5,35 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] ## [Unreleased]
## [v0.4.1] - 2019-02-12
### Added
- The RTFM book has been translated to Russian. You can find the translation
online at https://japaric.github.io/cortex-m-rtfm/book/ru/
- `Duration` now implements the `Default` trait.
### Changed
- [breaking-change][] [soundness-fix] `init` can not contain any early return as
that would result in late resources not being initialized and thus undefined
behavior.
- Use an absolute link to the book so it works when landing from crates.io - Use an absolute link to the book so it works when landing from crates.io
documentation page documentation page
## [v0.4.0] - 2018-11-03 - The initialization function can now be written as `fn init() ->
init::LateResources` when late resources are used. This is preferred over the
old `fn init()` form.
### Fixed
- `#[interrupt]` and `#[exception]` no longer produce warnings on recent nightlies.
## [v0.4.0] - 2018-11-03 - YANKED
Yanked due to a soundness issue in `init`; the issue has been mostly fixed in v0.4.1.
### Changed ### Changed
@ -150,7 +175,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Initial release - Initial release
[Unreleased]: https://github.com/japaric/cortex-m-rtfm/compare/v0.4.0...HEAD [Unreleased]: https://github.com/japaric/cortex-m-rtfm/compare/v0.4.1...HEAD
[v0.4.1]: https://github.com/japaric/cortex-m-rtfm/compare/v0.4.0...v0.4.1
[v0.4.0]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.4...v0.4.0 [v0.4.0]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.4...v0.4.0
[v0.3.4]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.3...v0.3.4 [v0.3.4]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.3...v0.3.4
[v0.3.3]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.2...v0.3.3 [v0.3.3]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.2...v0.3.3

View file

@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0"
name = "cortex-m-rtfm" name = "cortex-m-rtfm"
readme = "README.md" readme = "README.md"
repository = "https://github.com/japaric/cortex-m-rtfm" repository = "https://github.com/japaric/cortex-m-rtfm"
version = "0.4.0" version = "0.4.1"
[lib] [lib]
name = "rtfm" name = "rtfm"

View file

@ -78,8 +78,8 @@ runtime initialized resources as *late resources*. Late resources are useful for
interrupt and exception handlers. interrupt and exception handlers.
Late resources are declared like normal resources but that are given an initial Late resources are declared like normal resources but that are given an initial
value of `()` (the unit value). Late resources must be initialized at the end of value of `()` (the unit value). `init` must return the initial values of all
the `init` function using plain assignments (e.g. `FOO = 1`). late resources packed in a `struct` of type `init::LateResources`.
The example below uses late resources to stablish a lockless, one-way channel The example below uses late resources to stablish a lockless, one-way channel
between the `UART0` interrupt handler and the `idle` function. A single producer between the `UART0` interrupt handler and the `idle` function. A single producer

View file

@ -7,6 +7,6 @@
Эта книга содержит документацию уровня пользователя фреймворком Real Time For the Masses Эта книга содержит документацию уровня пользователя фреймворком Real Time For the Masses
(RTFM). Описание API можно найти [здесь](../api/rtfm/index.html). (RTFM). Описание API можно найти [здесь](../api/rtfm/index.html).
{{#include ../..ADME_RU.md:5:54}} {{#include README_RU.md:5:44}}
{{#include ../..ADME_RU.md:60:}} {{#include README_RU.md:50:}}

View file

@ -22,7 +22,7 @@ const APP: () = {
static mut C: Consumer<'static, u32, U4> = (); static mut C: Consumer<'static, u32, U4> = ();
#[init] #[init]
fn init() { fn init() -> init::LateResources {
// NOTE: we use `Option` here to work around the lack of // NOTE: we use `Option` here to work around the lack of
// a stable `const` constructor // a stable `const` constructor
static mut Q: Option<Queue<u32, U4>> = None; static mut Q: Option<Queue<u32, U4>> = None;
@ -31,8 +31,7 @@ const APP: () = {
let (p, c) = Q.as_mut().unwrap().split(); let (p, c) = Q.as_mut().unwrap().split();
// Initialization of late resources // Initialization of late resources
P = p; init::LateResources { P: p, C: c }
C = c;
} }
#[idle(resources = [C])] #[idle(resources = [C])]

View file

@ -20,10 +20,12 @@ const APP: () = {
static mut P: Pool<M> = (); static mut P: Pool<M> = ();
#[init(resources = [M])] #[init(resources = [M])]
fn init() { fn init() -> init::LateResources {
rtfm::pend(Interrupt::I2C0); rtfm::pend(Interrupt::I2C0);
P = Pool::new(resources.M); init::LateResources {
P: Pool::new(resources.M),
}
} }
#[interrupt( #[interrupt(

View file

@ -16,11 +16,11 @@ const APP: () = {
static KEY: u32 = (); static KEY: u32 = ();
#[init] #[init]
fn init() { fn init() -> init::LateResources {
rtfm::pend(Interrupt::UART0); rtfm::pend(Interrupt::UART0);
rtfm::pend(Interrupt::UART1); rtfm::pend(Interrupt::UART1);
KEY = 0xdeadbeef; init::LateResources { KEY: 0xdeadbeef }
} }
#[interrupt(resources = [KEY])] #[interrupt(resources = [KEY])]

View file

@ -1,7 +1,7 @@
use std::{collections::HashSet, iter}; use std::{collections::HashSet, iter};
use proc_macro2::Span; use proc_macro2::Span;
use syn::parse; use syn::{parse, spanned::Spanned, Block, Expr, Stmt};
use crate::syntax::App; use crate::syntax::App;
@ -35,17 +35,20 @@ pub fn app(app: &App) -> parse::Result<()> {
} }
} }
// Check that all late resources have been initialized in `#[init]` // Check that all late resources have been initialized in `#[init]` if `init` has signature
for res in app // `fn()`
.resources if !app.init.returns_late_resources {
.iter() for res in app
.filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None }) .resources
{ .iter()
if app.init.assigns.iter().all(|assign| assign.left != *res) { .filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None })
return Err(parse::Error::new( {
res.span(), if app.init.assigns.iter().all(|assign| assign.left != *res) {
"late resources MUST be initialized at the end of `init`", return Err(parse::Error::new(
)); res.span(),
"late resources MUST be initialized at the end of `init`",
));
}
} }
} }
@ -112,5 +115,258 @@ pub fn app(app: &App) -> parse::Result<()> {
} }
} }
// Check that `init` contains no early returns *if* late resources exist and `init` signature is
// `fn()`
if app.resources.values().any(|res| res.expr.is_none()) {
if !app.init.returns_late_resources {
for stmt in &app.init.stmts {
noreturn_stmt(stmt)?;
}
}
} else if app.init.returns_late_resources {
return Err(parse::Error::new(
Span::call_site(),
"`init` signature must be `[unsafe] fn()` if there are no late resources",
));
}
Ok(())
}
// checks that the given block contains no instance of `return`
fn noreturn_block(block: &Block) -> Result<(), parse::Error> {
for stmt in &block.stmts {
noreturn_stmt(stmt)?;
}
Ok(())
}
// checks that the given statement contains no instance of `return`
fn noreturn_stmt(stmt: &Stmt) -> Result<(), parse::Error> {
match stmt {
// `let x = ..` -- this may contain a return in the RHS
Stmt::Local(local) => {
if let Some(ref init) = local.init {
noreturn_expr(&init.1)?
}
}
// items have no effect on control flow
Stmt::Item(..) => {}
Stmt::Expr(expr) => noreturn_expr(expr)?,
Stmt::Semi(expr, ..) => noreturn_expr(expr)?,
}
Ok(())
}
// checks that the given expression contains no `return`
fn noreturn_expr(expr: &Expr) -> Result<(), parse::Error> {
match expr {
Expr::Box(b) => noreturn_expr(&b.expr)?,
Expr::InPlace(ip) => {
noreturn_expr(&ip.place)?;
noreturn_expr(&ip.value)?;
}
Expr::Array(a) => {
for elem in &a.elems {
noreturn_expr(elem)?;
}
}
Expr::Call(c) => {
noreturn_expr(&c.func)?;
for arg in &c.args {
noreturn_expr(arg)?;
}
}
Expr::MethodCall(mc) => {
noreturn_expr(&mc.receiver)?;
for arg in &mc.args {
noreturn_expr(arg)?;
}
}
Expr::Tuple(t) => {
for elem in &t.elems {
noreturn_expr(elem)?;
}
}
Expr::Binary(b) => {
noreturn_expr(&b.left)?;
noreturn_expr(&b.right)?;
}
Expr::Unary(u) => {
noreturn_expr(&u.expr)?;
}
Expr::Lit(..) => {}
Expr::Cast(c) => {
noreturn_expr(&c.expr)?;
}
Expr::Type(t) => {
noreturn_expr(&t.expr)?;
}
Expr::Let(l) => {
noreturn_expr(&l.expr)?;
}
Expr::If(i) => {
noreturn_expr(&i.cond)?;
noreturn_block(&i.then_branch)?;
if let Some(ref e) = i.else_branch {
noreturn_expr(&e.1)?;
}
}
Expr::While(w) => {
noreturn_expr(&w.cond)?;
noreturn_block(&w.body)?;
}
Expr::ForLoop(fl) => {
noreturn_expr(&fl.expr)?;
noreturn_block(&fl.body)?;
}
Expr::Loop(l) => {
noreturn_block(&l.body)?;
}
Expr::Match(m) => {
noreturn_expr(&m.expr)?;
for arm in &m.arms {
if let Some(g) = &arm.guard {
noreturn_expr(&g.1)?;
}
noreturn_expr(&arm.body)?;
}
}
// we don't care about `return`s inside closures
Expr::Closure(..) => {}
Expr::Unsafe(u) => {
noreturn_block(&u.block)?;
}
Expr::Block(b) => {
noreturn_block(&b.block)?;
}
Expr::Assign(a) => {
noreturn_expr(&a.left)?;
noreturn_expr(&a.right)?;
}
Expr::AssignOp(ao) => {
noreturn_expr(&ao.left)?;
noreturn_expr(&ao.right)?;
}
Expr::Field(f) => {
noreturn_expr(&f.base)?;
}
Expr::Index(i) => {
noreturn_expr(&i.expr)?;
noreturn_expr(&i.index)?;
}
Expr::Range(r) => {
if let Some(ref f) = r.from {
noreturn_expr(f)?;
}
if let Some(ref t) = r.to {
noreturn_expr(t)?;
}
}
Expr::Path(..) => {}
Expr::Reference(r) => {
noreturn_expr(&r.expr)?;
}
Expr::Break(b) => {
if let Some(ref e) = b.expr {
noreturn_expr(e)?;
}
}
Expr::Continue(..) => {}
Expr::Return(r) => {
return Err(parse::Error::new(
r.span(),
"`init` is *not* allowed to early return",
));
}
// we can not analyze this
Expr::Macro(..) => {}
Expr::Struct(s) => {
for field in &s.fields {
noreturn_expr(&field.expr)?;
}
if let Some(ref rest) = s.rest {
noreturn_expr(rest)?;
}
}
Expr::Repeat(r) => {
noreturn_expr(&r.expr)?;
noreturn_expr(&r.len)?;
}
Expr::Paren(p) => {
noreturn_expr(&p.expr)?;
}
Expr::Group(g) => {
noreturn_expr(&g.expr)?;
}
Expr::Try(t) => {
noreturn_expr(&t.expr)?;
}
// we don't care about `return`s inside async blocks
Expr::Async(..) => {}
Expr::TryBlock(tb) => {
noreturn_block(&tb.block)?;
}
Expr::Yield(y) => {
if let Some(expr) = &y.expr {
noreturn_expr(expr)?;
}
}
// we can not analyze this
Expr::Verbatim(..) => {}
}
Ok(()) Ok(())
} }

View file

@ -94,7 +94,7 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis); let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis);
let init_fn = init(&mut ctxt, &app, analysis); let (init_fn, has_late_resources) = init(&mut ctxt, &app, analysis);
let init_arg = if cfg!(feature = "timer-queue") { let init_arg = if cfg!(feature = "timer-queue") {
quote!(rtfm::Peripherals { quote!(rtfm::Peripherals {
CBP: p.CBP, CBP: p.CBP,
@ -123,6 +123,30 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
}) })
}; };
let init = &ctxt.init;
let init_phase = if has_late_resources {
let assigns = app
.resources
.iter()
.filter_map(|(name, res)| {
if res.expr.is_none() {
let alias = &ctxt.statics[name];
Some(quote!(#alias.set(res.#name);))
} else {
None
}
})
.collect::<Vec<_>>();
quote!(
let res = #init(#init_arg);
#(#assigns)*
)
} else {
quote!(#init(#init_arg);)
};
let post_init = post_init(&ctxt, &app, analysis); let post_init = post_init(&ctxt, &app, analysis);
let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis); let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis);
@ -147,7 +171,6 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
let assertions = assertions(app, analysis); let assertions = assertions(app, analysis);
let main = mk_ident(None); let main = mk_ident(None);
let init = &ctxt.init;
quote!( quote!(
#resources #resources
@ -185,7 +208,7 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
#pre_init #pre_init
#init(#init_arg); #init_phase
#post_init #post_init
@ -290,10 +313,11 @@ fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2:
quote!(#(#items)*) quote!(#(#items)*)
} }
fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::TokenStream, bool) {
let attrs = &app.init.attrs; let attrs = &app.init.attrs;
let locals = mk_locals(&app.init.statics, true); let locals = mk_locals(&app.init.statics, true);
let stmts = &app.init.stmts; let stmts = &app.init.stmts;
// TODO remove in v0.5.x
let assigns = app let assigns = app
.init .init
.assigns .assigns
@ -334,12 +358,47 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Toke
analysis, analysis,
); );
let (late_resources, late_resources_ident, ret) = if app.init.returns_late_resources {
// create `LateResources` struct in the root of the crate
let ident = mk_ident(None);
let fields = app
.resources
.iter()
.filter_map(|(name, res)| {
if res.expr.is_none() {
let ty = &res.ty;
Some(quote!(pub #name: #ty))
} else {
None
}
})
.collect::<Vec<_>>();
let late_resources = quote!(
#[allow(non_snake_case)]
pub struct #ident {
#(#fields),*
}
);
(
Some(late_resources),
Some(ident),
Some(quote!(-> init::LateResources)),
)
} else {
(None, None, None)
};
let has_late_resources = late_resources.is_some();
let module = module( let module = module(
ctxt, ctxt,
Kind::Init, Kind::Init,
!app.init.args.schedule.is_empty(), !app.init.args.schedule.is_empty(),
!app.init.args.spawn.is_empty(), !app.init.args.spawn.is_empty(),
app, app,
late_resources_ident,
); );
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
@ -365,25 +424,30 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Toke
let unsafety = &app.init.unsafety; let unsafety = &app.init.unsafety;
let device = &app.args.device; let device = &app.args.device;
let init = &ctxt.init; let init = &ctxt.init;
quote!( (
#module quote!(
#late_resources
#(#attrs)* #module
#unsafety fn #init(mut core: rtfm::Peripherals) {
#(#locals)*
#baseline_let #(#attrs)*
#unsafety fn #init(mut core: rtfm::Peripherals) #ret {
#(#locals)*
#prelude #baseline_let
let mut device = unsafe { #device::Peripherals::steal() }; #prelude
#start_let let mut device = unsafe { #device::Peripherals::steal() };
#(#stmts)* #start_let
#(#assigns)* #(#stmts)*
}
#(#assigns)*
}
),
has_late_resources,
) )
} }
@ -440,6 +504,7 @@ fn module(
schedule: bool, schedule: bool,
spawn: bool, spawn: bool,
app: &App, app: &App,
late_resources: Option<Ident>,
) -> proc_macro2::TokenStream { ) -> proc_macro2::TokenStream {
let mut items = vec![]; let mut items = vec![];
let mut fields = vec![]; let mut fields = vec![];
@ -572,6 +637,12 @@ fn module(
Kind::Task(_) => "Software task", Kind::Task(_) => "Software task",
}; };
if let Some(late_resources) = late_resources {
items.push(quote!(
pub use super::#late_resources as LateResources;
));
}
quote!( quote!(
#root #root
@ -950,6 +1021,7 @@ fn idle(
!idle.args.schedule.is_empty(), !idle.args.schedule.is_empty(),
!idle.args.spawn.is_empty(), !idle.args.spawn.is_empty(),
app, app,
None,
); );
let unsafety = &idle.unsafety; let unsafety = &idle.unsafety;
@ -1004,6 +1076,7 @@ fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_ma
!exception.args.schedule.is_empty(), !exception.args.schedule.is_empty(),
!exception.args.spawn.is_empty(), !exception.args.spawn.is_empty(),
app, app,
None,
); );
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
@ -1083,6 +1156,7 @@ fn interrupts(
!interrupt.args.schedule.is_empty(), !interrupt.args.schedule.is_empty(),
!interrupt.args.spawn.is_empty(), !interrupt.args.spawn.is_empty(),
app, app,
None,
)); ));
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
@ -1245,6 +1319,7 @@ fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Tok
!task.args.schedule.is_empty(), !task.args.schedule.is_empty(),
!task.args.spawn.is_empty(), !task.args.spawn.is_empty(),
app, app,
None,
)); ));
let attrs = &task.attrs; let attrs = &task.attrs;

View file

@ -596,6 +596,7 @@ impl Parse for InitArgs {
} }
} }
// TODO remove in v0.5.x
pub struct Assign { pub struct Assign {
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub left: Ident, pub left: Ident,
@ -608,32 +609,83 @@ pub struct Init {
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: HashMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
// TODO remove in v0.5.x
pub assigns: Vec<Assign>, pub assigns: Vec<Assign>,
pub returns_late_resources: bool,
} }
impl Init { impl Init {
fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> { fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> {
let valid_signature = item.vis == Visibility::Inherited let mut valid_signature = item.vis == Visibility::Inherited
&& item.constness.is_none() && item.constness.is_none()
&& item.asyncness.is_none() && item.asyncness.is_none()
&& item.abi.is_none() && item.abi.is_none()
&& item.decl.generics.params.is_empty() && item.decl.generics.params.is_empty()
&& item.decl.generics.where_clause.is_none() && item.decl.generics.where_clause.is_none()
&& item.decl.inputs.is_empty() && item.decl.inputs.is_empty()
&& item.decl.variadic.is_none() && item.decl.variadic.is_none();
&& is_unit(&item.decl.output);
let returns_late_resources = match &item.decl.output {
ReturnType::Default => false,
ReturnType::Type(_, ty) => {
match &**ty {
Type::Tuple(t) => {
if t.elems.is_empty() {
// -> ()
true
} else {
valid_signature = false;
false // don't care
}
}
Type::Path(p) => {
let mut segments = p.path.segments.iter();
if p.qself.is_none()
&& p.path.leading_colon.is_none()
&& p.path.segments.len() == 2
&& segments.next().map(|s| {
s.arguments == PathArguments::None && s.ident.to_string() == "init"
}) == Some(true)
&& segments.next().map(|s| {
s.arguments == PathArguments::None
&& s.ident.to_string() == "LateResources"
}) == Some(true)
{
// -> init::LateResources
true
} else {
valid_signature = false;
false // don't care
}
}
_ => {
valid_signature = false;
false // don't care
}
}
}
};
let span = item.span(); let span = item.span();
if !valid_signature { if !valid_signature {
return Err(parse::Error::new( return Err(parse::Error::new(
span, span,
"`init` must have type signature `[unsafe] fn()`", "`init` must have type signature `[unsafe] fn() [-> init::LateResources]`",
)); ));
} }
let (statics, stmts) = extract_statics(item.block.stmts); let (statics, stmts) = extract_statics(item.block.stmts);
let (stmts, assigns) = extract_assignments(stmts); let (stmts, assigns) = if returns_late_resources {
(stmts, vec![])
} else {
extract_assignments(stmts)
};
Ok(Init { Ok(Init {
args, args,
@ -642,6 +694,7 @@ impl Init {
statics: Static::parse(statics)?, statics: Static::parse(statics)?,
stmts, stmts,
assigns, assigns,
returns_late_resources,
}) })
} }
} }
@ -1207,6 +1260,7 @@ fn extract_statics(stmts: Vec<Stmt>) -> (Statics, Vec<Stmt>) {
(statics, stmts) (statics, stmts)
} }
// TODO remove in v0.5.x
fn extract_assignments(stmts: Vec<Stmt>) -> (Vec<Stmt>, Vec<Assign>) { fn extract_assignments(stmts: Vec<Stmt>) -> (Vec<Stmt>, Vec<Assign>) {
let mut istmts = stmts.into_iter().rev(); let mut istmts = stmts.into_iter().rev();

View file

@ -14,6 +14,20 @@
//! //!
//! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html //! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html
//! //!
//! # Minimum Supported Rust Version (MSRV)
//!
//! This crate is guaranteed to compile on stable Rust 1.31 (2018 edition) and up. It *might*
//! compile on older versions but that may change in any new patch release.
//!
//! # Semantic Versioning
//!
//! Like the Rust project, this crate adheres to [SemVer]: breaking changes in the API and semantics
//! require a *semver bump* (a new minor version release), with the exception of breaking changes
//! that fix soundness issues -- those are considered bug fixes and can be landed in a new patch
//! release.
//!
//! [SemVer]: https://semver.org/spec/v2.0.0.html
//!
//! # Cargo features //! # Cargo features
//! //!
//! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule //! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule

View file

@ -0,0 +1,29 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
static mut UNINITIALIZED: bool = ();
#[init]
fn init() {
if false {
return; //~ ERROR `init` is *not* allowed to early return
}
UNINITIALIZED = true;
}
#[interrupt(resources = [UNINITIALIZED])]
fn UART0() {
if resources.UNINITIALIZED {
// UB
}
}
};

View file

@ -0,0 +1,32 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
static mut UNINITIALIZED: bool = ();
#[init]
fn init() {
let x = || {
// this is OK
return 0;
};
return; //~ ERROR `init` is *not* allowed to early return
UNINITIALIZED = true;
}
#[interrupt(resources = [UNINITIALIZED])]
fn UART0() {
if resources.UNINITIALIZED {
// UB
}
}
};

View file

@ -11,7 +11,7 @@ use rtfm::app;
const APP: () = { const APP: () = {
#[init] #[init]
fn init() -> ! { fn init() -> ! {
//~^ ERROR `init` must have type signature `[unsafe] fn()` //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]`
loop {} loop {}
} }
}; };

View file

@ -11,6 +11,6 @@ use rtfm::app;
const APP: () = { const APP: () = {
#[init] #[init]
fn init(undef: u32) { fn init(undef: u32) {
//~^ ERROR `init` must have type signature `[unsafe] fn()` //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]`
} }
}; };

View file

@ -11,7 +11,7 @@ use rtfm::app;
const APP: () = { const APP: () = {
#[init] #[init]
fn init() -> u32 { fn init() -> u32 {
//~^ ERROR `init` must have type signature `[unsafe] fn()` //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]`
0 0
} }
}; };

View file

@ -22,8 +22,10 @@ const APP: () = {
static mut X: NotSend = (); static mut X: NotSend = ();
#[init] #[init]
fn init() { fn init() -> init::LateResources {
X = NotSend { _0: PhantomData }; init::LateResources {
X: NotSend { _0: PhantomData },
}
} }
#[interrupt(resources = [X])] #[interrupt(resources = [X])]

View file

@ -1,3 +1,5 @@
// TODO remove in v0.5.x
#![no_main] #![no_main]
#![no_std] #![no_std]

View file

@ -19,10 +19,12 @@ const APP: () = {
static mut Y: Option<NotSend> = None; static mut Y: Option<NotSend> = None;
#[init(resources = [Y])] #[init(resources = [Y])]
fn init() { fn init() -> init::LateResources {
*resources.Y = Some(NotSend { _0: PhantomData }); *resources.Y = Some(NotSend { _0: PhantomData });
X = NotSend { _0: PhantomData }; init::LateResources {
X: NotSend { _0: PhantomData },
}
} }
#[idle(resources = [X, Y])] #[idle(resources = [X, Y])]

View file

@ -14,8 +14,7 @@ const APP: () = {
static Y: u32 = (); static Y: u32 = ();
#[init] #[init]
fn init() { fn init() -> init::LateResources {
X = 0; init::LateResources { X: 0, Y: 1 }
Y = 1;
} }
}; };