Skip to content

Comments

Fix soundness issues with covariant Q argument#21

Merged
uazu merged 16 commits intouazu:masterfrom
steffahn:fix_unsound_covariance
Sep 20, 2021
Merged

Fix soundness issues with covariant Q argument#21
uazu merged 16 commits intouazu:masterfrom
steffahn:fix_unsound_covariance

Conversation

@steffahn
Copy link
Contributor

@steffahn steffahn commented Sep 18, 2021

Closes #20

I didn’t see any good common place to put Invariant<Q> struct, so I put a copy of it in both modules. Edit: Changed in additional commit.

@steffahn
Copy link
Contributor Author

steffahn commented Sep 18, 2021

Technically a breaking change, but also a soundness-bug fix (and it only breaks code that’s very close to exploiting the unsoundness that's being fixed, anyways; in other words, it shouldn't affect sane users of this crate); so a new minor version seems appropriate when releasing this.

Also consider yanking old versions with an unsound API (once a version containing this PR is published)

@steffahn
Copy link
Contributor Author

Added some commits that use Invariant<Q> for the Id<'id> type as-well, without the implication on Syncness that the previously used Cell<T> type implies. This also fixes up the auto implementations of [Ref]UnwindSafe for LCellOwner.

@uazu
Copy link
Owner

uazu commented Sep 20, 2021

Thanks for the PR. I've read the Rustonomicon to understand things better and thought it all through. This all makes sense. Just one thing: I think using a tuple in (*const (), Invariant<Q>) is not necessary. It can just be *const Invariant<Q>. The comment above that line is incorrect -- using the tuple or using Invariant doesn't make any difference to UnwindSafe for TCell/TLCell. The only difference to UnwindSafe is due to the change to Id<'id> in LCell. It's worth getting the comments right, because this part of Rust is confusing enough as it is.

I think there are two options: Either I accept the PR as it is and I fix up things as above, or if you have time you could do it. I don't mind either way. I will add further changes afterwards anyway to add comments referencing the Rustonomicon and justifying it in my own way, to make things clearer for anyone who looks at this later on. Locally I have also added a couple of tests for the problem situation you documented in the bug report, which I will also check in later. These fail before the change and succeed afterwards, as expected, so that is all fine.

As you suggest, I will probably yank all versions back to the first release with TCell in it, once this is published.

@steffahn
Copy link
Contributor Author

I think using a tuple in (*const (), Invariant<Q>) is not necessary. It can just be *const Invariant<Q>.

Ah, right… I guess I compared to *const Q which you’ve had previously (which does put extra restrictions on UnwindSafe).

The comment above that line is incorrect -- using the tuple or using Invariant doesn't make any difference to UnwindSafe for TCell/TLCell.

Let me take another look at those comments.

@steffahn
Copy link
Contributor Author

Just noticing that TCell could simply drop the *const () part and get the Send implementation for free due to UnsafeCell

@steffahn
Copy link
Contributor Author

steffahn commented Sep 20, 2021

Another idea: Following the example of RefCell and Cell, it’s probably a good idea not to implement RefUnwindSafe on any of the *Cell types?

Edit: That would be a non-soundness-related breaking change though, so let’s not do that here.

Nevermind, UnsafeCell already gives us this anyway. I must have misread the docs somehow.

@steffahn
Copy link
Contributor Author

The comment regarding UnwindSafe-ty is comparing Invariant<Q> with other options like Cell<Q> or *mut Q which would also be invariant in Q. So I think it’s correct, though it’s perhaps not clear enough to be understandable.

@steffahn
Copy link
Contributor Author

The question of (*const (), Invariant<Q>) vs *const Invariant<Q> is a question of separation of concerns.

While *const Invariant<Q> works, it feels like hacking together two unrelated things. Maybe even better would be two separate PhantomData fields… the tuple was an attempt to avoid that, but looking at those error messages I’ve blessed, I guess those would become better if it was separate fields

@uazu
Copy link
Owner

uazu commented Sep 20, 2021

So you're suggesting returning to using *const (), but in a separate PhantomData field in order to get clearer error messages? I think this sounds okay if you want to do that.

My own priorities are making the behaviour clear and have comments documenting why things are done as they are. I think the comment about UnwindSafe above uses of Invariant is not relevant to those places. Perhaps it could go on the Invariant definition to explain why that particular implementation of Invariant is used.

TLCell will need similar changes to TCell.

I will add tests after you finish so that the behaviour regarding auto-traits is well-defined and can be tested as Rust updates. Thanks

@steffahn
Copy link
Contributor Author

So I’ve added a few commits now 😃

@steffahn
Copy link
Contributor Author

TLCell will need similar changes to TCell.

right… I forgot that…

@uazu
Copy link
Owner

uazu commented Sep 20, 2021

I think TCell needs to be like TLCell now, i.e. put back in the manual implementation of Send, because that gave a better error message.

@steffahn
Copy link
Contributor Author

steffahn commented Sep 20, 2021

right, the error message would be slightly better for Send, because it wouldn’t mention the step involving UnsafeCell. On the other hand it’s somewhat worse for Sync (for TLCell) because it mentions both the UnsafeCell and the *const () (previously *const Q) when the marker field is present.

@steffahn
Copy link
Contributor Author

I personally feel a bit reluctant about adding an additional marker fields and a manual unsafe impl for Send when UnsafeCell already provides perfectly reasonable defaults.

Actually, I guess the marker fields aren't necessary even with the manual impl. Nonetheless, I feel like UnsafeCell is doing a great job here, providing a conservative choice of !Sync and Send for UnsafeCell<T> where T: Send.

Also, the handling of Send via UnsafeCell is consistent with how QCell and LCell already do it.

@uazu
Copy link
Owner

uazu commented Sep 20, 2021

Yes, that part is all fine. I will add tests to make sure that all the auto-traits come out as expected, so that won't get broken and we'll pick up on anything weird that occurs in Rust's implementation (or they'll pick it up in a crater run).

I was just reading a bit more on the drop checker (since PhantomData<Q> apparently invokes the drop checker (somehow), but PhantomData<Invariant<Q>> won't). But as far as I can see it is not relevant to this situation.

I think things are all pretty much fine to merge now. I'll just go over it all again to be sure though.

@uazu
Copy link
Owner

uazu commented Sep 20, 2021

Thanks for putting back in those comments, BTW. I was going to do the same, but you've already done it so that's great.

@uazu
Copy link
Owner

uazu commented Sep 20, 2021

I'll finish checking things over after lunch and merge then. Thanks!

@uazu uazu merged commit ac0fcb7 into uazu:master Sep 20, 2021
@steffahn steffahn deleted the fix_unsound_covariance branch September 21, 2021 12:20
@uazu
Copy link
Owner

uazu commented Sep 21, 2021

Thanks again for this. I've added a changelog to the release now, and I've put all the credits for contributions in there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TCell is unsound due to covariant Q parameter of TCellOwner (and the same applies to TLCell)

2 participants