diff options
| -rw-r--r-- | embassy-executor-macros/src/macros/task.rs | 82 | ||||
| -rw-r--r-- | embassy-executor/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | embassy-executor/src/lib.rs | 8 | ||||
| -rw-r--r-- | embassy-executor/tests/test.rs | 11 | ||||
| -rw-r--r-- | embassy-executor/tests/ui.rs | 3 | ||||
| -rw-r--r-- | embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr | 2 | ||||
| -rw-r--r-- | embassy-executor/tests/ui/nonstatic_struct_elided.stderr | 14 | ||||
| -rw-r--r-- | embassy-executor/tests/ui/task_safety_attribute.rs | 25 | ||||
| -rw-r--r-- | embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs | 10 | ||||
| -rw-r--r-- | embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr | 18 |
10 files changed, 153 insertions, 21 deletions
diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 1c5e3571d..fc8673743 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs | |||
| @@ -5,7 +5,7 @@ use darling::FromMeta; | |||
| 5 | use proc_macro2::{Span, TokenStream}; | 5 | use proc_macro2::{Span, TokenStream}; |
| 6 | use quote::{format_ident, quote}; | 6 | use quote::{format_ident, quote}; |
| 7 | use syn::visit::{self, Visit}; | 7 | use syn::visit::{self, Visit}; |
| 8 | use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type}; | 8 | use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type, Visibility}; |
| 9 | 9 | ||
| 10 | use crate::util::*; | 10 | use crate::util::*; |
| 11 | 11 | ||
| @@ -112,13 +112,11 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { | |||
| 112 | } | 112 | } |
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | let task_ident = f.sig.ident.clone(); | 115 | // Copy the generics + where clause to avoid more spurious errors. |
| 116 | let task_inner_ident = format_ident!("__{}_task", task_ident); | 116 | let generics = &f.sig.generics; |
| 117 | 117 | let where_clause = &f.sig.generics.where_clause; | |
| 118 | let mut task_inner = f.clone(); | 118 | let unsafety = &f.sig.unsafety; |
| 119 | let visibility = task_inner.vis.clone(); | 119 | let visibility = &f.vis; |
| 120 | task_inner.vis = syn::Visibility::Inherited; | ||
| 121 | task_inner.sig.ident = task_inner_ident.clone(); | ||
| 122 | 120 | ||
| 123 | // assemble the original input arguments, | 121 | // assemble the original input arguments, |
| 124 | // including any attributes that may have | 122 | // including any attributes that may have |
| @@ -131,6 +129,64 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { | |||
| 131 | )); | 129 | )); |
| 132 | } | 130 | } |
| 133 | 131 | ||
| 132 | let task_ident = f.sig.ident.clone(); | ||
| 133 | let task_inner_ident = format_ident!("__{}_task", task_ident); | ||
| 134 | |||
| 135 | let task_inner_future_output = match &f.sig.output { | ||
| 136 | ReturnType::Default => quote! {-> impl ::core::future::Future<Output = ()>}, | ||
| 137 | // Special case the never type since we can't stuff it into a `impl Future<Output = !>` | ||
| 138 | ReturnType::Type(arrow, maybe_never) | ||
| 139 | if f.sig.asyncness.is_some() && matches!(**maybe_never, Type::Never(_)) => | ||
| 140 | { | ||
| 141 | quote! { | ||
| 142 | #arrow impl ::core::future::Future<Output=#embassy_executor::_export::Never> | ||
| 143 | } | ||
| 144 | } | ||
| 145 | ReturnType::Type(arrow, maybe_never) if matches!(**maybe_never, Type::Never(_)) => quote! { | ||
| 146 | #arrow #maybe_never | ||
| 147 | }, | ||
| 148 | // Grab the arrow span, why not | ||
| 149 | ReturnType::Type(arrow, typ) if f.sig.asyncness.is_some() => quote! { | ||
| 150 | #arrow impl ::core::future::Future<Output = #typ> | ||
| 151 | }, | ||
| 152 | // We assume that if `f` isn't async, it must return `-> impl Future<...>` | ||
| 153 | // This is checked using traits later | ||
| 154 | ReturnType::Type(arrow, typ) => quote! { | ||
| 155 | #arrow #typ | ||
| 156 | }, | ||
| 157 | }; | ||
| 158 | |||
| 159 | // We have to rename the function since it might be recursive; | ||
| 160 | let mut task_inner_function = f.clone(); | ||
| 161 | let task_inner_function_ident = format_ident!("__{}_task_inner_function", task_ident); | ||
| 162 | task_inner_function.sig.ident = task_inner_function_ident.clone(); | ||
| 163 | task_inner_function.vis = Visibility::Inherited; | ||
| 164 | |||
| 165 | let task_inner_body = if errors.is_empty() { | ||
| 166 | quote! { | ||
| 167 | #task_inner_function | ||
| 168 | |||
| 169 | // SAFETY: All the preconditions to `#task_ident` apply to | ||
| 170 | // all contexts `#task_inner_ident` is called in | ||
| 171 | #unsafety { | ||
| 172 | #task_inner_function_ident(#(#full_args,)*) | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } else { | ||
| 176 | quote! { | ||
| 177 | async {::core::todo!()} | ||
| 178 | } | ||
| 179 | }; | ||
| 180 | |||
| 181 | let task_inner = quote! { | ||
| 182 | #visibility fn #task_inner_ident #generics (#fargs) | ||
| 183 | #task_inner_future_output | ||
| 184 | #where_clause | ||
| 185 | { | ||
| 186 | #task_inner_body | ||
| 187 | } | ||
| 188 | }; | ||
| 189 | |||
| 134 | let spawn = if returns_impl_trait { | 190 | let spawn = if returns_impl_trait { |
| 135 | quote!(spawn) | 191 | quote!(spawn) |
| 136 | } else { | 192 | } else { |
| @@ -173,7 +229,7 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { | |||
| 173 | unsafe { __task_pool_get(#task_inner_ident).#spawn(move || #task_inner_ident(#(#full_args,)*)) } | 229 | unsafe { __task_pool_get(#task_inner_ident).#spawn(move || #task_inner_ident(#(#full_args,)*)) } |
| 174 | }; | 230 | }; |
| 175 | 231 | ||
| 176 | let task_outer_attrs = task_inner.attrs.clone(); | 232 | let task_outer_attrs = &f.attrs; |
| 177 | 233 | ||
| 178 | if !errors.is_empty() { | 234 | if !errors.is_empty() { |
| 179 | task_outer_body = quote! { | 235 | task_outer_body = quote! { |
| @@ -183,10 +239,6 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { | |||
| 183 | }; | 239 | }; |
| 184 | } | 240 | } |
| 185 | 241 | ||
| 186 | // Copy the generics + where clause to avoid more spurious errors. | ||
| 187 | let generics = &f.sig.generics; | ||
| 188 | let where_clause = &f.sig.generics.where_clause; | ||
| 189 | |||
| 190 | let result = quote! { | 242 | let result = quote! { |
| 191 | // This is the user's task function, renamed. | 243 | // This is the user's task function, renamed. |
| 192 | // We put it outside the #task_ident fn below, because otherwise | 244 | // We put it outside the #task_ident fn below, because otherwise |
| @@ -196,7 +248,7 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { | |||
| 196 | #task_inner | 248 | #task_inner |
| 197 | 249 | ||
| 198 | #(#task_outer_attrs)* | 250 | #(#task_outer_attrs)* |
| 199 | #visibility fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken<impl Sized> #where_clause{ | 251 | #visibility #unsafety fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken<impl Sized> #where_clause{ |
| 200 | #task_outer_body | 252 | #task_outer_body |
| 201 | } | 253 | } |
| 202 | 254 | ||
| @@ -213,7 +265,7 @@ fn check_arg_ty(errors: &mut TokenStream, ty: &Type) { | |||
| 213 | 265 | ||
| 214 | impl<'a, 'ast> Visit<'ast> for Visitor<'a> { | 266 | impl<'a, 'ast> Visit<'ast> for Visitor<'a> { |
| 215 | fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) { | 267 | fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) { |
| 216 | // only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`. | 268 | // Only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`. |
| 217 | if i.lifetime.is_none() { | 269 | if i.lifetime.is_none() { |
| 218 | error( | 270 | error( |
| 219 | self.errors, | 271 | self.errors, |
diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 914863a83..7404961f3 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md | |||
| @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 21 | - Added support for `-> impl Future<Output = ()>` in `#[task]` | 21 | - Added support for `-> impl Future<Output = ()>` in `#[task]` |
| 22 | - Fixed `Send` unsoundness with `-> impl Future` tasks | 22 | - Fixed `Send` unsoundness with `-> impl Future` tasks |
| 23 | - Marked `Spawner::for_current_executor` as `unsafe` | 23 | - Marked `Spawner::for_current_executor` as `unsafe` |
| 24 | - `#[task]` now properly marks the generated function as unsafe if the task is marked unsafe | ||
| 24 | 25 | ||
| 25 | ## 0.7.0 - 2025-01-02 | 26 | ## 0.7.0 - 2025-01-02 |
| 26 | 27 | ||
diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index e174a0594..0747db032 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs | |||
| @@ -216,7 +216,7 @@ pub mod _export { | |||
| 216 | ); | 216 | ); |
| 217 | 217 | ||
| 218 | #[allow(dead_code)] | 218 | #[allow(dead_code)] |
| 219 | trait HasOutput { | 219 | pub trait HasOutput { |
| 220 | type Output; | 220 | type Output; |
| 221 | } | 221 | } |
| 222 | 222 | ||
| @@ -225,7 +225,7 @@ pub mod _export { | |||
| 225 | } | 225 | } |
| 226 | 226 | ||
| 227 | #[allow(dead_code)] | 227 | #[allow(dead_code)] |
| 228 | type Never = <fn() -> ! as HasOutput>::Output; | 228 | pub type Never = <fn() -> ! as HasOutput>::Output; |
| 229 | } | 229 | } |
| 230 | 230 | ||
| 231 | /// Implementation details for embassy macros. | 231 | /// Implementation details for embassy macros. |
| @@ -242,7 +242,7 @@ pub mod _export { | |||
| 242 | impl TaskReturnValue for Never {} | 242 | impl TaskReturnValue for Never {} |
| 243 | 243 | ||
| 244 | #[allow(dead_code)] | 244 | #[allow(dead_code)] |
| 245 | trait HasOutput { | 245 | pub trait HasOutput { |
| 246 | type Output; | 246 | type Output; |
| 247 | } | 247 | } |
| 248 | 248 | ||
| @@ -251,5 +251,5 @@ pub mod _export { | |||
| 251 | } | 251 | } |
| 252 | 252 | ||
| 253 | #[allow(dead_code)] | 253 | #[allow(dead_code)] |
| 254 | type Never = <fn() -> ! as HasOutput>::Output; | 254 | pub type Never = <fn() -> ! as HasOutput>::Output; |
| 255 | } | 255 | } |
diff --git a/embassy-executor/tests/test.rs b/embassy-executor/tests/test.rs index c1e7ec5d7..b84d3785a 100644 --- a/embassy-executor/tests/test.rs +++ b/embassy-executor/tests/test.rs | |||
| @@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex}; | |||
| 7 | use std::task::Poll; | 7 | use std::task::Poll; |
| 8 | 8 | ||
| 9 | use embassy_executor::raw::Executor; | 9 | use embassy_executor::raw::Executor; |
| 10 | use embassy_executor::task; | 10 | use embassy_executor::{task, Spawner}; |
| 11 | 11 | ||
| 12 | #[export_name = "__pender"] | 12 | #[export_name = "__pender"] |
| 13 | fn __pender(context: *mut ()) { | 13 | fn __pender(context: *mut ()) { |
| @@ -317,3 +317,12 @@ fn executor_task_cfg_args() { | |||
| 317 | let (_, _, _) = (a, b, c); | 317 | let (_, _, _) = (a, b, c); |
| 318 | } | 318 | } |
| 319 | } | 319 | } |
| 320 | |||
| 321 | #[test] | ||
| 322 | fn recursive_task() { | ||
| 323 | #[embassy_executor::task(pool_size = 2)] | ||
| 324 | async fn task1() { | ||
| 325 | let spawner = unsafe { Spawner::for_current_executor().await }; | ||
| 326 | spawner.spawn(task1()); | ||
| 327 | } | ||
| 328 | } | ||
diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs index 7757775ee..5486a0624 100644 --- a/embassy-executor/tests/ui.rs +++ b/embassy-executor/tests/ui.rs | |||
| @@ -32,4 +32,7 @@ fn ui() { | |||
| 32 | t.compile_fail("tests/ui/self.rs"); | 32 | t.compile_fail("tests/ui/self.rs"); |
| 33 | t.compile_fail("tests/ui/type_error.rs"); | 33 | t.compile_fail("tests/ui/type_error.rs"); |
| 34 | t.compile_fail("tests/ui/where_clause.rs"); | 34 | t.compile_fail("tests/ui/where_clause.rs"); |
| 35 | t.compile_fail("tests/ui/unsafe_op_in_unsafe_task.rs"); | ||
| 36 | |||
| 37 | t.pass("tests/ui/task_safety_attribute.rs"); | ||
| 35 | } | 38 | } |
diff --git a/embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr b/embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr index 73ceb989d..3c3c9503b 100644 --- a/embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr +++ b/embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr | |||
| @@ -7,4 +7,4 @@ error[E0277]: task futures must resolve to `()` or `!` | |||
| 7 | = note: use `async fn` or change the return type to `impl Future<Output = ()>` | 7 | = note: use `async fn` or change the return type to `impl Future<Output = ()>` |
| 8 | = help: the following other types implement trait `TaskReturnValue`: | 8 | = help: the following other types implement trait `TaskReturnValue`: |
| 9 | () | 9 | () |
| 10 | <fn() -> ! as _export::HasOutput>::Output | 10 | <fn() -> ! as HasOutput>::Output |
diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr index 099ef8b4e..0ee1bfe0c 100644 --- a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr | |||
| @@ -8,3 +8,17 @@ help: indicate the anonymous lifetime | |||
| 8 | | | 8 | | |
| 9 | 6 | async fn task(_x: Foo<'_>) {} | 9 | 6 | async fn task(_x: Foo<'_>) {} |
| 10 | | ++++ | 10 | | ++++ |
| 11 | |||
| 12 | error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds | ||
| 13 | --> tests/ui/nonstatic_struct_elided.rs:5:1 | ||
| 14 | | | ||
| 15 | 5 | #[embassy_executor::task] | ||
| 16 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ opaque type defined here | ||
| 17 | 6 | async fn task(_x: Foo) {} | ||
| 18 | | --- hidden type `impl Sized` captures the anonymous lifetime defined here | ||
| 19 | | | ||
| 20 | = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info) | ||
| 21 | help: add a `use<...>` bound to explicitly capture `'_` | ||
| 22 | | | ||
| 23 | 5 | #[embassy_executor::task] + use<'_> | ||
| 24 | | +++++++++ | ||
diff --git a/embassy-executor/tests/ui/task_safety_attribute.rs b/embassy-executor/tests/ui/task_safety_attribute.rs new file mode 100644 index 000000000..ab5a2f99f --- /dev/null +++ b/embassy-executor/tests/ui/task_safety_attribute.rs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | #![deny(unused_unsafe)] | ||
| 3 | |||
| 4 | use std::mem; | ||
| 5 | |||
| 6 | #[embassy_executor::task] | ||
| 7 | async fn safe() {} | ||
| 8 | |||
| 9 | #[embassy_executor::task] | ||
| 10 | async unsafe fn not_safe() {} | ||
| 11 | |||
| 12 | #[export_name = "__pender"] | ||
| 13 | fn pender(_: *mut ()) { | ||
| 14 | // The test doesn't link if we don't include this. | ||
| 15 | // We never call this anyway. | ||
| 16 | } | ||
| 17 | |||
| 18 | fn main() { | ||
| 19 | let _forget_me = safe(); | ||
| 20 | // SAFETY: not_safe has not safety preconditions | ||
| 21 | let _forget_me2 = unsafe { not_safe() }; | ||
| 22 | |||
| 23 | mem::forget(_forget_me); | ||
| 24 | mem::forget(_forget_me2); | ||
| 25 | } | ||
diff --git a/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs b/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs new file mode 100644 index 000000000..ee7924838 --- /dev/null +++ b/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | #![deny(unsafe_op_in_unsafe_fn)] | ||
| 3 | |||
| 4 | #[embassy_executor::task] | ||
| 5 | async unsafe fn task() { | ||
| 6 | let x = 5; | ||
| 7 | (&x as *const i32).read(); | ||
| 8 | } | ||
| 9 | |||
| 10 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr b/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr new file mode 100644 index 000000000..d987a4b95 --- /dev/null +++ b/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | error[E0133]: call to unsafe function `std::ptr::const_ptr::<impl *const T>::read` is unsafe and requires unsafe block | ||
| 2 | --> tests/ui/unsafe_op_in_unsafe_task.rs:7:5 | ||
| 3 | | | ||
| 4 | 7 | (&x as *const i32).read(); | ||
| 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function | ||
| 6 | | | ||
| 7 | = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html> | ||
| 8 | = note: consult the function's documentation for information on how to avoid undefined behavior | ||
| 9 | note: an unsafe function restricts its caller, but its body is safe by default | ||
| 10 | --> tests/ui/unsafe_op_in_unsafe_task.rs:5:1 | ||
| 11 | | | ||
| 12 | 5 | async unsafe fn task() { | ||
| 13 | | ^^^^^^^^^^^^^^^^^^^^^^ | ||
| 14 | note: the lint level is defined here | ||
| 15 | --> tests/ui/unsafe_op_in_unsafe_task.rs:2:9 | ||
| 16 | | | ||
| 17 | 2 | #![deny(unsafe_op_in_unsafe_fn)] | ||
| 18 | | ^^^^^^^^^^^^^^^^^^^^^^ | ||
