Skip to content

Commit 40bc552

Browse files
committed
replace shadowed return token by unsafe-to-create token
We use a unit struct `__InitOk` in the closure generated by the initializer macros as the return value. We shadow it by creating a struct with the same name again inside of the closure, preventing early returns of `Ok` in the initializer (before all fields have been initialized). In the face of Type Alias Impl Trait (TAIT) and the next trait solver, this solution no longer works [1]. The shadowed struct can be named through type inference. In addition, there is an RFC proposing to add the feature of path inference to Rust, which would similarly allow [2] Thus remove the shadowed token and replace it with an `unsafe` to create token. The reason we initially used the shadowing solution was because an alternative solution used a builder pattern. Gary writes [3]: In the early builder-pattern based InitOk, having a single InitOk type for token is unsound because one can launder an InitOk token used for one place to another initializer. I used a branded lifetime solution, and then you figured out that using a shadowed type would work better because nobody could construct it at all. The laundering issue does not apply to the approach we ended up with today. With this change, the example by Tim Chirananthavat in [1] no longer compiles and results in this error: error: cannot construct `pin_init::__internal::InitOk` with struct literal syntax due to private fields --> src/main.rs:26:17 | 26 | InferredType {} | ^^^^^^^^^^^^ | = note: private field `0` that was not provided help: you might have meant to use the `new` associated function | 26 - InferredType {} 26 + InferredType::new() | Applying the suggestion of using the `::new()` function, results in another expected error: error[E0133]: call to unsafe function `pin_init::__internal::InitOk::new` is unsafe and requires unsafe block --> src/main.rs:26:17 | 26 | InferredType::new() | ^^^^^^^^^^^^^^^^^^^ call to unsafe function | = note: consult the function's documentation for information on how to avoid undefined behavior Reported-by: Tim Chirananthavat <theemathas@gmail.com> Link: rust-lang/rust#153535 [1] Link: rust-lang/rfcs#3444 (comment) [2] Link: rust-lang/rust#153535 (comment) [3] Signed-off-by: Benno Lossin <lossin@kernel.org>
1 parent bbf992b commit 40bc552

6 files changed

Lines changed: 113 additions & 62 deletions

File tree

internal/src/init.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,6 @@ pub(crate) fn expand(
156156
);
157157
let field_check = make_field_check(&fields, init_kind, &path);
158158
Ok(quote! {{
159-
// We do not want to allow arbitrary returns, so we declare this type as the `Ok` return
160-
// type and shadow it later when we insert the arbitrary user code. That way there will be
161-
// no possibility of returning without `unsafe`.
162-
struct __InitOk;
163-
164159
// Get the data about fields from the supplied type.
165160
// SAFETY: TODO
166161
let #data = unsafe {
@@ -170,18 +165,15 @@ pub(crate) fn expand(
170165
#path::#get_data()
171166
};
172167
// Ensure that `#data` really is of type `#data` and help with type inference:
173-
let init = ::pin_init::__internal::#data_trait::make_closure::<_, __InitOk, #error>(
168+
let init = ::pin_init::__internal::#data_trait::make_closure::<_, ::pin_init::__internal::InitOk, #error>(
174169
#data,
175170
move |slot| {
176-
{
177-
// Shadow the structure so it cannot be used to return early.
178-
struct __InitOk;
179-
#zeroable_check
180-
#this
181-
#init_fields
182-
#field_check
183-
}
184-
Ok(__InitOk)
171+
#zeroable_check
172+
#this
173+
#init_fields
174+
#field_check
175+
// SAFETY: we are the `init!` macro that is allowed to call this.
176+
Ok(unsafe { ::pin_init::__internal::InitOk::new() })
185177
}
186178
);
187179
let init = move |slot| -> ::core::result::Result<(), #error> {

src/__internal.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,23 @@ where
4646
}
4747
}
4848

49+
/// Token type to signify successful initialization.
50+
///
51+
/// Can only be constructed via the unsafe [`Self::new`] function. The initializer macros use this
52+
/// token type to prevent returning `Ok` from an initializer without initializing all fields.
53+
pub struct InitOk(());
54+
55+
impl InitOk {
56+
/// Creates a new token.
57+
///
58+
/// # Safety
59+
///
60+
/// This function may only be called from the `init!` macro in `../internal/src/init.rs`.
61+
pub unsafe fn new() -> Self {
62+
Self(())
63+
}
64+
}
65+
4966
/// This trait is only implemented via the `#[pin_data]` proc-macro. It is used to facilitate
5067
/// the pin projections within the initializers.
5168
///
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use pin_init::*;
2+
3+
struct Foo {
4+
a: usize,
5+
}
6+
7+
fn main() {
8+
let _ = init!(Foo {
9+
_: {
10+
return Ok(());
11+
},
12+
a: 42,
13+
});
14+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error[E0308]: mismatched types
2+
--> tests/ui/compile-fail/init/early_return.rs:10:23
3+
|
4+
10 | return Ok(());
5+
| -- ^^ expected `InitOk`, found `()`
6+
| |
7+
| arguments to this enum variant are incorrect
8+
|
9+
help: the type constructed contains `()` due to the type of the argument passed
10+
--> tests/ui/compile-fail/init/early_return.rs:10:20
11+
|
12+
10 | return Ok(());
13+
| ^^^--^
14+
| |
15+
| this argument influences the type of `Ok`
16+
note: tuple variant defined here
17+
--> $RUST/core/src/result.rs
18+
|
19+
| Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
20+
| ^^
21+
22+
warning: unreachable statement
23+
--> tests/ui/compile-fail/init/early_return.rs:8:13
24+
|
25+
8 | let _ = init!(Foo {
26+
| _____________^
27+
9 | | _: {
28+
10 | | return Ok(());
29+
| | ------------- any code following this expression is unreachable
30+
11 | | },
31+
12 | | a: 42,
32+
13 | | });
33+
| |______^ unreachable statement
34+
|
35+
= note: `#[warn(unreachable_code)]` (part of `#[warn(unused)]`) on by default
36+
= note: this warning originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui/expand/no_field_access.expanded.rs

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,53 @@ struct Foo {
77
}
88
fn main() {
99
let _ = {
10-
struct __InitOk;
1110
let __data = unsafe {
1211
use ::pin_init::__internal::HasInitData;
1312
Foo::__init_data()
1413
};
1514
let init = ::pin_init::__internal::InitData::make_closure::<
1615
_,
17-
__InitOk,
16+
::pin_init::__internal::InitOk,
1817
::core::convert::Infallible,
1918
>(
2019
__data,
2120
move |slot| {
2221
{
23-
struct __InitOk;
24-
{
25-
let c = -42;
26-
unsafe { ::core::ptr::write(&raw mut (*slot).c, c) };
27-
}
28-
let __c_guard = unsafe {
29-
::pin_init::__internal::DropGuard::new(&raw mut (*slot).c)
30-
};
31-
{
32-
let b = *c;
33-
unsafe { ::core::ptr::write(&raw mut (*slot).b, b) };
34-
}
35-
let __b_guard = unsafe {
36-
::pin_init::__internal::DropGuard::new(&raw mut (*slot).b)
37-
};
38-
{
39-
let a = 0;
40-
unsafe { ::core::ptr::write(&raw mut (*slot).a, a) };
41-
}
42-
let __a_guard = unsafe {
43-
::pin_init::__internal::DropGuard::new(&raw mut (*slot).a)
44-
};
45-
::core::mem::forget(__c_guard);
46-
::core::mem::forget(__b_guard);
47-
::core::mem::forget(__a_guard);
48-
#[allow(unreachable_code, clippy::diverging_sub_expression)]
49-
let _ = || unsafe {
50-
::core::ptr::write(
51-
slot,
52-
Foo {
53-
c: ::core::panicking::panic("explicit panic"),
54-
b: ::core::panicking::panic("explicit panic"),
55-
a: ::core::panicking::panic("explicit panic"),
56-
},
57-
)
58-
};
22+
let c = -42;
23+
unsafe { ::core::ptr::write(&raw mut (*slot).c, c) };
5924
}
60-
Ok(__InitOk)
25+
let __c_guard = unsafe {
26+
::pin_init::__internal::DropGuard::new(&raw mut (*slot).c)
27+
};
28+
{
29+
let b = *c;
30+
unsafe { ::core::ptr::write(&raw mut (*slot).b, b) };
31+
}
32+
let __b_guard = unsafe {
33+
::pin_init::__internal::DropGuard::new(&raw mut (*slot).b)
34+
};
35+
{
36+
let a = 0;
37+
unsafe { ::core::ptr::write(&raw mut (*slot).a, a) };
38+
}
39+
let __a_guard = unsafe {
40+
::pin_init::__internal::DropGuard::new(&raw mut (*slot).a)
41+
};
42+
::core::mem::forget(__c_guard);
43+
::core::mem::forget(__b_guard);
44+
::core::mem::forget(__a_guard);
45+
#[allow(unreachable_code, clippy::diverging_sub_expression)]
46+
let _ = || unsafe {
47+
::core::ptr::write(
48+
slot,
49+
Foo {
50+
c: ::core::panicking::panic("explicit panic"),
51+
b: ::core::panicking::panic("explicit panic"),
52+
a: ::core::panicking::panic("explicit panic"),
53+
},
54+
)
55+
};
56+
Ok(unsafe { ::pin_init::__internal::InitOk::new() })
6157
},
6258
);
6359
let init = move |

tests/ui/expand/simple-init.expanded.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,20 @@ use pin_init::*;
22
struct Foo {}
33
fn main() {
44
let _ = {
5-
struct __InitOk;
65
let __data = unsafe {
76
use ::pin_init::__internal::HasInitData;
87
Foo::__init_data()
98
};
109
let init = ::pin_init::__internal::InitData::make_closure::<
1110
_,
12-
__InitOk,
11+
::pin_init::__internal::InitOk,
1312
::core::convert::Infallible,
1413
>(
1514
__data,
1615
move |slot| {
17-
{
18-
struct __InitOk;
19-
#[allow(unreachable_code, clippy::diverging_sub_expression)]
20-
let _ = || unsafe { ::core::ptr::write(slot, Foo {}) };
21-
}
22-
Ok(__InitOk)
16+
#[allow(unreachable_code, clippy::diverging_sub_expression)]
17+
let _ = || unsafe { ::core::ptr::write(slot, Foo {}) };
18+
Ok(unsafe { ::pin_init::__internal::InitOk::new() })
2319
},
2420
);
2521
let init = move |

0 commit comments

Comments
 (0)