-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Arc::ptr_eq
does not always return "true if the two Arcs point to the same allocation" as documented
#103763
Comments
Can you provide a code sample where this happens? I certainly accept that two e.g. when comparing two fat pointers |
Ah, right, I had got the wrong impression that it was going to do In my case I never witnessed a specific error condition but I did get a big surprise when running Clippy and got warned that and then #80505 - which I was very surprised to see had been closed. The Clippy warning is phrased as: "error: comparing trait object pointers compares a non-unique vtable address" and so I probably took my over-simplified understanding of how I can strike out / edit this part of my issue description to address my misunderstanding here but I think the core of the issue still stands. Thanks for the clarification. |
I think this has been fixed by #103567? The docs now clarify the semantics of |
From the So I think the clarification for how It's only the current implementation of The documentation for The docs don't say anything about comparing pointers to the wrapped values of an Arc. If someone needs to do that then they could call I believe the currently documented semantics are precisely what users of the API are expecting, for being able to determine if separate references point back to common state. (At least for the multiple projects I have using this API I had never imagined the implementation might also compare meta data for the wrapped value and never considered what unexpected implications that could have). This issue is suggesting that the implementation should probably be changed to effectively compare the "allocation" pointers - which is not the same thing as comparing (potentially wide) pointers to the wrapped values. I think one possible solution could be to compare the pointers to the values after stripping away any meta data. Another solution could be to compare the heap pointers to the inner |
That PR also changes the
That is your interpretation of the docs, but the docs also say that it works like
This comment has already gotten a reply almost 2 years ago:
You are just repeating what has already been said many times. This is not helping. Don't try to interpret the scripture that is comments and argue that your interpretation is better than someone else's. It's not going to end well for anyone. Instead make an independent argument for why the behavior should be the way you want it, why that is better than the current behavior, also taking into account
I'll nominate this for discussion by @rust-lang/libs-api and will see myself out since I don't actually have any skin in this game, all I care about is having clearly documented. Last time, it looks like the team was torn -- @m-ou-se said the team preferred the 'thin ptr only' semantics but @SimonSapin had concerns. |
Right, my interpretation has always been that it does a thin pointer comparison for the backing storage allocation and so I was surprised to see a Clippy warning showing that that's not what the current implementation does and then worried about the implications of other interpretations on the correctness of my code. I (personally) saw no inconsistency / conflict with the docs saying that the function works "(in a vein similar to ptr::eq)" because I believe that a valid comparison of the allocation pointers could be literally done using I see nothing to imply that pointers to the wrapped values will be compared which is what introduces all this ambiguity about how wide pointers / meta data should be handled. Sorry that you see that I'm repeating things that were discussed before. The previous pull request that raised this was closed and so there was nothing to track the the issue. Also regarding scripture / re-interpretation of comments this is feeling somewhat more heated than I expected. I'm not trying to just disregard other opinions / comments. I tried to lay out a considered break down above of how I see this issue. I didn't simply assert that I disagree with someone's interpretation and that my interpretation is right, but sorry that it came across like that for you. My impression from #80505 was that it was previously agreed that |
This is interesting for sure. (Personally) I find that the updated documentation from #103567 is more confusing for me. To me the initial statement of what I think it would be good for the docs to be much more explicit either way about the semantics here. I feel like the docs should either:
If going with the former it would be interesting to understand when/why someone would want to compare Arcs and take into account meta data for the wrapped value. Any use case I have had for |
We discussed this in the libs-api meeting and think this might be better solved at the language level by making It seems like the current behavior comes from rust-lang/rfcs#1135 (comment) which was a decision made when pointer equality for fat pointers was initially added to the language. |
Thanks for discussing the topic in the libs team. I think there's a risk we're conflating two distinct issues/questions here and it could be good to find a way of tracking them both:
When opening this issue I think I was hoping we'd be able to focus on the first question - deciding what address @Amanieu would it perhaps be possible to, more specifically, raise the first question with the libs team? It seems hard to see how the Previously from #80505 (comment) it looked like the libs team agreed that it should refer to the allocation that holds the counters. If that still holds then that may imply that the semantics for Answering the first question should enable developers to know whether they need to manually strip the meta data from wide pointers if they know they want to disregard meta data associated with the value wrapped by the I.e. even if the semantics for |
This was discussed in T-lang triage meeting yesterday. I do not agree that this is better solved by making The team does agree that you cannot rely on two dyn trait pointer comparisons to reliably tell you that the two objects denote the same thing, due to implementation artifacts such as having multiple copies of the vtable embedded in the object binary. However, it would be a mistake to address this by making My own intuition about how I believe the Rust languages tries to follow the above intuition today2. Thus, one's business logic can use The change to
As a concrete example of the latter, I submit the following example code (playground): trait Trait { fn msg(&self) -> String; }
fn pair(a: *const dyn Trait, b: *const dyn Trait) {
let a_msg; let b_msg;
unsafe {
a_msg = a.as_ref().unwrap().msg();
b_msg = b.as_ref().unwrap().msg();
}
let a_data_addr = a as *const ();
let b_data_addr = b as *const ();
// The Question: for non-null a, b,
// if `a_data_addr == b_data_addr`,
// can you ever have `a_msg != b_msg` ... ?
if a_msg != b_msg && a_data_addr == b_data_addr {
println!("Found one!");
dbg!(a_data_addr);
dbg!(b_data_addr);
dbg!(a);
dbg!(b);
dbg!(a_msg);
dbg!(b_msg);
}
}
// ... and the answer is, yes you can:
fn main() {
let zm = (Zst, Msg("good gravy"));
let zm: (&dyn Trait, &dyn Trait) = (&zm.0, &zm.1);
pair(zm.0, zm.1);
} Thus, I do not think just comparing the data addresses of a Footnotes
|
@rustbot label: -I-lang-nominated |
In the example on top of this thread, if you change both T::f impls to be identical (e.g. make both print "A"), the program will print |
FCP for fixing the issue for Arc::ptr_eq by ignoring the pointer metadata (so, only comparing the pointer address), such that the example above returns @rfcbot merge See the first comment in this thread for details and rationale. |
Team member @m-ou-se has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Due to a clippy warning about Arc::ptr_eq potentially comparing vtable meta data associated with wide pointers there was a recent change to explicitly only check the address pointers and discard any wide pointer meta data. The Rust libs team has since resolved to update Arc::ptr_eq so that it doesn't compare meta data for the wrapped value (such as a dyn trait vtable): rust-lang/rust#103763 In the meantime we can silence this clippy suggestion since the vtable is benign in this case anyway: rust-lang/rust-clippy#6524
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
I opened #106447 to discuss a language change to pointer equality. In the meantime the discussion on this issue seems to be done, we just need a PR to change the behavior of |
@rustbot claim |
Does the FCP also covers the |
Yes. |
Make `{Arc,Rc,Weak}::ptr_eq` ignore pointer metadata FCP completed in rust-lang/rust#103763 (comment) Closes #103763
Make `{Arc,Rc,Weak}::ptr_eq` ignore pointer metadata FCP completed in rust-lang/rust#103763 (comment) Closes #103763
Make `{Arc,Rc,Weak}::ptr_eq` ignore pointer metadata FCP completed in rust-lang/rust#103763 (comment) Closes #103763
Make `{Arc,Rc,Weak}::ptr_eq` ignore pointer metadata FCP completed in rust-lang/rust#103763 (comment) Closes #103763
Make `{Arc,Rc,Weak}::ptr_eq` ignore pointer metadata FCP completed in rust-lang/rust#103763 (comment) Closes #103763
Edited: to account for an initial misunderstanding about how
dyn Trait
wide pointers are comparedTL;DR
The implementation of
Arc::ptr_eq
is implemented based on comparing pointers to the inner value (not something that the documentation suggests/requires), which may involve comparing wide pointer meta data that is not relevant to it's stated function - and could lead to false negativesHow
Arc::ptr_eq
is documented, and current expectations:For reference here this is the (I think concise) documentation for
Arc::ptr_eq
:My expectation from this is rather simple: the implementation will get the pointer for the
Box
/"allocation" that is allocated as the backing storage for two arcs (holding the reference count and inner value) and it will compare those pointers withptr::eq
.Notably: the wrapped type should have no bearing on the implementation / semantics here, since it's the pointer of the "allocation" that is supposed (documented) to be compared.
This way you can create any number of references for a single
Arc
that wraps some inner value and cheaply determine whether twoArc
s point back to the same state allocation.I've been relying on those semantics for
Arc::ptr_eq
across a number of projects for exactly that purpose.For a few projects I should also note that I have generalized how I wrap state in an
Arc
as an aid for also exposing that state across FFI boundaries. I note this just to highlight the separation of concerns that I have relied on - namely that I assume I can useArc::ptr_eq
to check whether two Arcs point to the same state without having to worry about what kind of type is wrapped by the Arc.Actual implementation of
Arc::ptr_eq
:Instead of comparing the pointers of the backing "allocation", the current implementation of
Arc::ptr_eq
instead figures out the offset pointer to the inner value and does a comparison of those value pointers with semantics that could vary depending on what is wrapped by the Arc.I.e. it does
Arc::as_ptr(&a) == Arc::as_ptr(&b)
edited, to address an initial misunderstanding:
In particular if an Arc contains adyn Trait
then the pointers would be wide/fat pointers and the comparison will compare the vtable pointer for those trait objects which could be equal even though the twoArc
s are otherwise unrelated, not referencing the same "allocation". (I.e. it could returntrue
for two Arcs that don't point to the same allocation)In particular if an Arc contains a
dyn Trait
then the pointers would be wide/fat pointers and the comparison willcheck that
target_a == target_b && vtable_a == vtable_b
which makes it possible for the comparison to returnfalse
for twoArc
s that have the same allocations but differing vtables.Example
Based on #80505 (comment)
Discussion
I'm not sure what safety rules have been established for transmuting
Arc
s in such a way as to hit this but the more general consideration here is that Rust supports the notion of wide pointers with meta data whereby that meta data may affect pointer comparisons in various ways depending on the types involved.The
Arc::ptr_eq
API is specifically documented to compare whether the "allocation" of two Arcs are the same and so I don't believe that the semantics for comparing any form of wide pointer to the inner value should be relevant here.Additionally comparing meta data associated with the inner value poses a rather open-ended risk that this function may deviate from its documented function - something that might change further if new types of wide pointers + meta data are added to Rust in the future.
Especially in the context of state that's being shared over unsafe FFI boundaries I think the potential for deviation from the documentation could be rather dangerous.
Even unrelated to FFI this breaks the basic notion that you can rely on this API compare whether two Arcs reference the same state which may easily break logical invariants that code relies on, for example to implement the
Eq
trait of a higher level type.As it happens I discovered this in a project for Json Web Tokens after I introduced Clippy to the project's CI. There was some validation state that was wrapped in an
Arc
(closures that the application could add for validating a token) and discovering that the logic for comparing this validation state is broken by this was particularly concerning for me.Duplicate issue
This issue has been discussed before as #80505 but I think it was, perhaps, a mistake that the discussion focused on the question of consistency between
ptr::eq
andArc::ptr_eq
in terms of how wide pointers should be compared.The documentation for
Arc::ptr_eq
gives no suggestion that it is comparing pointers to the wrapped type and so (imho) there is no cause for concern regarding the consistent handling of wide pointer comparisons.Even considering the mention of
ptr::eq
in the documentation where it says:there is no implication that the inner type of the
Arc
affects what pointers are being compared.ptr::eq
should conceptually be used to compare the pointers to theArc
s inner allocation, which should just be the raw (non-wide) pointer for theBox
that encompasses theArc
s reference count. In this way the comparison is done "in a vein similar toptr::eq
" (it may literally useptr::eq
internally).Previous agreement that this is a bug
The discussion for #80505 seemed to go around in circles but it seems important to highlight that the libraries team did discuss the issue and actually agreed that it was a bug #80505 (comment)
With that agreement it's hard to understand why #80505 was closed. (It looks like it was essentially closed since it was a pull request not an issue, and there had been some questions raised about the patch that maybe weren't addressed.)
Meta
rustc --version --verbose
:The text was updated successfully, but these errors were encountered: