-
Notifications
You must be signed in to change notification settings - Fork 60
Transmute between functions with different return types, where one function never returns #266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
That is actually already done at the abi level without involving any optimizations. The x86_64 System-V abi among other abi's pass a return value pointer to a function when the return value can't be stored in registers. This extra argument always comes first. If it were to come last, it wouldn't really be a problem. At least for most abi's. https://github.com/rust-lang/rust/blob/e261649593cf9c2707f7b30a61c46c4469c67ebb/compiler/rustc_codegen_ssa/src/mir/block.rs#L623 |
For the case of |
It looks like Miri doesn't care about particular ABIs or subtyping and only lets you change the return type if it is a pointer. An example was made where we transmute from an
Thanks. I had a feeling something like this could happen, but wasn't sure of the specifics.
Is the overall area of subtyping function arguments/return values something that is worth specifying for people writing |
I just tried that with
Here I mean subtyping in the dynamic sense, i.e. a function pointer of one kind can be used in all contexts where the other one could be used. This is really what unsafe authors care about, and in fact it may not be possible for the abstract machine to be able to tell the difference between a type and a transparent wrapper type around it, so I don't think it is possible for this to be declared as UB even if we wanted to. In particular, lifetimes don't actually exist in the rust abstract machine, just "stacked borrows" stored in the memory, so you can't use any wording like "lifetimes (respecting variance)" in the definition of whether this is UB. As a more positive proposal, I would suggest not saying anything about function pointer casting being UB; all transmutes are okay, but calling a function requires that the input ABI match the ABI of the function, and returning from a function requires that the output ABI matches as well. This may have the effect of transmuting the function parameters if the ABI matches but the types don't, and this is not UB until the transmuted parameter is used, assuming the types don't match up. |
The ABI considerations of those two types may be different. I think that the original |
This sounds like a pretty reasonable rule-of-thumb 👍 It's also easier to specify what is valid instead of listing a bunch of exceptions where something isn't valid.
This would be a big ticket item for me. For example, I'd really like to declare FFI bindings like You can do that in FFI code at the moment and it works because of |
This is already guaranteed, as part of |
I think you can substitute other types that are as layout equivalent as we have promises for, for example |
|
I'd expect |
Note that the Miri checks for this are just "whatever seemed most sensible to me at the time". I tried to make them restrictive enough so that any code that is allowed is unambiguously correct, but it is very possible that the checks could be relaxed. This requires more knowledge about calling conventions and ABIs than I have, though. ;) |
It may be reasonable to adopt the C rules, which must work on any platform where the underlying ABI is used by C (and if there is no platform C abi, rust can do whatever it wants for
|
@chorman0773 that list is a great starting point, thanks!
C doesn't really have uninhabited types, so I am not entirely sure about this. You talked about By "in return type position", do you also mean nested positions in the return type? Like, is
This seems to be doing a lot, and is getting close to the discussions around I should also point at rust-lang/rust#56166: rustc's |
Well, in the return type position, I believe they are fundamentally equivalent. A function returning
Honestly, I do not know. My intention is that only in the top-level return-type position, that is,
This rule is derived, it's just written for completeness. By definition, a transparent type has the same abi as it's transparent field, so they must be compatible types. For "transparent over" that's fine, it can be defined as the field of the transparent struct which is not a 1-ZST, or
This was not an accident. The assumption is that if you pass in a parameter which is the wrong type, it gets transmuted into the correct one. |
Oh, sure. I am just saying, instead of having to implement your list, maybe it would also work for Miri to just check if the
Well, a #[repr(transparent)]
struct Transparent<T: Trait>(T::Assoc); But I think this is just a matter of notation -- your use of generic notation |
#[repr(transparent)]
struct Transparent<T: Trait>(T::Assoc); That is true, though the definition implies that #[repr(transparent)]
struct Transparent<T: Trait>(T); with optional 1-ZST fields. It can also be a concrete type. As mentioned it's a matter of notation
Possibly, that could work. Though that may not work out using |
For type layouts, the stanza I took with Miri is that it will use whatever layout rustc happens to pick. IOW, Miri can not be used to determine if a program (incorrectly) relies on unspecified layout details. I think it makes sense to do the same for ABI concerns -- if, with whatever ABI rustc happens to pick, the call is well-defined, then Miri will let the program pass. |
Makes sense. In which case, stating that (in the absence of a preceeding rule), type compatibility is unspecified. So under this, it would be valid to rely only on the listed rules (which can be added to), and then miri can just check what rustc does. Adding to this, should enums have compatibility guarantees at all? One thing that should definately be the case is that a |
I think these questions are already covered in other issues, and the UCG document. If there is layout compatibility, I don't see any reason to prevent indirectly transmuting parameters by calling a transmuted function pointer with a compatible ABI. We shouldn't be inventing rules beyond the already relatively well understood rules for layout compatibility (or lack thereof). |
These rules are specifically ones that either would work for any C abi that
rust can operate on, or as necessitated by the rust language. For example,
the rule for structs (and for unions) is a subset of the same from the C
standard. Saying layout compatibility may pose problems, as the layout of
#[repr(C)] struct A(u32); and #[repr(transparent)] struct B(u32); may be
the same, but they may not be compatible in an extern"C" function, because
the host C abi dictates the former is passed on the stack, and the latter
in a register.
…On Sun, Dec 20, 2020 at 17:40 Mario Carneiro ***@***.***> wrote:
I think these questions are already covered in other issues, and the UCG
document
<https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html>.
If there is layout compatibility, I don't see any reason to prevent
indirectly transmuting parameters by calling a transmuted function pointer
with a compatible ABI. We shouldn't be inventing rules beyond the already
relatively well understood rules for layout compatibility (or lack thereof).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#266 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABGLD27YQHMBVUIM7WR7V4TSVZ4ORANCNFSM4U364BUQ>
.
|
I think that would be covered by ABI compatibility of the calls. If one function passes its 32 bit value in register EDI and the other passes it in a stack slot or puts a pointer to the data in EDI then that's an ABI incompatibility, and the function call is UB. |
My rules cover both, which would give the users the benefit of knowing what is not UB. Types which are compatible guaranteed to be both layout compatible (as far as raw data layout is concerned, disreguarding validity invariants) and abi compatible. |
I don't know what the rules are on ABI compatibility for rust-call functions, but I think it needs its own issue. |
This is why I left |
Does this need to have an "and" instead of "or"? Or would accepting a pointer of a different type be equivalent to a normal pointer cast, with all the usual requirements that entails (alignment, out-of-bounds access, etc.). |
"Or" is correct here (and note that references should be included in
pointers). If the pointee is sized, they are compatible. If they are
slices, with the same element type, they are compatible.
…On Mon, Dec 21, 2020 at 03:13 Michael Bryan ***@***.***> wrote:
Two pointer types are compatible if both pointees are Sized, or the
pointee types are compatible
Does this need to have an "and" instead of "or"?
Or would accepting a pointer of a different type be equivalent to a normal
pointer cast, with all the usual requirements that entails (alignment,
out-of-bounds access, etc.).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#266 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABGLD22PR5VOTGIDUNRTIU3SV37S3ANCNFSM4U364BUQ>
.
|
Just be careful about guaranteeing compatibility between two function types if the function signatures as translated to C are different, and not guaranteed compatible by the C standard. This could break under any control-flow integrity scheme that verifies at runtime, before each call to a function pointer, that the called function has a compatible type. Clang supports this in software, and it could also be implemented on top of ARMv8.3 hardware pointer authentication. (That said, Apple's ARM pointer authentication implementation at least does not verify types for C function pointers, since doing so breaks a lot of real code. But that might change in the future. It does already verify types for C++ vtable calls.) Of course, this isn't a problem for types that only exist in Rust, such as |
That's what my list is for. For example, C says that two structs as
compatible if they have the same number of members each corresponding
member in declaration order are either both not bitfields or both are
bitfields with the same width, and both have compatible types. That's where
the repr(C) struct compatibility rule comes from (minus the bitfield part,
because rust does not have bitfields, sometimes to my annoyance). My list
is derived from these rules that any C abi must support, as well as rules
implied by the rust language itself (with a few additional rules that
aren't unreasonable, such as compatibility of all 1-ZSTs with (), and
compatibility of empty enum types and !).
…On Wed, Dec 23, 2020 at 15:36 comex ***@***.***> wrote:
Just be careful about guaranteeing compatibility between two function
types if the function signatures as translated to C are different, and not guaranteed
compatible by the C standard
<https://en.cppreference.com/w/c/language/type#Compatible_types>.
This could break under any control-flow integrity scheme that verifies at
runtime, before each call to a function pointer, that the called function
has a compatible type. Clang supports this in software
<https://clang.llvm.org/docs/ControlFlowIntegrity.html>, and it could
also be implemented on top of ARMv8.3 hardware pointer authentication.
(That said, Apple's ARM pointer authentication implementation at least does
*not* verify types for C function pointers, since doing so breaks a lot
of real code. But that might change in the future. It does already verify
types for C++ vtable calls.)
Of course, this isn't a problem for types that only exist in Rust, such as
! and #[repr(transparent)] structs, where rustc can unilaterally
guarantee that it will treat them, for code generation purposes, as if they
were some canonical C type.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#266 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABGLD23EIIU52ML4KM3TAZ3SWJIGRANCNFSM4U364BUQ>
.
|
In order for something like this to work, I assume that we have to say that the function being used has a type, though, with at least enough specificity for this check to be able to work with it. I think it is reasonable to consider that a separate calling convention of its own, possibly |
@chorman0773 And my point is that even if two types are similar enough that 'any sane ABI' would treat them the same way, if they're different C types and not C-spec compatible, then replacing one with the other in a function signature may actually cause incompatibility in practice – in the presence of a control-flow integrity system which verifies function signature compatibility on all calls through function pointers. I don't think there is anything currently guaranteed by Rust that would prevent it from working with such a control-flow integrity system. Rust does make stronger guarantees than C about in-memory layout, and I think most if not all of your list would be justified there, but not for function calling ABI. (Also, even without CFI, the standard 32-bit ARM ABI sometimes represents a length-1 array differently from its element type.) |
(Sorry for the double post.) Yes, my point about CFI is mainly referring to |
I agree, or rather I think this is an unfortunate overlapping of the meanings of Regarding |
Actually, signed and unsigned integers are compatible, IIRC. |
I'm not particularily attached to the |
A question came up on the community Discord today and I'd like to know if my reasoning was correct.
This was the original question:
And the following conversation ensued:
(I then posted a playground example which runs the transmute with Miri, and Miri complains that it's UB)
Is my reasoning correct that, generally, optimisations like RVO being sound is what makes this transmute is UB?
The text was updated successfully, but these errors were encountered: