Mercurial > hg
comparison rust/hg-cpython/src/ancestors.rs @ 51255:24d3298189d7
rust-index: document safety invariants being upheld for every `unsafe` block
We've added a lot of `unsafe` code that shares Rust structs with Python.
While this is unfortunate, it is also unavoidable, so let's at least
systematically explain why each call to `unsafe` is sound.
If any of the unsafe code ends up being wrong (because everyone screws up
at some point), this change at least continues the unspoken rule of always
explaining the need for `unsafe`, so we at least get a chance to think.
author | Raphaël Gomès <rgomes@octobus.net> |
---|---|
date | Thu, 23 Nov 2023 03:41:58 +0100 |
parents | 59d81768ad6d |
children | c4cbb515b006 |
comparison
equal
deleted
inserted
replaced
51254:f94c10334bcb | 51255:24d3298189d7 |
---|---|
58 // | 58 // |
59 // It would be nice for UnsharedPyLeaked to provide this directly as a variant | 59 // It would be nice for UnsharedPyLeaked to provide this directly as a variant |
60 // of the `map` method with a signature such as: | 60 // of the `map` method with a signature such as: |
61 // | 61 // |
62 // ``` | 62 // ``` |
63 // unafe fn map_or_err(py: Python, | 63 // unsafe fn map_or_err(py: Python, |
64 // f: impl FnOnce(T) -> Result(U, E), | 64 // f: impl FnOnce(T) -> Result(U, E), |
65 // convert_err: impl FnOnce(Python, E) -> PyErr) | 65 // convert_err: impl FnOnce(Python, E) -> PyErr) |
66 // ``` | 66 // ``` |
67 // | 67 // |
68 // This would spare users of the `cpython` crate the additional `unsafe` deref | 68 // This would spare users of the `cpython` crate the additional `unsafe` deref |
69 // to inspect the error and return it outside `UnsafePyLeaked`, and the | 69 // to inspect the error and return it outside `UnsafePyLeaked`, and the |
70 // subsequent unwrapping that this function performs. | 70 // subsequent unwrapping that this function performs. |
72 py: Python, | 72 py: Python, |
73 leaked: UnsafePyLeaked<Result<T, E>>, | 73 leaked: UnsafePyLeaked<Result<T, E>>, |
74 convert_err: impl FnOnce(Python, E) -> PyErr, | 74 convert_err: impl FnOnce(Python, E) -> PyErr, |
75 ) -> PyResult<UnsafePyLeaked<T>> { | 75 ) -> PyResult<UnsafePyLeaked<T>> { |
76 // Result.inspect_err is unstable in Rust 1.61 | 76 // Result.inspect_err is unstable in Rust 1.61 |
77 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
77 if let Err(e) = *unsafe { leaked.try_borrow(py)? } { | 78 if let Err(e) = *unsafe { leaked.try_borrow(py)? } { |
78 return Err(convert_err(py, e)); | 79 return Err(convert_err(py, e)); |
79 } | 80 } |
81 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
80 Ok(unsafe { | 82 Ok(unsafe { |
81 leaked.map(py, |res| { | 83 leaked.map(py, |res| { |
82 res.expect("Error case should have already be treated") | 84 res.expect("Error case should have already be treated") |
83 }) | 85 }) |
84 }) | 86 }) |
87 py_class!(pub class AncestorsIterator |py| { | 89 py_class!(pub class AncestorsIterator |py| { |
88 data inner: RefCell<UnsafePyLeaked<VCGAncestorsIterator<PySharedIndex>>>; | 90 data inner: RefCell<UnsafePyLeaked<VCGAncestorsIterator<PySharedIndex>>>; |
89 | 91 |
90 def __next__(&self) -> PyResult<Option<PyRevision>> { | 92 def __next__(&self) -> PyResult<Option<PyRevision>> { |
91 let mut leaked = self.inner(py).borrow_mut(); | 93 let mut leaked = self.inner(py).borrow_mut(); |
94 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
92 let mut inner = unsafe { leaked.try_borrow_mut(py)? }; | 95 let mut inner = unsafe { leaked.try_borrow_mut(py)? }; |
93 match inner.next() { | 96 match inner.next() { |
94 Some(Err(e)) => Err(GraphError::pynew_from_vcsgraph(py, e)), | 97 Some(Err(e)) => Err(GraphError::pynew_from_vcsgraph(py, e)), |
95 None => Ok(None), | 98 None => Ok(None), |
96 Some(Ok(r)) => Ok(Some(PyRevision(r))), | 99 Some(Ok(r)) => Ok(Some(PyRevision(r))), |
97 } | 100 } |
98 } | 101 } |
99 | 102 |
100 def __contains__(&self, rev: PyRevision) -> PyResult<bool> { | 103 def __contains__(&self, rev: PyRevision) -> PyResult<bool> { |
101 let mut leaked = self.inner(py).borrow_mut(); | 104 let mut leaked = self.inner(py).borrow_mut(); |
105 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
102 let mut inner = unsafe { leaked.try_borrow_mut(py)? }; | 106 let mut inner = unsafe { leaked.try_borrow_mut(py)? }; |
103 inner.contains(rev.0) | 107 inner.contains(rev.0) |
104 .map_err(|e| GraphError::pynew_from_vcsgraph(py, e)) | 108 .map_err(|e| GraphError::pynew_from_vcsgraph(py, e)) |
105 } | 109 } |
106 | 110 |
134 initrevs: PyObject, | 138 initrevs: PyObject, |
135 stoprev: PyRevision, | 139 stoprev: PyRevision, |
136 inclusive: bool, | 140 inclusive: bool, |
137 ) -> PyResult<AncestorsIterator> { | 141 ) -> PyResult<AncestorsIterator> { |
138 let index = py_rust_index_to_graph(py, index)?; | 142 let index = py_rust_index_to_graph(py, index)?; |
143 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
139 let initvec: Vec<_> = { | 144 let initvec: Vec<_> = { |
140 let borrowed_idx = unsafe { index.try_borrow(py)? }; | 145 let borrowed_idx = unsafe { index.try_borrow(py)? }; |
141 rev_pyiter_collect(py, &initrevs, &*borrowed_idx)? | 146 rev_pyiter_collect(py, &initrevs, &*borrowed_idx)? |
142 }; | 147 }; |
148 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
143 let res_ait = unsafe { | 149 let res_ait = unsafe { |
144 index.map(py, |idx| { | 150 index.map(py, |idx| { |
145 VCGAncestorsIterator::new( | 151 VCGAncestorsIterator::new( |
146 idx, | 152 idx, |
147 initvec.into_iter().map(|r| r.0), | 153 initvec.into_iter().map(|r| r.0), |
165 data stoprev: PyRevision; | 171 data stoprev: PyRevision; |
166 data inclusive: bool; | 172 data inclusive: bool; |
167 | 173 |
168 def __contains__(&self, rev: PyRevision) -> PyResult<bool> { | 174 def __contains__(&self, rev: PyRevision) -> PyResult<bool> { |
169 let leaked = self.inner(py).borrow(); | 175 let leaked = self.inner(py).borrow(); |
176 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
170 let inner: &RefCell<VCGLazyAncestors<PySharedIndex>> = | 177 let inner: &RefCell<VCGLazyAncestors<PySharedIndex>> = |
171 &*unsafe { leaked.try_borrow(py)? }; | 178 &*unsafe { leaked.try_borrow(py)? }; |
172 let inner_mut: &mut VCGLazyAncestors<PySharedIndex> = | 179 let inner_mut: &mut VCGLazyAncestors<PySharedIndex> = |
173 &mut *inner.borrow_mut(); | 180 &mut *inner.borrow_mut(); |
174 inner_mut.contains(rev.0) | 181 inner_mut.contains(rev.0) |
183 *self.inclusive(py)) | 190 *self.inclusive(py)) |
184 } | 191 } |
185 | 192 |
186 def __bool__(&self) -> PyResult<bool> { | 193 def __bool__(&self) -> PyResult<bool> { |
187 let leaked = self.inner(py).borrow(); | 194 let leaked = self.inner(py).borrow(); |
195 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
188 let inner = unsafe { leaked.try_borrow(py)? }; | 196 let inner = unsafe { leaked.try_borrow(py)? }; |
189 let empty = inner.borrow().is_empty(); | 197 let empty = inner.borrow().is_empty(); |
190 Ok(!empty) | 198 Ok(!empty) |
191 } | 199 } |
192 | 200 |
198 inclusive: bool | 206 inclusive: bool |
199 ) -> PyResult<Self> { | 207 ) -> PyResult<Self> { |
200 let cloned_index = index.clone_ref(py); | 208 let cloned_index = index.clone_ref(py); |
201 let index = py_rust_index_to_graph(py, index)?; | 209 let index = py_rust_index_to_graph(py, index)?; |
202 let initvec: Vec<_> = { | 210 let initvec: Vec<_> = { |
211 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
203 let borrowed_idx = unsafe {index.try_borrow(py)?}; | 212 let borrowed_idx = unsafe {index.try_borrow(py)?}; |
204 rev_pyiter_collect(py, &initrevs, &*borrowed_idx)? | 213 rev_pyiter_collect(py, &initrevs, &*borrowed_idx)? |
205 }; | 214 }; |
206 | 215 |
216 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
207 let res_lazy = | 217 let res_lazy = |
208 unsafe { index.map(py, |idx| VCGLazyAncestors::new( | 218 unsafe { index.map(py, |idx| VCGLazyAncestors::new( |
209 idx, | 219 idx, |
210 initvec.into_iter().map(|r| r.0), | 220 initvec.into_iter().map(|r| r.0), |
211 stoprev.0, | 221 stoprev.0, |
212 inclusive | 222 inclusive |
213 ))}; | 223 ))}; |
214 let lazy = pyleaked_or_map_err(py, res_lazy, | 224 let lazy = pyleaked_or_map_err(py, res_lazy, |
215 GraphError::pynew_from_vcsgraph)?; | 225 GraphError::pynew_from_vcsgraph)?; |
226 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
216 let lazy_cell = unsafe { lazy.map(py, RefCell::new)}; | 227 let lazy_cell = unsafe { lazy.map(py, RefCell::new)}; |
217 let res = Self::create_instance( | 228 let res = Self::create_instance( |
218 py, RefCell::new(lazy_cell), | 229 py, RefCell::new(lazy_cell), |
219 cloned_index, initrevs, stoprev, inclusive)?; | 230 cloned_index, initrevs, stoprev, inclusive)?; |
220 Ok(res) | 231 Ok(res) |
234 bases: PyObject | 245 bases: PyObject |
235 ) | 246 ) |
236 -> PyResult<MissingAncestors> { | 247 -> PyResult<MissingAncestors> { |
237 let cloned_index = index.clone_ref(py); | 248 let cloned_index = index.clone_ref(py); |
238 let inner_index = py_rust_index_to_graph(py, index)?; | 249 let inner_index = py_rust_index_to_graph(py, index)?; |
250 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
239 let bases_vec: Vec<_> = { | 251 let bases_vec: Vec<_> = { |
240 let borrowed_idx = unsafe { inner_index.try_borrow(py)? }; | 252 let borrowed_idx = unsafe { inner_index.try_borrow(py)? }; |
241 rev_pyiter_collect(py, &bases, &*borrowed_idx)? | 253 rev_pyiter_collect(py, &bases, &*borrowed_idx)? |
242 }; | 254 }; |
243 | 255 |
256 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
244 let inner = unsafe { | 257 let inner = unsafe { |
245 inner_index.map(py, |idx| CoreMissing::new(idx, bases_vec)) | 258 inner_index.map(py, |idx| CoreMissing::new(idx, bases_vec)) |
246 }; | 259 }; |
247 MissingAncestors::create_instance( | 260 MissingAncestors::create_instance( |
248 py, | 261 py, |
251 ) | 264 ) |
252 } | 265 } |
253 | 266 |
254 def hasbases(&self) -> PyResult<bool> { | 267 def hasbases(&self) -> PyResult<bool> { |
255 let leaked = self.inner(py).borrow(); | 268 let leaked = self.inner(py).borrow(); |
269 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
256 let inner: &CoreMissing<PySharedIndex> = | 270 let inner: &CoreMissing<PySharedIndex> = |
257 &*unsafe { leaked.try_borrow(py)? }; | 271 &*unsafe { leaked.try_borrow(py)? }; |
258 Ok(inner.has_bases()) | 272 Ok(inner.has_bases()) |
259 } | 273 } |
260 | 274 |
261 def addbases(&self, bases: PyObject) -> PyResult<PyObject> { | 275 def addbases(&self, bases: PyObject) -> PyResult<PyObject> { |
262 let bases_vec: Vec<_> = { | 276 let bases_vec: Vec<_> = { |
263 let leaked = py_rust_index_to_graph(py, | 277 let leaked = py_rust_index_to_graph(py, |
264 self.index(py).clone_ref(py))?; | 278 self.index(py).clone_ref(py))?; |
279 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
265 let index = &*unsafe { leaked.try_borrow(py)? }; | 280 let index = &*unsafe { leaked.try_borrow(py)? }; |
266 rev_pyiter_collect(py, &bases, index)? | 281 rev_pyiter_collect(py, &bases, index)? |
267 }; | 282 }; |
268 | 283 |
269 let mut leaked = self.inner(py).borrow_mut(); | 284 let mut leaked = self.inner(py).borrow_mut(); |
285 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
270 let inner: &mut CoreMissing<PySharedIndex> = | 286 let inner: &mut CoreMissing<PySharedIndex> = |
271 &mut *unsafe { leaked.try_borrow_mut(py)? }; | 287 &mut *unsafe { leaked.try_borrow_mut(py)? }; |
272 | 288 |
273 inner.add_bases(bases_vec); | 289 inner.add_bases(bases_vec); |
274 // cpython doc has examples with PyResult<()> but this gives me | 290 // cpython doc has examples with PyResult<()> but this gives me |
277 Ok(py.None()) | 293 Ok(py.None()) |
278 } | 294 } |
279 | 295 |
280 def bases(&self) -> PyResult<HashSet<PyRevision>> { | 296 def bases(&self) -> PyResult<HashSet<PyRevision>> { |
281 let leaked = self.inner(py).borrow(); | 297 let leaked = self.inner(py).borrow(); |
298 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
282 let inner: &CoreMissing<PySharedIndex> = | 299 let inner: &CoreMissing<PySharedIndex> = |
283 &*unsafe { leaked.try_borrow(py)? }; | 300 &*unsafe { leaked.try_borrow(py)? }; |
284 Ok(inner.get_bases() | 301 Ok(inner.get_bases() |
285 .iter() | 302 .iter() |
286 .map(|r| PyRevision(r.0)) | 303 .map(|r| PyRevision(r.0)) |
288 ) | 305 ) |
289 } | 306 } |
290 | 307 |
291 def basesheads(&self) -> PyResult<HashSet<PyRevision>> { | 308 def basesheads(&self) -> PyResult<HashSet<PyRevision>> { |
292 let leaked = self.inner(py).borrow(); | 309 let leaked = self.inner(py).borrow(); |
310 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
293 let inner: &CoreMissing<PySharedIndex> = | 311 let inner: &CoreMissing<PySharedIndex> = |
294 &*unsafe { leaked.try_borrow(py)? }; | 312 &*unsafe { leaked.try_borrow(py)? }; |
295 Ok( | 313 Ok( |
296 inner | 314 inner |
297 .bases_heads() | 315 .bases_heads() |
313 // discard | 331 // discard |
314 // - define a trait for sets of revisions in the core and | 332 // - define a trait for sets of revisions in the core and |
315 // implement it for a Python set rewrapped with the GIL marker | 333 // implement it for a Python set rewrapped with the GIL marker |
316 let leaked = py_rust_index_to_graph(py, | 334 let leaked = py_rust_index_to_graph(py, |
317 self.index(py).clone_ref(py))?; | 335 self.index(py).clone_ref(py))?; |
336 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
318 let index = &*unsafe { leaked.try_borrow(py)? }; | 337 let index = &*unsafe { leaked.try_borrow(py)? }; |
319 rev_pyiter_collect(py, &revs, &*index)? | 338 rev_pyiter_collect(py, &revs, &*index)? |
320 }; | 339 }; |
321 | 340 |
322 let mut leaked = self.inner(py).borrow_mut(); | 341 let mut leaked = self.inner(py).borrow_mut(); |
342 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
323 let inner: &mut CoreMissing<PySharedIndex> = | 343 let inner: &mut CoreMissing<PySharedIndex> = |
324 &mut *unsafe { leaked.try_borrow_mut(py)? }; | 344 &mut *unsafe { leaked.try_borrow_mut(py)? }; |
325 | 345 |
326 inner.remove_ancestors_from(&mut revs_pyset) | 346 inner.remove_ancestors_from(&mut revs_pyset) |
327 .map_err(|e| GraphError::pynew(py, e))?; | 347 .map_err(|e| GraphError::pynew(py, e))?; |
340 | 360 |
341 def missingancestors(&self, revs: PyObject) -> PyResult<PyList> { | 361 def missingancestors(&self, revs: PyObject) -> PyResult<PyList> { |
342 let revs_vec: Vec<Revision> = { | 362 let revs_vec: Vec<Revision> = { |
343 let leaked = py_rust_index_to_graph(py, | 363 let leaked = py_rust_index_to_graph(py, |
344 self.index(py).clone_ref(py))?; | 364 self.index(py).clone_ref(py))?; |
365 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
345 let index = &*unsafe { leaked.try_borrow(py)? }; | 366 let index = &*unsafe { leaked.try_borrow(py)? }; |
346 rev_pyiter_collect(py, &revs, index)? | 367 rev_pyiter_collect(py, &revs, index)? |
347 }; | 368 }; |
348 | 369 |
349 let mut leaked = self.inner(py).borrow_mut(); | 370 let mut leaked = self.inner(py).borrow_mut(); |
371 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
350 let inner: &mut CoreMissing<PySharedIndex> = | 372 let inner: &mut CoreMissing<PySharedIndex> = |
351 &mut *unsafe { leaked.try_borrow_mut(py)? }; | 373 &mut *unsafe { leaked.try_borrow_mut(py)? }; |
352 | 374 |
353 let missing_vec = match inner.missing_ancestors(revs_vec) { | 375 let missing_vec = match inner.missing_ancestors(revs_vec) { |
354 Ok(missing) => missing, | 376 Ok(missing) => missing, |