mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-12-24 19:09:33 +01:00
Merge #140
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:
commit
ed6460f6dc
20 changed files with 549 additions and 57 deletions
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:}}
|
||||||
|
|
|
@ -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])]
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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])]
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -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
|
||||||
|
|
29
tests/cfail/early-return-2.rs
Normal file
29
tests/cfail/early-return-2.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
32
tests/cfail/early-return.rs
Normal file
32
tests/cfail/early-return.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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]`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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])]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// TODO remove in v0.5.x
|
||||||
|
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
|
|
@ -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])]
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue