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,