Mercurial > hg
changeset 43437:93f74a7d3f07
merge with stable
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Tue, 05 Nov 2019 13:19:24 -0800 |
parents | bfc68404cccd (diff) 93aeebc90ff9 (current diff) |
children | a77d4fe347a4 |
files | mercurial/commands.py mercurial/pycompat.py |
diffstat | 13 files changed, 378 insertions(+), 217 deletions(-) [+] |
line wrap: on
line diff
--- a/contrib/perf.py Tue Nov 05 12:10:38 2019 -0500 +++ b/contrib/perf.py Tue Nov 05 13:19:24 2019 -0800 @@ -760,7 +760,10 @@ @command( b'perfstatus', - [(b'u', b'unknown', False, b'ask status to look for unknown files')] + [ + (b'u', b'unknown', False, b'ask status to look for unknown files'), + (b'', b'dirstate', False, b'benchmark the internal dirstate call'), + ] + formatteropts, ) def perfstatus(ui, repo, **opts): @@ -776,7 +779,20 @@ # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False, # False)))) timer, fm = gettimer(ui, opts) - timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown'])))) + if opts[b'dirstate']: + dirstate = repo.dirstate + m = scmutil.matchall(repo) + unknown = opts[b'unknown'] + + def status_dirstate(): + s = dirstate.status( + m, subrepos=[], ignored=False, clean=False, unknown=unknown + ) + sum(map(len, s)) + + timer(status_dirstate) + else: + timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown'])))) fm.end()
--- a/hgext/censor.py Tue Nov 05 12:10:38 2019 -0500 +++ b/hgext/censor.py Tue Nov 05 13:19:24 2019 -0800 @@ -23,6 +23,9 @@ ``hg update``, must be capable of tolerating censored data to continue to function in a meaningful way. Such commands only tolerate censored file revisions if they are allowed by the "censor.policy=ignore" config option. + +A few informative commands such as ``hg grep`` will unconditionally +ignore censored data and merely report that it was encountered. """ from __future__ import absolute_import
--- a/hgext/largefiles/reposetup.py Tue Nov 05 12:10:38 2019 -0500 +++ b/hgext/largefiles/reposetup.py Tue Nov 05 13:19:24 2019 -0800 @@ -360,20 +360,6 @@ ) return result - def push(self, remote, force=False, revs=None, newbranch=False): - if remote.local(): - missing = set(self.requirements) - remote.local().supported - if missing: - msg = _( - b"required features are not" - b" supported in the destination:" - b" %s" - ) % (b', '.join(sorted(missing))) - raise error.Abort(msg) - return super(lfilesrepo, self).push( - remote, force=force, revs=revs, newbranch=newbranch - ) - # TODO: _subdirlfs should be moved into "lfutil.py", because # it is referred only from "lfutil.updatestandinsbymatch" def _subdirlfs(self, files, lfiles):
--- a/mercurial/commands.py Tue Nov 05 12:10:38 2019 -0500 +++ b/mercurial/commands.py Tue Nov 05 13:19:24 2019 -0800 @@ -3446,6 +3446,9 @@ def grepbody(fn, rev, body): matches[rev].setdefault(fn, []) m = matches[rev][fn] + if body is None: + return + for lnum, cstart, cend, line in matchlines(body): s = linestate(line, lnum, cstart, cend) m.append(s) @@ -3581,6 +3584,19 @@ getrenamed = scmutil.getrenamedfn(repo) + def get_file_content(filename, filelog, filenode, context, revision): + try: + content = filelog.read(filenode) + except error.WdirUnsupported: + content = context[filename].data() + except error.CensoredNodeError: + content = None + ui.warn( + _(b'cannot search in censored file: %(filename)s:%(revnum)s\n') + % {b'filename': filename, b'revnum': pycompat.bytestr(revision)} + ) + return content + def prep(ctx, fns): rev = ctx.rev() pctx = ctx.p1() @@ -3607,17 +3623,15 @@ files.append(fn) if fn not in matches[rev]: - try: - content = flog.read(fnode) - except error.WdirUnsupported: - content = ctx[fn].data() + content = get_file_content(fn, flog, fnode, ctx, rev) grepbody(fn, rev, content) pfn = copy or fn if pfn not in matches[parent]: try: - fnode = pctx.filenode(pfn) - grepbody(pfn, parent, flog.read(fnode)) + pfnode = pctx.filenode(pfn) + pcontent = get_file_content(pfn, flog, pfnode, pctx, parent) + grepbody(pfn, parent, pcontent) except error.LookupError: pass
--- a/mercurial/dirstate.py Tue Nov 05 12:10:38 2019 -0500 +++ b/mercurial/dirstate.py Tue Nov 05 13:19:24 2019 -0800 @@ -687,8 +687,7 @@ delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite') if delaywrite > 0: # do we have any files to delay for? - items = pycompat.iteritems(self._map) - for f, e in items: + for f, e in pycompat.iteritems(self._map): if e[0] == b'n' and e[3] == now: import time # to avoid useless import @@ -700,12 +699,6 @@ time.sleep(end - clock) now = end # trust our estimate that the end is near now break - # since the iterator is potentially not deleted, - # delete the iterator to release the reference for the Rust - # implementation. - # TODO make the Rust implementation behave like Python - # since this would not work with a non ref-counting GC. - del items self._map.write(st, now) self._lastnormaltime = 0
--- a/mercurial/pycompat.py Tue Nov 05 12:10:38 2019 -0500 +++ b/mercurial/pycompat.py Tue Nov 05 13:19:24 2019 -0800 @@ -94,6 +94,13 @@ import io import struct + if os.name == r'nt' and sys.version_info >= (3, 6): + # MBCS (or ANSI) filesystem encoding must be used as before. + # Otherwise non-ASCII filenames in existing repositories would be + # corrupted. + # This must be set once prior to any fsencode/fsdecode calls. + sys._enablelegacywindowsfsencoding() + fsencode = os.fsencode fsdecode = os.fsdecode oscurdir = os.curdir.encode('ascii') @@ -139,8 +146,8 @@ # # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55 # - # TODO: On Windows, the native argv is wchar_t, so we'll need a different - # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior. + # On Windows, the native argv is unicode and is converted to MBCS bytes + # since we do enable the legacy filesystem encoding. if getattr(sys, 'argv', None) is not None: sysargv = list(map(os.fsencode, sys.argv))
--- a/rust/hg-cpython/src/dirstate/copymap.rs Tue Nov 05 12:10:38 2019 -0500 +++ b/rust/hg-cpython/src/dirstate/copymap.rs Tue Nov 05 13:19:24 2019 -0800 @@ -12,7 +12,7 @@ use std::cell::RefCell; use crate::dirstate::dirstate_map::DirstateMap; -use crate::ref_sharing::PyLeakedRef; +use crate::ref_sharing::PyLeaked; use hg::{utils::hg_path::HgPathBuf, CopyMapIter}; py_class!(pub class CopyMap |py| { @@ -104,14 +104,14 @@ py_shared_iterator!( CopyMapKeysIterator, - PyLeakedRef<CopyMapIter<'static>>, + PyLeaked<CopyMapIter<'static>>, CopyMap::translate_key, Option<PyBytes> ); py_shared_iterator!( CopyMapItemsIterator, - PyLeakedRef<CopyMapIter<'static>>, + PyLeaked<CopyMapIter<'static>>, CopyMap::translate_key_value, Option<(PyBytes, PyBytes)> );
--- a/rust/hg-cpython/src/dirstate/dirs_multiset.rs Tue Nov 05 12:10:38 2019 -0500 +++ b/rust/hg-cpython/src/dirstate/dirs_multiset.rs Tue Nov 05 13:19:24 2019 -0800 @@ -17,7 +17,7 @@ }; use crate::dirstate::extract_dirstate; -use crate::ref_sharing::{PyLeakedRef, PySharedRefCell}; +use crate::ref_sharing::{PyLeaked, PySharedRefCell}; use hg::{ utils::hg_path::{HgPath, HgPathBuf}, DirsMultiset, DirsMultisetIter, DirstateMapError, DirstateParseError, @@ -92,7 +92,7 @@ }) } def __iter__(&self) -> PyResult<DirsMultisetKeysIterator> { - let leaked_ref = self.inner_shared(py).leak_immutable()?; + let leaked_ref = self.inner_shared(py).leak_immutable(); DirsMultisetKeysIterator::from_inner( py, unsafe { leaked_ref.map(py, |o| o.iter()) }, @@ -123,7 +123,7 @@ py_shared_iterator!( DirsMultisetKeysIterator, - PyLeakedRef<DirsMultisetIter<'static>>, + PyLeaked<DirsMultisetIter<'static>>, Dirs::translate_key, Option<PyBytes> );
--- a/rust/hg-cpython/src/dirstate/dirstate_map.rs Tue Nov 05 12:10:38 2019 -0500 +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs Tue Nov 05 13:19:24 2019 -0800 @@ -20,7 +20,7 @@ use crate::{ dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator}, dirstate::{dirs_multiset::Dirs, make_dirstate_tuple}, - ref_sharing::{PyLeakedRef, PySharedRefCell}, + ref_sharing::{PyLeaked, PySharedRefCell}, }; use hg::{ utils::hg_path::{HgPath, HgPathBuf}, @@ -304,7 +304,7 @@ } def keys(&self) -> PyResult<DirstateMapKeysIterator> { - let leaked_ref = self.inner_shared(py).leak_immutable()?; + let leaked_ref = self.inner_shared(py).leak_immutable(); DirstateMapKeysIterator::from_inner( py, unsafe { leaked_ref.map(py, |o| o.iter()) }, @@ -312,7 +312,7 @@ } def items(&self) -> PyResult<DirstateMapItemsIterator> { - let leaked_ref = self.inner_shared(py).leak_immutable()?; + let leaked_ref = self.inner_shared(py).leak_immutable(); DirstateMapItemsIterator::from_inner( py, unsafe { leaked_ref.map(py, |o| o.iter()) }, @@ -320,7 +320,7 @@ } def __iter__(&self) -> PyResult<DirstateMapKeysIterator> { - let leaked_ref = self.inner_shared(py).leak_immutable()?; + let leaked_ref = self.inner_shared(py).leak_immutable(); DirstateMapKeysIterator::from_inner( py, unsafe { leaked_ref.map(py, |o| o.iter()) }, @@ -437,7 +437,7 @@ } def copymapiter(&self) -> PyResult<CopyMapKeysIterator> { - let leaked_ref = self.inner_shared(py).leak_immutable()?; + let leaked_ref = self.inner_shared(py).leak_immutable(); CopyMapKeysIterator::from_inner( py, unsafe { leaked_ref.map(py, |o| o.copy_map.iter()) }, @@ -445,7 +445,7 @@ } def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> { - let leaked_ref = self.inner_shared(py).leak_immutable()?; + let leaked_ref = self.inner_shared(py).leak_immutable(); CopyMapItemsIterator::from_inner( py, unsafe { leaked_ref.map(py, |o| o.copy_map.iter()) }, @@ -483,14 +483,14 @@ py_shared_iterator!( DirstateMapKeysIterator, - PyLeakedRef<StateMapIter<'static>>, + PyLeaked<StateMapIter<'static>>, DirstateMap::translate_key, Option<PyBytes> ); py_shared_iterator!( DirstateMapItemsIterator, - PyLeakedRef<StateMapIter<'static>>, + PyLeaked<StateMapIter<'static>>, DirstateMap::translate_key_value, Option<(PyBytes, PyObject)> );
--- a/rust/hg-cpython/src/dirstate/status.rs Tue Nov 05 12:10:38 2019 -0500 +++ b/rust/hg-cpython/src/dirstate/status.rs Tue Nov 05 13:19:24 2019 -0800 @@ -6,7 +6,8 @@ // GNU General Public License version 2 or any later version. //! Bindings for the `hg::status` module provided by the -//! `hg-core` crate. From Python, this will be seen as `rustext.dirstate.status`. +//! `hg-core` crate. From Python, this will be seen as +//! `rustext.dirstate.status`. //! use crate::dirstate::DirstateMap;
--- a/rust/hg-cpython/src/ref_sharing.rs Tue Nov 05 12:10:38 2019 -0500 +++ b/rust/hg-cpython/src/ref_sharing.rs Tue Nov 05 13:19:24 2019 -0800 @@ -23,53 +23,56 @@ //! Macros for use in the `hg-cpython` bridge library. use crate::exceptions::AlreadyBorrowed; -use cpython::{PyClone, PyObject, PyResult, Python}; -use std::cell::{Cell, Ref, RefCell, RefMut}; +use cpython::{exc, PyClone, PyErr, PyObject, PyResult, Python}; +use std::cell::{Ref, RefCell, RefMut}; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicUsize, Ordering}; /// Manages the shared state between Python and Rust +/// +/// `PySharedState` is owned by `PySharedRefCell`, and is shared across its +/// derived references. The consistency of these references are guaranteed +/// as follows: +/// +/// - The immutability of `py_class!` object fields. Any mutation of +/// `PySharedRefCell` is allowed only through its `borrow_mut()`. +/// - The `py: Python<'_>` token, which makes sure that any data access is +/// synchronized by the GIL. +/// - The underlying `RefCell`, which prevents `PySharedRefCell` data from +/// being directly borrowed or leaked while it is mutably borrowed. +/// - The `borrow_count`, which is the number of references borrowed from +/// `PyLeaked`. Just like `RefCell`, mutation is prohibited while `PyLeaked` +/// is borrowed. +/// - The `generation` counter, which increments on `borrow_mut()`. `PyLeaked` +/// reference is valid only if the `current_generation()` equals to the +/// `generation` at the time of `leak_immutable()`. #[derive(Debug, Default)] struct PySharedState { - leak_count: Cell<usize>, - mutably_borrowed: Cell<bool>, + // The counter variable could be Cell<usize> since any operation on + // PySharedState is synchronized by the GIL, but being "atomic" makes + // PySharedState inherently Sync. The ordering requirement doesn't + // matter thanks to the GIL. + borrow_count: AtomicUsize, + generation: AtomicUsize, } -// &PySharedState can be Send because any access to inner cells is -// synchronized by the GIL. -unsafe impl Sync for PySharedState {} - impl PySharedState { fn borrow_mut<'a, T>( &'a self, py: Python<'a>, pyrefmut: RefMut<'a, T>, - ) -> PyResult<PyRefMut<'a, T>> { - if self.mutably_borrowed.get() { - return Err(AlreadyBorrowed::new( - py, - "Cannot borrow mutably while there exists another \ - mutable reference in a Python object", - )); - } - match self.leak_count.get() { + ) -> PyResult<RefMut<'a, T>> { + match self.current_borrow_count(py) { 0 => { - self.mutably_borrowed.replace(true); - Ok(PyRefMut::new(py, pyrefmut, self)) + // Note that this wraps around to the same value if mutably + // borrowed more than usize::MAX times, which wouldn't happen + // in practice. + self.generation.fetch_add(1, Ordering::Relaxed); + Ok(pyrefmut) } - // TODO - // For now, this works differently than Python references - // in the case of iterators. - // Python does not complain when the data an iterator - // points to is modified if the iterator is never used - // afterwards. - // Here, we are stricter than this by refusing to give a - // mutable reference if it is already borrowed. - // While the additional safety might be argued for, it - // breaks valid programming patterns in Python and we need - // to fix this issue down the line. _ => Err(AlreadyBorrowed::new( py, - "Cannot borrow mutably while there are \ - immutable references in Python objects", + "Cannot borrow mutably while immutably borrowed", )), } } @@ -84,41 +87,60 @@ /// extended. Do not call this function directly. unsafe fn leak_immutable<T>( &self, - py: Python, - data: &PySharedRefCell<T>, - ) -> PyResult<(&'static T, &'static PySharedState)> { - if self.mutably_borrowed.get() { - return Err(AlreadyBorrowed::new( - py, - "Cannot borrow immutably while there is a \ - mutable reference in Python objects", - )); - } - // TODO: it's weird that self is data.py_shared_state. Maybe we - // can move stuff to PySharedRefCell? - let ptr = data.as_ptr(); - let state_ptr: *const PySharedState = &data.py_shared_state; - self.leak_count.replace(self.leak_count.get() + 1); - Ok((&*ptr, &*state_ptr)) + _py: Python, + data: Ref<T>, + ) -> (&'static T, &'static PySharedState) { + let ptr: *const T = &*data; + let state_ptr: *const PySharedState = self; + (&*ptr, &*state_ptr) + } + + fn current_borrow_count(&self, _py: Python) -> usize { + self.borrow_count.load(Ordering::Relaxed) + } + + fn increase_borrow_count(&self, _py: Python) { + // Note that this wraps around if there are more than usize::MAX + // borrowed references, which shouldn't happen due to memory limit. + self.borrow_count.fetch_add(1, Ordering::Relaxed); + } + + fn decrease_borrow_count(&self, _py: Python) { + let prev_count = self.borrow_count.fetch_sub(1, Ordering::Relaxed); + assert!(prev_count > 0); } - /// # Safety - /// - /// It's up to you to make sure the reference is about to be deleted - /// when updating the leak count. - fn decrease_leak_count(&self, _py: Python, mutable: bool) { - if mutable { - assert_eq!(self.leak_count.get(), 0); - assert!(self.mutably_borrowed.get()); - self.mutably_borrowed.replace(false); - } else { - let count = self.leak_count.get(); - assert!(count > 0); - self.leak_count.replace(count - 1); + fn current_generation(&self, _py: Python) -> usize { + self.generation.load(Ordering::Relaxed) + } +} + +/// Helper to keep the borrow count updated while the shared object is +/// immutably borrowed without using the `RefCell` interface. +struct BorrowPyShared<'a> { + py: Python<'a>, + py_shared_state: &'a PySharedState, +} + +impl<'a> BorrowPyShared<'a> { + fn new( + py: Python<'a>, + py_shared_state: &'a PySharedState, + ) -> BorrowPyShared<'a> { + py_shared_state.increase_borrow_count(py); + BorrowPyShared { + py, + py_shared_state, } } } +impl Drop for BorrowPyShared<'_> { + fn drop(&mut self) { + self.py_shared_state.decrease_borrow_count(self.py); + } +} + /// `RefCell` wrapper to be safely used in conjunction with `PySharedState`. /// /// This object can be stored in a `py_class!` object as a data field. Any @@ -144,15 +166,11 @@ self.inner.borrow() } - fn as_ptr(&self) -> *mut T { - self.inner.as_ptr() - } - // TODO: maybe this should be named as try_borrow_mut(), and use // inner.try_borrow_mut(). The current implementation panics if // self.inner has been borrowed, but returns error if py_shared_state // refuses to borrow. - fn borrow_mut<'a>(&'a self, py: Python<'a>) -> PyResult<PyRefMut<'a, T>> { + fn borrow_mut<'a>(&'a self, py: Python<'a>) -> PyResult<RefMut<'a, T>> { self.py_shared_state.borrow_mut(py, self.inner.borrow_mut()) } } @@ -181,78 +199,31 @@ self.data.borrow(self.py) } - pub fn borrow_mut(&self) -> PyResult<PyRefMut<'a, T>> { + pub fn borrow_mut(&self) -> PyResult<RefMut<'a, T>> { self.data.borrow_mut(self.py) } /// Returns a leaked reference. - pub fn leak_immutable(&self) -> PyResult<PyLeakedRef<&'static T>> { + /// + /// # Panics + /// + /// Panics if this is mutably borrowed. + pub fn leak_immutable(&self) -> PyLeaked<&'static T> { let state = &self.data.py_shared_state; + // make sure self.data isn't mutably borrowed; otherwise the + // generation number can't be trusted. + let data_ref = self.borrow(); unsafe { let (static_ref, static_state_ref) = - state.leak_immutable(self.py, self.data)?; - Ok(PyLeakedRef::new( - self.py, - self.owner, - static_ref, - static_state_ref, - )) + state.leak_immutable(self.py, data_ref); + PyLeaked::new(self.py, self.owner, static_ref, static_state_ref) } } } -/// Holds a mutable reference to data shared between Python and Rust. -pub struct PyRefMut<'a, T> { - py: Python<'a>, - inner: RefMut<'a, T>, - py_shared_state: &'a PySharedState, -} - -impl<'a, T> PyRefMut<'a, T> { - // Must be constructed by PySharedState after checking its leak_count. - // Otherwise, drop() would incorrectly update the state. - fn new( - py: Python<'a>, - inner: RefMut<'a, T>, - py_shared_state: &'a PySharedState, - ) -> Self { - Self { - py, - inner, - py_shared_state, - } - } -} - -impl<'a, T> std::ops::Deref for PyRefMut<'a, T> { - type Target = RefMut<'a, T>; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} -impl<'a, T> std::ops::DerefMut for PyRefMut<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -impl<'a, T> Drop for PyRefMut<'a, T> { - fn drop(&mut self) { - self.py_shared_state.decrease_leak_count(self.py, true); - } -} - /// Allows a `py_class!` generated struct to share references to one of its /// data members with Python. /// -/// # Warning -/// -/// TODO allow Python container types: for now, integration with the garbage -/// collector does not extend to Rust structs holding references to Python -/// objects. Should the need surface, `__traverse__` and `__clear__` will -/// need to be written as per the `rust-cpython` docs on GC integration. -/// /// # Parameters /// /// * `$name` is the same identifier used in for `py_class!` macro call. @@ -307,16 +278,22 @@ } /// Manage immutable references to `PyObject` leaked into Python iterators. -pub struct PyLeakedRef<T> { +/// +/// This reference will be invalidated once the original value is mutably +/// borrowed. +pub struct PyLeaked<T> { inner: PyObject, data: Option<T>, py_shared_state: &'static PySharedState, + /// Generation counter of data `T` captured when PyLeaked is created. + generation: usize, } -// DO NOT implement Deref for PyLeakedRef<T>! Dereferencing PyLeakedRef -// without taking Python GIL wouldn't be safe. +// DO NOT implement Deref for PyLeaked<T>! Dereferencing PyLeaked +// without taking Python GIL wouldn't be safe. Also, the underling reference +// is invalid if generation != py_shared_state.generation. -impl<T> PyLeakedRef<T> { +impl<T> PyLeaked<T> { /// # Safety /// /// The `py_shared_state` must be owned by the `inner` Python object. @@ -330,20 +307,39 @@ inner: inner.clone_ref(py), data: Some(data), py_shared_state, + generation: py_shared_state.current_generation(py), } } - /// Returns an immutable reference to the inner value. - pub fn get_ref<'a>(&'a self, _py: Python<'a>) -> &'a T { - self.data.as_ref().unwrap() + /// Immutably borrows the wrapped value. + /// + /// Borrowing fails if the underlying reference has been invalidated. + pub fn try_borrow<'a>( + &'a self, + py: Python<'a>, + ) -> PyResult<PyLeakedRef<'a, T>> { + self.validate_generation(py)?; + Ok(PyLeakedRef { + _borrow: BorrowPyShared::new(py, self.py_shared_state), + data: self.data.as_ref().unwrap(), + }) } - /// Returns a mutable reference to the inner value. + /// Mutably borrows the wrapped value. + /// + /// Borrowing fails if the underlying reference has been invalidated. /// /// Typically `T` is an iterator. If `T` is an immutable reference, /// `get_mut()` is useless since the inner value can't be mutated. - pub fn get_mut<'a>(&'a mut self, _py: Python<'a>) -> &'a mut T { - self.data.as_mut().unwrap() + pub fn try_borrow_mut<'a>( + &'a mut self, + py: Python<'a>, + ) -> PyResult<PyLeakedRefMut<'a, T>> { + self.validate_generation(py)?; + Ok(PyLeakedRefMut { + _borrow: BorrowPyShared::new(py, self.py_shared_state), + data: self.data.as_mut().unwrap(), + }) } /// Converts the inner value by the given function. @@ -351,41 +347,85 @@ /// Typically `T` is a static reference to a container, and `U` is an /// iterator of that container. /// + /// # Panics + /// + /// Panics if the underlying reference has been invalidated. + /// + /// This is typically called immediately after the `PyLeaked` is obtained. + /// In which case, the reference must be valid and no panic would occur. + /// /// # Safety /// /// The lifetime of the object passed in to the function `f` is cheated. /// It's typically a static reference, but is valid only while the - /// corresponding `PyLeakedRef` is alive. Do not copy it out of the + /// corresponding `PyLeaked` is alive. Do not copy it out of the /// function call. pub unsafe fn map<U>( mut self, py: Python, f: impl FnOnce(T) -> U, - ) -> PyLeakedRef<U> { + ) -> PyLeaked<U> { + // Needs to test the generation value to make sure self.data reference + // is still intact. + self.validate_generation(py) + .expect("map() over invalidated leaked reference"); + // f() could make the self.data outlive. That's why map() is unsafe. // In order to make this function safe, maybe we'll need a way to // temporarily restrict the lifetime of self.data and translate the // returned object back to Something<'static>. let new_data = f(self.data.take().unwrap()); - PyLeakedRef { + PyLeaked { inner: self.inner.clone_ref(py), data: Some(new_data), py_shared_state: self.py_shared_state, + generation: self.generation, + } + } + + fn validate_generation(&self, py: Python) -> PyResult<()> { + if self.py_shared_state.current_generation(py) == self.generation { + Ok(()) + } else { + Err(PyErr::new::<exc::RuntimeError, _>( + py, + "Cannot access to leaked reference after mutation", + )) } } } -impl<T> Drop for PyLeakedRef<T> { - fn drop(&mut self) { - // py_shared_state should be alive since we do have - // a Python reference to the owner object. Taking GIL makes - // sure that the state is only accessed by this thread. - let gil = Python::acquire_gil(); - let py = gil.python(); - if self.data.is_none() { - return; // moved to another PyLeakedRef - } - self.py_shared_state.decrease_leak_count(py, false); +/// Immutably borrowed reference to a leaked value. +pub struct PyLeakedRef<'a, T> { + _borrow: BorrowPyShared<'a>, + data: &'a T, +} + +impl<T> Deref for PyLeakedRef<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + self.data + } +} + +/// Mutably borrowed reference to a leaked value. +pub struct PyLeakedRefMut<'a, T> { + _borrow: BorrowPyShared<'a>, + data: &'a mut T, +} + +impl<T> Deref for PyLeakedRefMut<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + self.data + } +} + +impl<T> DerefMut for PyLeakedRefMut<'_, T> { + fn deref_mut(&mut self) -> &mut T { + self.data } } @@ -414,7 +454,7 @@ /// data inner: PySharedRefCell<MyStruct>; /// /// def __iter__(&self) -> PyResult<MyTypeItemsIterator> { -/// let leaked_ref = self.inner_shared(py).leak_immutable()?; +/// let leaked_ref = self.inner_shared(py).leak_immutable(); /// MyTypeItemsIterator::from_inner( /// py, /// unsafe { leaked_ref.map(py, |o| o.iter()) }, @@ -439,7 +479,7 @@ /// /// py_shared_iterator!( /// MyTypeItemsIterator, -/// PyLeakedRef<HashMap<'static, Vec<u8>, Vec<u8>>>, +/// PyLeaked<HashMap<'static, Vec<u8>, Vec<u8>>>, /// MyType::translate_key_value, /// Option<(PyBytes, PyBytes)> /// ); @@ -452,23 +492,14 @@ $success_type: ty ) => { py_class!(pub class $name |py| { - data inner: RefCell<Option<$leaked>>; + data inner: RefCell<$leaked>; def __next__(&self) -> PyResult<$success_type> { - let mut inner_opt = self.inner(py).borrow_mut(); - if let Some(leaked) = inner_opt.as_mut() { - match leaked.get_mut(py).next() { - None => { - // replace Some(inner) by None, drop $leaked - inner_opt.take(); - Ok(None) - } - Some(res) => { - $success_func(py, res) - } - } - } else { - Ok(None) + let mut leaked = self.inner(py).borrow_mut(); + let mut iter = leaked.try_borrow_mut(py)?; + match iter.next() { + None => Ok(None), + Some(res) => $success_func(py, res), } } @@ -484,7 +515,7 @@ ) -> PyResult<Self> { Self::create_instance( py, - RefCell::new(Some(leaked)), + RefCell::new(leaked), ) } } @@ -512,12 +543,94 @@ } #[test] - fn test_borrow_mut_while_leaked() { + fn test_leaked_borrow() { + let (gil, owner) = prepare_env(); + let py = gil.python(); + let leaked = owner.string_shared(py).leak_immutable(); + let leaked_ref = leaked.try_borrow(py).unwrap(); + assert_eq!(*leaked_ref, "new"); + } + + #[test] + fn test_leaked_borrow_mut() { + let (gil, owner) = prepare_env(); + let py = gil.python(); + let leaked = owner.string_shared(py).leak_immutable(); + let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; + let mut leaked_ref = leaked_iter.try_borrow_mut(py).unwrap(); + assert_eq!(leaked_ref.next(), Some('n')); + assert_eq!(leaked_ref.next(), Some('e')); + assert_eq!(leaked_ref.next(), Some('w')); + assert_eq!(leaked_ref.next(), None); + } + + #[test] + fn test_leaked_borrow_after_mut() { + let (gil, owner) = prepare_env(); + let py = gil.python(); + let leaked = owner.string_shared(py).leak_immutable(); + owner.string_shared(py).borrow_mut().unwrap().clear(); + assert!(leaked.try_borrow(py).is_err()); + } + + #[test] + fn test_leaked_borrow_mut_after_mut() { + let (gil, owner) = prepare_env(); + let py = gil.python(); + let leaked = owner.string_shared(py).leak_immutable(); + let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; + owner.string_shared(py).borrow_mut().unwrap().clear(); + assert!(leaked_iter.try_borrow_mut(py).is_err()); + } + + #[test] + #[should_panic(expected = "map() over invalidated leaked reference")] + fn test_leaked_map_after_mut() { + let (gil, owner) = prepare_env(); + let py = gil.python(); + let leaked = owner.string_shared(py).leak_immutable(); + owner.string_shared(py).borrow_mut().unwrap().clear(); + let _leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; + } + + #[test] + fn test_borrow_mut_while_leaked_ref() { let (gil, owner) = prepare_env(); let py = gil.python(); assert!(owner.string_shared(py).borrow_mut().is_ok()); - let _leaked = owner.string_shared(py).leak_immutable().unwrap(); - // TODO: will be allowed - assert!(owner.string_shared(py).borrow_mut().is_err()); + let leaked = owner.string_shared(py).leak_immutable(); + { + let _leaked_ref = leaked.try_borrow(py).unwrap(); + assert!(owner.string_shared(py).borrow_mut().is_err()); + { + let _leaked_ref2 = leaked.try_borrow(py).unwrap(); + assert!(owner.string_shared(py).borrow_mut().is_err()); + } + assert!(owner.string_shared(py).borrow_mut().is_err()); + } + assert!(owner.string_shared(py).borrow_mut().is_ok()); + } + + #[test] + fn test_borrow_mut_while_leaked_ref_mut() { + let (gil, owner) = prepare_env(); + let py = gil.python(); + assert!(owner.string_shared(py).borrow_mut().is_ok()); + let leaked = owner.string_shared(py).leak_immutable(); + let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; + { + let _leaked_ref = leaked_iter.try_borrow_mut(py).unwrap(); + assert!(owner.string_shared(py).borrow_mut().is_err()); + } + assert!(owner.string_shared(py).borrow_mut().is_ok()); + } + + #[test] + #[should_panic(expected = "mutably borrowed")] + fn test_leak_while_borrow_mut() { + let (gil, owner) = prepare_env(); + let py = gil.python(); + let _mut_ref = owner.string_shared(py).borrow_mut(); + owner.string_shared(py).leak_immutable(); } }
--- a/tests/test-censor.t Tue Nov 05 12:10:38 2019 -0500 +++ b/tests/test-censor.t Tue Nov 05 13:19:24 2019 -0800 @@ -442,6 +442,33 @@ checking files checked 14 changesets with 15 changes to 2 files +Grepping only warns, doesn't error out + + $ cd ../rpull + $ hg grep 'Normal file' + bystander:Normal file v2 + $ hg grep nothing + target:Re-sanitized; nothing to see here + $ hg grep --diff 'Normal file' + cannot search in censored file: target:7 + cannot search in censored file: target:10 + cannot search in censored file: target:12 + bystander:6:-:Normal file v2 + cannot search in censored file: target:1 + cannot search in censored file: target:2 + cannot search in censored file: target:3 + bystander:2:-:Normal file here + bystander:2:+:Normal file v2 + bystander:0:+:Normal file here + $ hg grep --diff nothing + cannot search in censored file: target:7 + cannot search in censored file: target:10 + cannot search in censored file: target:12 + target:13:+:Re-sanitized; nothing to see here + cannot search in censored file: target:1 + cannot search in censored file: target:2 + cannot search in censored file: target:3 + Censored nodes can be imported on top of censored nodes, consecutively $ hg init ../rimport