-
Notifications
You must be signed in to change notification settings - Fork 0
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
Make unsafe APIs more narrow in scope #1
Comments
Hey Nathan,
Thanks for letting us have the OpenEXR crate, and bringing up this issue.
I could be wrong, but the APIs you brought up are something we're looking
into marking as unsafe. We're thinking of allowing certain APIs in the
bindings we write be marked as unsafe, and to have a safe layer written in
top at a later date (discussion is here:
vfx-rs/organization#9)
I am not sure which API we are planning on pulling out and mark unsafe
instead, but it could be this one. Anders would know more.
…On Thu, Oct 14, 2021, 12:35 PM Nathan Vegdahl ***@***.***> wrote:
Hello! First: thanks for all the work you guys have put into building this
new version of the Rust bindings to OpenEXR. I maintained the previous set
of bindings along with Benjamin Saunders, so I know how much work it can
be, and how hard it can be to get right!
Anyway, browsing through the documentation, I was confused that Slice
<https://docs.rs/openexr/0.10.1/openexr/core/frame_buffer/struct.Slice.html>'s
constructors weren't marked as unsafe, since they take raw pointers which
can't be bounds checked.
Of course, it's perfectly fine to create and store raw pointers--doing so
isn't unsafe. It isn't until you operate on them that it actually becomes
unsafe. So after a bit of thought, it became clear that, indeed, the Slice
constructors aren't unsafe in isolation. This crate instead makes the
choice to mark e.g. write_pixels() as unsafe, since that's where the Slice
is actually operated on.
While this is perfectly fine from a technical standpoint, from an API
standpoint it doesn't feel great. If something goes wrong (e.g. a segfault)
when calling write_pixels(), then from the standpoint of someone who
isn't familiar with the crate's implementation, almost anything done up to
that point could be the source of the problem. Any of the things passed to
write_pixels() could be bogus in some way. In other words, it makes the
possible sources of unsafety very broad and non-specific.
We can make the unsafety in the API more narrow (in some sense) by marking
write_pixels() as safe, and instead marking as unsafe any places where
(unsafely) bogus data can be constructed. For example, the Slice
constructors that take raw pointers.
This is similar to the approach taken by e.g. str slices in Rust. Instead
of marking every method that *operates* on str slices unsafe, they just
mark functions that have the potential to construct bogus str slices as
unsafe. This is a much narrower surface area in the API, and makes it far
more specific where something may have gone wrong.
Granted, Slice will need to be changed a bit to accommodate this.
Specifically, it currently lacks a lifetime parameter. But it should
probably have a lifetime parameter anyway: it does, after all, represent a
reference to data.
This would also open up the possibility of actually safe Slice
constructors, that simply take a (Rust) slice to the data rather than a raw
pointer, and can properly validate things. This would provide a fully safe
API through the entire OpenEXR pipeline, and would just generally make the
APIs harder to accidentally misuse.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#1>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAVFQOCV2SC6JPD7UCQWSG3UG4WIZANCNFSM5GAM45IQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
Hi Nathan, thanks for your feedback! As Scott said, I'm actually doing exactly this right now. If you've got any other thoughts we'd love to hear them given your previous experience. I'm taking the approach of marking only the functions that can actually cause memory issues as unsafe, which in this case is basically read/writre_pixels and friends. I did toy around a bit with adding lifetimes to Slice, but in the end decided against it as you quickly end up in borrow checker hell if you actually want to do anything with the memory the Slice is referencing. I started working on a different safe API called "Frame" which you can see here openexr-rs/src/core/frame_buffer.rs Line 560 in 0a0590b
This takes a slightly different approach of taking ownership of the memory that will be read to/written and wrapping up the multi-stage Slice stuff in a typestate so that the API can be completely safe. This works pretty well for flat images, but is a bit more troublesome for deeps because of the dependency between the sampleCount Slice and the others. It's also obviously a little more limited than the general Slice API because you have to transfer ownership of the memory to the FramBuffer and then back again, which depending on what you're doing may or may not be a problem, which is why I've left the original unsafe API there too. Longer term we're hoping to help upstream rework the C++ FrameBuffer API a bit to be safer for all (it seems everyone involved realizes it's not the best design as it currently stands). |
Ah! That's great! I didn't notice that on my first pass through the documentation. That's what I get for skimming, ha ha. It would still be nice to have a safe API that doesn't require openexr to actually allocate and own the image data itself. But for a lot of use cases that's probably fine.
Oh yeah, for sure. I'm definitely not advocating to remove the unsafe versions of the APIs--they're really useful. We had a similar distinction in our previous wrapper: the "happy path" completely safe APIs, and the "full flexibility" unsafe APIs. I think that's a great approach.
Ah, yeah, that's fair. The way we dealt with that in the old bindings was that we didn't expose the slice concept at all, and just directly used let mut fb = FrameBuffer::new(256, 256);
fb.insert_channels(&["R", "G", "B"], &pixel_data); The I'm not sure if that would be a good fit for the new bindings, which feel like a thinner layer over the C++ API than the old bindings. But maybe there's some inspiration you can take from that. Here's the old FrameBuffer API docs if you want to take a peek. |
Thanks! Honestly I was also thinking to just completely internalize FrameBuffer to the read/write methods. Covers 99% of use cases, is simple and can be completely safe... |
Do you mean instead of eliminating If we make exr_file.write(&[
(&["R", "G", "B"], Slice::from_interleaved_f32(3, &pixel_data)),
(&["Z"], Slice::from_f32(&z_data)),
]);
other_exr_file.read(&[
(&["R", "G", "B"], SliceMut::from_interleaved_f32(3, &mut other_pixel_data)),
(&["Z"], SliceMut::from_f32(&mut other_z_data)),
]); Or maybe exr_file.write(&[
Slice::from_interleaved_f32(&["R", "G", "B"], &pixel_data),
Slice::from_f32("Z", &z_data),
]); In both cases, we also then have the benefit of eliminating heap allocation due to the It would also make it easier to give My gut feeling is that basically what we want is to somehow flatten the whole (I could, of course, be missing something here.) |
Hello! First: thanks for all the work you guys have put into building this new version of the Rust bindings to OpenEXR. I maintained the previous set of bindings along with Benjamin Saunders, so I know how much work it can be, and how hard it can be to get right!
Anyway, browsing through the documentation, I was confused that
Slice
's constructors weren't marked as unsafe, since they take raw pointers which can't be bounds checked.Of course, it's perfectly fine to create and store raw pointers--doing so isn't unsafe. It isn't until you operate on them that it actually becomes unsafe. So after a bit of thought, it became clear that, indeed, the
Slice
constructors aren't unsafe in isolation. This crate instead makes the choice to mark e.g.write_pixels()
as unsafe, since that's where theSlice
is actually operated on.While this is perfectly fine from a technical standpoint, from an API standpoint it doesn't feel great. If something goes wrong (e.g. a segfault) when calling
write_pixels()
, then from the standpoint of someone who isn't familiar with the crate's implementation, almost anything done up to that point could be the source of the problem. Any of the things passed towrite_pixels()
could be bogus in some way. In other words, it makes the possible sources of unsafety very broad and non-specific.We can make the unsafety in the API more narrow (in some sense) by marking
write_pixels()
as safe, and instead marking as unsafe any places where (unsafely) bogus data can be constructed. For example, theSlice
constructors that take raw pointers.This is similar to the approach taken by e.g.
str
slices in Rust. Instead of marking every method that operates onstr
slices unsafe, they just mark functions that have the potential to construct bogusstr
slices as unsafe. This is a much narrower surface area in the API, and makes it far more specific where something may have gone wrong.Granted,
Slice
will need to be changed a bit to accommodate this. Specifically, it currently lacks a lifetime parameter. But it should probably have a lifetime parameter anyway: it does, after all, represent a reference to data.This would also open up the possibility of actually safe
Slice
constructors, that simply take a (Rust) slice to the data rather than a raw pointer, and can properly validate things. This would provide a fully safe API through the entire OpenEXR pipeline, and would just generally make the APIs harder to accidentally misuse.The text was updated successfully, but these errors were encountered: