From 366ea0598510e07b6fd5a3f548af7b559f15bbca Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Sun, 12 Nov 2023 21:05:17 +0100 Subject: [PATCH 1/4] Draft RFC 3521: forbidden function casts --- text/3521-forbidden-function-casts.md | 116 ++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 text/3521-forbidden-function-casts.md diff --git a/text/3521-forbidden-function-casts.md b/text/3521-forbidden-function-casts.md new file mode 100644 index 00000000000..51e3daa02e2 --- /dev/null +++ b/text/3521-forbidden-function-casts.md @@ -0,0 +1,116 @@ +- Feature Name: `forbidden_function_casts` +- Start Date: (fill me in with today's date, 2023-11-12) +- RFC PR: [rust-lang/rfcs#3521](https://github.com/rust-lang/rfcs/pull/3521) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +This RFC proposes to forbid all function-to-integer `as` casts in the next edition. + + +# Motivation +[motivation]: #motivation + +Currently we allow casting of function pointers and function item types to any integer width, returning the function address. Casts to types that are smaller than `usize` truncate, while casts to bigger types zero-extend. Thus, a function-to-int cast is basically transitive — `f as u8` is the same as `f as usize as u8`. + +This behavior can be surprising and error-prone. Moreover casting function item types is especially confusing, for example: +```rust +// This is an identity cast +// (`u8::MAX` is a `u8` constant) +let m = u8::MAX as u8; + +// This returns the least significant byte +// of the function pointer address of `u8::max` +// (`u8::max` is a function returning maximum of two `u8`s) +let m = u8::max as u8 +``` + +An example of this being a problem in practice can be seen in [rust-lang/rust#115511](https://github.com/rust-lang/rust/issues/115511): +```rust +// Code in `std::sys::windows::process::Command::spawn` +si.cb = mem::size_of:: as c::DWORD; +``` + +Instead of casting the *result* of `size_of` to `c::DWORD` the `size_of` function itself was cast, causing issues. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +In edition 2024, function-to-integer casts are a hard error: +```rust +let ptr: fn() = todo!(); +let addr = ptr as usize; //~ ERROR +let addr_byte = ptr as u8; //~ ERROR +``` + +To get the old behavior you can cast fn pointer to a raw pointer first and then to an integer: +```rust +let addr = ptr as *const () as usize; +let addr_byte = ptr as *const () as usize as u8; +``` + +Those changes can be automatically applied by an edition migration lint. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +- On `edition >= 2024` + - Casting function pointers or function defenitions to an integer type is a hard error + - Note that you can still cast function pointers and function item types to `*const V` and `*mut V` where `V: Sized` + - Note that function item types can also be casted to function pointers +- On `edition < 2024` + - Everything works as it used to + - In cases where we produce an error on `edition >= 2024` a FCW (Futurte Compatability Warning) is emited + +# Drawbacks +[drawbacks]: #drawbacks + +None known. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +The simplest alternative is to do nothing, but it leaves a footgun that is easy to reach, which is probably not a good idea. + +Another alternative is to only emit a lint, without making it an error on editions `>= 2024`. However, it seems like making `as` casts less powerful and less error prone is a desirable thing, and as such making it a hard error on newer editions is a good idea. + +Another option would be to still allow casts of function pointers to `usize` (but disallow all other function->integer casts). This, however, still leaves possible bugs caused by accidentally casting a function to a `usize` instead of calling the function or using a similarly named constant. + +We could also disallow more things: +- Disallow casting of function item types to raw pointers, requiring an intermediate cast to a function pointer. This may be desirable as it makes things even more explicit. +- Disallow casting on function pointers and function item types to any type using `as`. This removes all possible confusion, but requires us to provide a stable alternative (possibly a function accepting [`F: FnPtr`](https://doc.rust-lang.org/std/marker/trait.FnPtr.html), like [`FnPtr::addr`](https://doc.rust-lang.org/std/marker/trait.FnPtr.html#tymethod.addr)). + +# Prior art +[prior-art]: #prior-art + +- C supports casting function to differently sized integers + - But both clang and gcc [produce a warning](https://godbolt.org/z/7bW3xc8Ec) +- C++ seems to disallow casts to differently sized integers + - Both clang and gcc [produce an error](https://godbolt.org/z/Kd51aKaGh) + - Casts to types that happen to have the same size are allowed + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- [ ] Should the lint on editions `< 2024` be allow-by-default or warn-by-default? + +# Future possibilities +[future-possibilities]: #future-possibilities + +### Disallowing transitive ptr-to-integer and integer-to-ptr casts + +Similarly to function pointers, data pointers can also be cast to any integer width: + +```rust +fn byte(ptr: *const ()) -> u8 { + ptr as u8 // the same as `ptr as usize as u8` +} + +fn cursed(v: i8) -> *const () { + v as *const () // sign-extends + // (same as `v as usize as *const ()`) +} +``` + +This can be confusing in a similar way to function-to-int casts and we could disallow it in a similar way too. From f5889bac7b94d485700c1d0184a9da520473d9a7 Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Sun, 12 Nov 2023 21:32:49 +0100 Subject: [PATCH 2/4] Make links to unstable docs specify a version Co-authored-by: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> --- text/3521-forbidden-function-casts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3521-forbidden-function-casts.md b/text/3521-forbidden-function-casts.md index 51e3daa02e2..5f3204fc0da 100644 --- a/text/3521-forbidden-function-casts.md +++ b/text/3521-forbidden-function-casts.md @@ -79,7 +79,7 @@ Another option would be to still allow casts of function pointers to `usize` (bu We could also disallow more things: - Disallow casting of function item types to raw pointers, requiring an intermediate cast to a function pointer. This may be desirable as it makes things even more explicit. -- Disallow casting on function pointers and function item types to any type using `as`. This removes all possible confusion, but requires us to provide a stable alternative (possibly a function accepting [`F: FnPtr`](https://doc.rust-lang.org/std/marker/trait.FnPtr.html), like [`FnPtr::addr`](https://doc.rust-lang.org/std/marker/trait.FnPtr.html#tymethod.addr)). +- Disallow casting on function pointers and function item types to any type using `as`. This removes all possible confusion, but requires us to provide a stable alternative (possibly a function accepting [`F: FnPtr`](https://doc.rust-lang.org/1.73.0/std/marker/trait.FnPtr.html), like [`FnPtr::addr`](https://doc.rust-lang.org/1.73.0/std/marker/trait.FnPtr.html#tymethod.addr)). # Prior art [prior-art]: #prior-art From 3d66b258aa2812748de728aaba8c12f9d3bdd022 Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Sun, 12 Nov 2023 22:39:26 +0100 Subject: [PATCH 3/4] Clarify what warning is emitted on older editions Co-authored-by: Jules Bertholet --- text/3521-forbidden-function-casts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3521-forbidden-function-casts.md b/text/3521-forbidden-function-casts.md index 5f3204fc0da..175711cb99c 100644 --- a/text/3521-forbidden-function-casts.md +++ b/text/3521-forbidden-function-casts.md @@ -61,7 +61,7 @@ Those changes can be automatically applied by an edition migration lint. - Note that function item types can also be casted to function pointers - On `edition < 2024` - Everything works as it used to - - In cases where we produce an error on `edition >= 2024` a FCW (Futurte Compatability Warning) is emited + - In cases where we produce an error on `edition >= 2024` an edition compatibility warning is emitted # Drawbacks [drawbacks]: #drawbacks From 39e31abb4b7631537bf3104c73186424254b1833 Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Mon, 22 Jan 2024 23:00:29 +0400 Subject: [PATCH 4/4] fix typo Co-authored-by: Malo <57839069+MDLC01@users.noreply.github.com> --- text/3521-forbidden-function-casts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3521-forbidden-function-casts.md b/text/3521-forbidden-function-casts.md index 175711cb99c..3c450eb5e57 100644 --- a/text/3521-forbidden-function-casts.md +++ b/text/3521-forbidden-function-casts.md @@ -56,7 +56,7 @@ Those changes can be automatically applied by an edition migration lint. [reference-level-explanation]: #reference-level-explanation - On `edition >= 2024` - - Casting function pointers or function defenitions to an integer type is a hard error + - Casting function pointers or function definitions to an integer type is a hard error - Note that you can still cast function pointers and function item types to `*const V` and `*mut V` where `V: Sized` - Note that function item types can also be casted to function pointers - On `edition < 2024`