Skip to content
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

Add the Allowed type to libtock_platform. #222

Merged
merged 3 commits into from
Aug 5, 2020
Merged

Conversation

jrvanwhy
Copy link
Collaborator

Allowed is the type that will be returned by Platform::allow() in the successful case. It represents a memory buffer shared with the kernel. It uses raw pointers and volatile writes to perform read/write accesses to the memory to avoid encountering undefined behavior.

This is the first step towards fixing #129 and #143.

Allowed is the type that will be returned by Platform::allow() in the successful case. It represents a memory buffer shared with the kernel. It uses raw pointers and volatile writes to perform read/write accesses to the memory to avoid encountering undefined behavior.

This is the first step towards fixing tock#129 and tock#143.
@jrvanwhy
Copy link
Collaborator Author

The Travis failure is due to #225 and is unrelated to this PR; please review this PR when you get a chance.

Copy link
Contributor

@hudson-ayers hudson-ayers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR contains code that it is very important to get right, as mistakes could lead to very subtle and hard to trace bugs, so I tried to review it closely. In general, everything seems right to me. However, the comments about dropping Allowed values after replacing them seemed a little misleading to me.

In general, it almost feels like it might be better to disallow placing anything with a Drop implementation in an Allowed type, as it is impossible to know whether drop() will be called or not. But perhaps I am missing something.

Comment on lines 55 to 57
// Sets the value in the buffer. Note that unlike `core::cell::Cell::set`,
// `Allowed::set` will not drop the existing value. To drop the existing
// value, use `replace` and drop the return value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this by modifying replace(), and it does seem to be generally true.

#[test]
fn replace() {
    let dropped1 = core::cell::Cell::new(false);
    let dropped2 = core::cell::Cell::new(false);
    let dropped3 = core::cell::Cell::new(false);
    let mut buffer = DropCheck {
        flag: Some(&dropped1),
        value: 1,
    };
    let (allowed, kernel_ptr) = KernelPtr::allow(&mut buffer);
    assert_eq!(kernel_ptr.value(), 1);
    assert_eq!(dropped1.get(), false);

    // Simulate the kernel replacing the value in buffer. We don't drop the
    // existing value.
    kernel_ptr.set(DropCheck {
        flag: Some(&dropped2),
        value: 2,
    });
    let returned = allowed.replace(DropCheck {
        flag: Some(&dropped3),
        value: 3,
    });
    assert_eq!(returned.value, 2);
    assert_eq!(kernel_ptr.value(), 3);
    assert_eq!(dropped1.get(), false);
    drop(returned); // <-- Added by me
    assert_eq!(dropped1.get(), false); // <-- added by me
    assert_eq!(dropped2.get(), true); // <-- modified by me
    assert_eq!(dropped3.get(), false);
}

However, this behavior is definitely somewhat confusing, as the caller of replace cannot know whether the destructor will be called for the item originally inserted from the userspace side, or for the item inserted by the kernel.

It feels like it would be a mistake for any userspace implementation to rely on drop being called for values that have been Allowed, but I read this comment as suggesting that doing so is possible. Perhaps it should be reworded? Alternatively, perhaps I am misunderstanding?

core/platform/src/allows/allowed_tests.rs Show resolved Hide resolved
@jrvanwhy
Copy link
Collaborator Author

jrvanwhy commented Aug 4, 2020

In general, it almost feels like it might be better to disallow placing anything with a Drop implementation in an Allowed type

In principle, I agree. Putting a Drop type into a shared buffer seems like a footgun to me. However, the only way to disallow Drop is to require Copy, which seems unnecessarily restrictive. The behavior I implemented was to allow non-Copy types, but make sure that Allowed does not drop values. It may make sense to document that on Allowed directly rather than implicitly through member documentation.

Another thing we could do is to put a Copy bound on set, but still allow replace and take to be used with Drop types.

... or I could remove support for Drop types entirely.

What do you think?

@hudson-ayers
Copy link
Contributor

In principle, I agree. Putting a Drop type into a shared buffer seems like a footgun to me. However, the only way to disallow Drop is to require Copy, which seems unnecessarily restrictive. The behavior I implemented was to allow non-Copy types, but make sure that Allowed does not drop values. It may make sense to document that on Allowed directly rather than implicitly through member documentation.

Another thing we could do is to put a Copy bound on set, but still allow replace and take to be used with Drop types.

Hmm...I agree that requiring Copy seems too restrictive -- slices are not Copy (rust-lang/rust#30483), and it definitely feels like we should support allowing slices. I also feel that having to use a different API for types that implement Drop is confusing in itself - while it would indicate to users that they should be careful, there would be lots of false positives for things like slices which have no Drop implementation but are not Copy. It is too bad something like rust-lang/rfcs#1148 is not possible.

... or I could remove support for Drop types entirely.

What do you think?

After thinking about this some more, I think that it is better to just leave things as-is but to document the potential failure of drops directly on Allowed, and make it clear in the documentation that Allowing a Drop type should only be done after careful consideration.

If there were a way to disallow Drop without requiring Copy I might feel differently :/

@jrvanwhy
Copy link
Collaborator Author

jrvanwhy commented Aug 5, 2020

Hmm...I agree that requiring Copy seems too restrictive -- slices are not Copy (rust-lang/rust#30483),

That only applies to dynamically-sized slices. Arrays are Copy if the type they contain is Copy. Also, I will be introducing an AllowedSlice type soon that would be better for working with arrays.

@hudson-ayers
Copy link
Contributor

Hmm...I agree that requiring Copy seems too restrictive -- slices are not Copy (rust-lang/rust#30483),

That only applies to dynamically-sized slices. Arrays are Copy if the type they contain is Copy. Also, I will be introducing an AllowedSlice type soon that would be better for working with arrays.

I was imagining that it would be common to share a portion of a userspace array with the Kernel (I had in mind a packet buffer in userspace, but only the portion of the packet that the app actually populates with data gets Allowed), which I believe would require slices. However if there is a separate AllowedSlice type that mitigates my concerns.

Outside of arrays/slices, I am not sure whether disallowing non-Copy types will lead to more headaches than removing the Drop footgun will save. Currently it does not seem that any libtock-rs driver allows() any Drop types or any non-Copy types, so experience is not a useful teacher. I am inclined to defer to your judgement on this :)

Originally, I build Allowed so it can work with non-Copy types, which allows Drop types. The semantics of storing non-Copy and non-Drop types in shared memory, however, are somewhat nonobvious. Based on code review feedback, and being particularly cautious because Allowed uses `unsafe`, I decided to add a Copy constraint to Allowed.
@jrvanwhy
Copy link
Collaborator Author

jrvanwhy commented Aug 5, 2020

Because Allowed uses unsafe code, I decided to take the conservative route and add a Copy bound.

Copy link
Contributor

@hudson-ayers hudson-ayers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@jrvanwhy
Copy link
Collaborator Author

jrvanwhy commented Aug 5, 2020

I believe this PR is ready to merge.

@hudson-ayers
Copy link
Contributor

bors r+

@bors
Copy link
Contributor

bors bot commented Aug 5, 2020

Build succeeded:

@bors bors bot merged commit b13550b into tock:master Aug 5, 2020
@jrvanwhy jrvanwhy deleted the allowed branch November 11, 2020 03:00
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.

2 participants