-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
clarify requirements of byte_{offset,add,sub}
for zero-sized referents
#133576
base: master
Are you sure you want to change the base?
Conversation
I don't think it should be, I think of So I'm not sure what other behavior you would want for these methods on ZST pointees. Making them do nothing would be a breaking change. |
I think something like this would both behave equivalently for non-zero-sized referents, and also soundly admit any pub unsafe fn byte_offset(self, count: isize) -> *const T {
let (addr, meta) = self.to_raw_parts();
let addr = addr.map_addr(|addr| addr.wrapping_add_signed(count));
super::from_raw_parts(addr as *const (), meta)
} ...but presumably optimize worse, since |
The validity of |
Oh, good point! What do you think about this clarification instead:
|
I am confused by this statement. The rule does apply there as well. The rule applies whenever the operation has size 0. For It seems to me that this PR is trying to fix a non-problem. So please explain the problem. :) |
/// safety requirements. Note that the usual guidance on operations on | ||
/// zero-sized referents does not apply here — the only valid `count` for | ||
/// zero-sized referents is `0`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs for pointer::offset don't even have any guidance about zero-sized references? In fact, neither operation involves any reference at all! Only raw pointers are involved.
I don't understand which problem you see with the current docs, and I think the new sentence is quite confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So to be clear, please undo all these changes to the bytewise offset methods in your PR, and only keep the change in library/core/src/ptr/mod.rs
.
No, this is not equivalent at all. It seems you misunderstood how "If the computed offset is non-zero, then self must be derived from a pointer to some allocated object, and the entire memory range between self and the result must be in bounds of that allocated object." (For two operations to be equivalent, they must produce the same results, and they must also be UB under the same conditions.) Your operation is equivalent to
That's not even a goal for Please write a problem statement explaining your reasoning. Why do you think (quoting from the PR description) "this rule does not apply to these methods"? |
Re. goal: The background for this PR is I was reviewing a contract on
At a glance, this might be read as running counter to the guidance on Perhaps a more apt clarification would be:
Re. semantics:
I understand the difference in pre-conditions between |
No, ptr::offset does not require ZST pointers to be backed by a valid allocation. Maybe you mean ptr::byte_offset. If the offset is non-zero, the pointer must be backed by a valid allocation, even if the pointee type is a ZST. But that is already stated in the rule you quoted: "For operations of size zero, every pointer is valid, including the null pointer." The operation So I don't think there is anything here that needs to be fixed. |
Also note that "For operations of size zero, every pointer is valid, including the null pointer." does not even apply to offset. It is part of the definition of when a pointer is valid for an access. So IOW, (a) the rule you quoted is indeed correct even for offsets, under a reasonable interpretation of the idea of the "size of the operation", and (b) the docs do not even say that the rule applies to |
If the preconditions are met, the return values are the same. However, the fact that the preconditions are different already makes them fundamentally not equivalent, so it is not correct to state that your |
Where the pointer docs say "For operations of size zero, every pointer is valid", "size zero" is actually a hyperlink to the reference section on zero sized types. This, to me, (incorrectly) suggested that the size of the referent was relevant in this case. |
r? @RalfJung |
Note the context of that statement. The paragraph just above says: "Many functions in this module take raw pointers as arguments and read from or write to them. For this to be safe, these pointers must be valid for the given access. Whether a pointer is valid depends on the operation it is used for (read or write), and the extent of the memory that is accessed (i.e., how many bytes are read/written) – it makes no sense to ask “is this pointer valid”; one has to ask “is this pointer valid for a given access”. Most functions use *mut T and *const T to access only a single value, in which case the documentation omits the size and implicitly assumes it to be size_of::() bytes." Why do you think this statement has anything to do with That paragraph also explains where the size comes from: some other parts of the docs will say something like "must be valid for reads of size N", and that is the size. Only if the size is not mentioned does it use the size of the type.
Good point, we should probably remove that link. |
@RalfJung I have a question about the preconditions of
If satisfying the second precondition implies that the first precondition is satisfied, is the first precondition entirely redundant? |
The safety documentation on `core::ptr` presents this rule: > For operations of size zero, every pointer is valid, including the null pointer. However, due to the implementation details of `byte_{offset,add,sub}`, which involve operations on non-zero-sized referents, this rule does not apply to these methods. This commit clarifies extends the over-arching rule with an "unless otherwise noted" caveat, and clarifies the documentation of `byte_{offset,add,sub}` to note that the only valid `count` for zero-sized referents is presently `0`.
c6d48d0
to
e9c0f21
Compare
It is technically redundant, yes. It is explicitly mentioned for clarity, since we don't expect people to know the maximal size of a Rust allocation by heart.
|
I think it would be reasonable to remove the redundancy. A SAFETY proof for an invocation of I'd like to reduce the safety preconditions of
|
@@ -15,7 +15,7 @@ | |||
//! The precise rules for validity are not determined yet. The guarantees that are | |||
//! provided at this point are very minimal: | |||
//! | |||
//! * For operations of [size zero][zst], *every* pointer is valid, including the [null] pointer. | |||
//! * For operations of size zero, *every* pointer is valid, including the [null] pointer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This leaves a dangling [zst]
markdown link further down this file, please also remove that.
That won't do as it leaves it unclear how the offset is computed in terms of overflow. The current wording is the result of quite a bit of discussion to ensure it is understandable and unambiguous. I'm not super excited about the idea of re-litigating that discussion... but I guess you insist. @rust-lang/opsem @rust-lang/libs-api @nikic what is your opinion on removing the mention of the |
@RalfJung I don't think we can remove that part. There are two parts to the operation, one is the multiplication You could specify this as instead performing |
The second bullet point in the current safety specification includes:
Is it possible for |
Or, put another way, the next sentence of the specification is:
I read this as saying "if you prove the second requirement, the first requirement holds by implication". Is that incorrect? |
We can do the |
Yes, if we specify that the whole operation works on unbounded integers, that works. I don't think that formulation is better than the current one though. If we really want to change the way this is currently specified, I think the correct way to do it is to make byte_offset the fundamental operation (which only needs the second bullet point) and then offset is just a byte_offset of unchecked_mul. (Incidentally, this is also how it ought to be implemented, but LLVM may not be ready for that yet in terms of codegen quality.) |
☔ The latest upstream changes (presumably #134952) made this pull request unmergeable. Please resolve the merge conflicts. |
The safety documentation on
core::ptr
presents this rule:However, due to the implementation details of
byte_{offset,add,sub}
, which involve intermediate operations on non-zero-sized referents, this rule does not apply to these methods.This commit clarifies extends the over-arching rule with an "unless otherwise noted" caveat, and clarifies the documentation of
byte_{offset,add,sub}
to note that the only validcount
for zero-sized referents is presently0
(though I wonder if this requirement might be relaxed).cc @rust-lang/opsem