Use Rust’s libc::ssize_t as the closest match to C’s Py_ssize_t.
See details in test comment.
Going forward we should find a way to have such Rust declarations
auto-generated from C headers at build time,
or auto-checked against them in a test.
Alphare | |
pulkit |
hg-reviewers |
Use Rust’s libc::ssize_t as the closest match to C’s Py_ssize_t.
See details in test comment.
Going forward we should find a way to have such Rust declarations
auto-generated from C headers at build time,
or auto-checked against them in a test.
Automatic diff as part of commit; lint not applicable. |
Automatic diff as part of commit; unit tests not applicable. |
Path | Packages | |||
---|---|---|---|---|
M | rust/hg-cpython/src/cindex.rs (8 lines) | |||
M | tests/test-persistent-nodemap.t (14 lines) |
Status | Author | Revision | |
---|---|---|---|
Closed | SimonSapin | ||
Closed | SimonSapin |
//! but this will take some time to get there. | //! but this will take some time to get there. | ||||
use cpython::{ | use cpython::{ | ||||
exc::ImportError, ObjectProtocol, PyClone, PyErr, PyObject, PyResult, | exc::ImportError, ObjectProtocol, PyClone, PyErr, PyObject, PyResult, | ||||
PyTuple, Python, PythonObject, | PyTuple, Python, PythonObject, | ||||
}; | }; | ||||
use hg::revlog::{Node, RevlogIndex}; | use hg::revlog::{Node, RevlogIndex}; | ||||
use hg::{Graph, GraphError, Revision, WORKING_DIRECTORY_REVISION}; | use hg::{Graph, GraphError, Revision, WORKING_DIRECTORY_REVISION}; | ||||
use libc::c_int; | use libc::{c_int, ssize_t}; | ||||
const REVLOG_CABI_VERSION: c_int = 2; | const REVLOG_CABI_VERSION: c_int = 2; | ||||
#[repr(C)] | #[repr(C)] | ||||
pub struct Revlog_CAPI { | pub struct Revlog_CAPI { | ||||
abi_version: c_int, | abi_version: c_int, | ||||
index_length: | index_length: | ||||
unsafe extern "C" fn(index: *mut revlog_capi::RawPyObject) -> c_int, | unsafe extern "C" fn(index: *mut revlog_capi::RawPyObject) -> ssize_t, | ||||
index_node: unsafe extern "C" fn( | index_node: unsafe extern "C" fn( | ||||
index: *mut revlog_capi::RawPyObject, | index: *mut revlog_capi::RawPyObject, | ||||
rev: c_int, | rev: ssize_t, | ||||
) -> *const Node, | ) -> *const Node, | ||||
index_parents: unsafe extern "C" fn( | index_parents: unsafe extern "C" fn( | ||||
index: *mut revlog_capi::RawPyObject, | index: *mut revlog_capi::RawPyObject, | ||||
rev: c_int, | rev: c_int, | ||||
ps: *mut [c_int; 2], | ps: *mut [c_int; 2], | ||||
) -> c_int, | ) -> c_int, | ||||
} | } | ||||
/// Note C return type is Py_ssize_t (hence signed), but we shall | /// Note C return type is Py_ssize_t (hence signed), but we shall | ||||
/// force it to unsigned, because it's a length | /// force it to unsigned, because it's a length | ||||
fn len(&self) -> usize { | fn len(&self) -> usize { | ||||
unsafe { (self.capi.index_length)(self.index.as_ptr()) as usize } | unsafe { (self.capi.index_length)(self.index.as_ptr()) as usize } | ||||
} | } | ||||
fn node(&self, rev: Revision) -> Option<&Node> { | fn node(&self, rev: Revision) -> Option<&Node> { | ||||
let raw = unsafe { | let raw = unsafe { | ||||
(self.capi.index_node)(self.index.as_ptr(), rev as c_int) | (self.capi.index_node)(self.index.as_ptr(), rev as ssize_t) | ||||
}; | }; | ||||
if raw.is_null() { | if raw.is_null() { | ||||
None | None | ||||
} else { | } else { | ||||
// TODO it would be much better for the C layer to give us | // TODO it would be much better for the C layer to give us | ||||
// a length, since the hash length will change in the near | // a length, since the hash length will change in the near | ||||
// future, but that's probably out of scope for the nodemap | // future, but that's probably out of scope for the nodemap | ||||
// patch series. | // patch series. | ||||
// | // | ||||
// The root of that unsafety relies in the signature of | // The root of that unsafety relies in the signature of | ||||
// `capi.index_node()` itself: returning a `Node` pointer | // `capi.index_node()` itself: returning a `Node` pointer | ||||
// whereas it's a `char *` in the C counterpart. | // whereas it's a `char *` in the C counterpart. | ||||
Some(unsafe { &*raw }) | Some(unsafe { &*raw }) | ||||
} | } | ||||
} | } | ||||
} | } |
> # to avoid spamming the test | > # to avoid spamming the test | ||||
> revlog.persistent-nodemap.slow-path=allow | > revlog.persistent-nodemap.slow-path=allow | ||||
> EOF | > EOF | ||||
#endif | #endif | ||||
#if rust | #if rust | ||||
Reported bug: some Rust code panics when handling the null revision | Regression test for a previous bug in Rust/C FFI for the `Revlog_CAPI` capsule: | ||||
in places where `mercurial/cext/revlog.c` function signatures use `Py_ssize_t` | |||||
(64 bits on Linux x86_64), corresponding declarations in `rust/hg-cpython/src/cindex.rs` | |||||
incorrectly used `libc::c_int` (32 bits). | |||||
As a result, -1 passed from Rust for the null revision became 4294967295 in C. | |||||
$ hg log -r 00000000 | |||||
changeset: -1:000000000000 | |||||
tag: tip | |||||
user: | |||||
date: Thu Jan 01 00:00:00 1970 +0000 | |||||
$ hg log -r 00000000 2>&1 | grep panicked | |||||
thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', hg-cpython/src/revlog.rs:* (glob) | |||||
#endif | #endif | ||||
$ hg debugformat | $ hg debugformat | ||||
format-variant repo | format-variant repo | ||||
fncache: yes | fncache: yes | ||||
dotencode: yes | dotencode: yes |