From dd423a22257eea319518dbc580d1fe2500f3cce7 Mon Sep 17 00:00:00 2001 From: DSPOM Date: Wed, 26 Jan 2022 16:58:51 +0100 Subject: [PATCH] remove safe static for datetime API from ffi module --- pyo3-ffi/src/datetime.rs | 62 ++++++----- pyo3-ffi/src/lib.rs | 35 ++++++ src/ffi/cpython/mod.rs | 1 - src/ffi/cpython/unicodeobject.rs | 128 --------------------- src/ffi/datetime.rs | 99 ----------------- src/ffi/mod.rs | 94 +--------------- src/ffi/tests.rs | 183 +++++++++++++++++++++++++++++++ src/types/datetime.rs | 137 ++++++++++++++++------- tests/test_datetime.rs | 6 +- 9 files changed, 354 insertions(+), 391 deletions(-) delete mode 100644 src/ffi/cpython/mod.rs delete mode 100644 src/ffi/cpython/unicodeobject.rs delete mode 100644 src/ffi/datetime.rs create mode 100644 src/ffi/tests.rs diff --git a/pyo3-ffi/src/datetime.rs b/pyo3-ffi/src/datetime.rs index 201345603ea..45a055b643f 100644 --- a/pyo3-ffi/src/datetime.rs +++ b/pyo3-ffi/src/datetime.rs @@ -9,7 +9,7 @@ //! Support for `PyDateTime_CAPI` is limited as of PyPy 7.0.0. //! `DateTime_FromTimestamp` and `Date_FromTimestamp` are currently not supported. -use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE}; +use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE, UnsafeGILCell}; use std::os::raw::{c_char, c_int, c_uchar}; use std::ptr; #[cfg(not(PyPy))] @@ -431,31 +431,29 @@ pub struct PyDateTime_CAPI { // Python already shares this object between threads, so it's no more evil for us to do it too! unsafe impl Sync for PyDateTime_CAPI {} -pub static mut PyDateTimeAPI: *mut PyDateTime_CAPI = ptr::null_mut(); +pub static PyDateTimeAPI: UnsafeGILCell = UnsafeGILCell::new(ptr::null_mut()); #[cfg(not(all(PyPy, not(Py_3_8))))] pub unsafe fn PyDateTime_TimeZone_UTC() -> *mut PyObject { - (*PyDateTimeAPI).TimeZone_UTC + (*PyDateTimeAPI.get()).TimeZone_UTC } /// Populates the `PyDateTimeAPI` object pub unsafe fn PyDateTime_IMPORT() { - PyDateTimeAPI = { - // PyPy expects the C-API to be initialized via PyDateTime_Import, so trying to use - // `PyCapsule_Import` will behave unexpectedly in pypy. - #[cfg(PyPy)] - let py_datetime_c_api = PyDateTime_Import(); + // PyPy expects the C-API to be initialized via PyDateTime_Import, so trying to use + // `PyCapsule_Import` will behave unexpectedly in pypy. + #[cfg(PyPy)] + let py_datetime_c_api = PyDateTime_Import(); - #[cfg(not(PyPy))] - let py_datetime_c_api = { - // PyDateTime_CAPSULE_NAME is a macro in C - let PyDateTime_CAPSULE_NAME = CString::new("datetime.datetime_CAPI").unwrap(); - - PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1) as *mut PyDateTime_CAPI - }; + #[cfg(not(PyPy))] + let py_datetime_c_api = { + // PyDateTime_CAPSULE_NAME is a macro in C + let PyDateTime_CAPSULE_NAME = CString::new("datetime.datetime_CAPI").unwrap(); - py_datetime_c_api + PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1) as *mut PyDateTime_CAPI }; + + PyDateTimeAPI.set(py_datetime_c_api) } // skipped non-limited PyDateTime_TimeZone_UTC @@ -468,61 +466,61 @@ pub unsafe fn PyDateTime_IMPORT() { #[inline] /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. pub unsafe fn PyDate_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, (*PyDateTimeAPI).DateType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI.get()).DateType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DateType`. pub unsafe fn PyDate_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == (*PyDateTimeAPI).DateType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI.get()).DateType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype. pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, (*PyDateTimeAPI).DateTimeType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI.get()).DateTimeType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DateTimeType`. pub unsafe fn PyDateTime_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == (*PyDateTimeAPI).DateTimeType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI.get()).DateTimeType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype. pub unsafe fn PyTime_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, (*PyDateTimeAPI).TimeType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI.get()).TimeType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.TimeType`. pub unsafe fn PyTime_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == (*PyDateTimeAPI).TimeType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI.get()).TimeType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype. pub unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, (*PyDateTimeAPI).DeltaType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI.get()).DeltaType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DeltaType`. pub unsafe fn PyDelta_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == (*PyDateTimeAPI).DeltaType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI.get()).DeltaType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype. pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int { - PyObject_TypeCheck(op, (*PyDateTimeAPI).TZInfoType) as c_int + PyObject_TypeCheck(op, (*PyDateTimeAPI.get()).TZInfoType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.TZInfoType`. pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int { - (Py_TYPE(op) == (*PyDateTimeAPI).TZInfoType) as c_int + (Py_TYPE(op) == (*PyDateTimeAPI.get()).TZInfoType) as c_int } // skipped non-limited PyDate_FromDate @@ -536,14 +534,18 @@ pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int { #[cfg(not(PyPy))] pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject { - let f = (*PyDateTimeAPI).DateTime_FromTimestamp; - f((*PyDateTimeAPI).DateTimeType, args, std::ptr::null_mut()) + let f = (*PyDateTimeAPI.get()).DateTime_FromTimestamp; + f( + (*PyDateTimeAPI.get()).DateTimeType, + args, + std::ptr::null_mut(), + ) } #[cfg(not(PyPy))] pub unsafe fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject { - let f = (*PyDateTimeAPI).Date_FromTimestamp; - f((*PyDateTimeAPI).DateType, args) + let f = (*PyDateTimeAPI.get()).Date_FromTimestamp; + f((*PyDateTimeAPI.get()).DateType, args) } #[cfg(PyPy)] diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index 36d7f4a0f9d..ccfe9f67068 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -253,6 +253,8 @@ macro_rules! opaque_struct { }; } +use std::cell::Cell; + pub use self::abstract_::*; pub use self::bltinmodule::*; pub use self::boolobject::*; @@ -418,3 +420,36 @@ mod cpython; #[cfg(not(Py_LIMITED_API))] pub use self::cpython::*; + +/// This cell type allows shared mutability for python types. +/// It is intended to hold raw pointers to the python heap and therefore only accepts copy types to +/// reduce the changes of accidental undefined behaviour. +/// Thread safety is ensured by the python GIL. +/// Therefore accessing the contents of this cell in any form while the GIL is not held is UB +#[repr(transparent)] +pub struct UnsafeGILCell(Cell<*mut T>); + +impl UnsafeGILCell { + pub const fn new(val: *mut T) -> UnsafeGILCell { + UnsafeGILCell(Cell::new(val)) + } + + /// Sets the contained value. + /// + /// # Safety + /// The caller must assure that the GIL is held + pub unsafe fn set(&self, val: *mut T) { + self.0.set(val) + } + + /// Returns a copy of the contained value. + /// + /// # Safety + /// The caller must assure that the GIL is held + pub unsafe fn get(&self) -> *mut T { + self.0.get() + } +} + +unsafe impl Sync for UnsafeGILCell {} +unsafe impl Send for UnsafeGILCell {} diff --git a/src/ffi/cpython/mod.rs b/src/ffi/cpython/mod.rs deleted file mode 100644 index 26aebaf4c38..00000000000 --- a/src/ffi/cpython/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod unicodeobject; diff --git a/src/ffi/cpython/unicodeobject.rs b/src/ffi/cpython/unicodeobject.rs deleted file mode 100644 index 79512583fc7..00000000000 --- a/src/ffi/cpython/unicodeobject.rs +++ /dev/null @@ -1,128 +0,0 @@ -#[cfg(test)] -#[cfg(target_endian = "little")] -mod tests { - use libc::wchar_t; - - use crate::types::PyString; - use crate::{AsPyPointer, Python}; - use pyo3_ffi::*; - - #[test] - fn ascii_object_bitfield() { - let ob_base: PyObject = unsafe { std::mem::zeroed() }; - - let mut o = PyASCIIObject { - ob_base, - length: 0, - hash: 0, - state: 0, - wstr: std::ptr::null_mut() as *mut wchar_t, - }; - - unsafe { - assert_eq!(o.interned(), 0); - assert_eq!(o.kind(), 0); - assert_eq!(o.compact(), 0); - assert_eq!(o.ascii(), 0); - assert_eq!(o.ready(), 0); - - for i in 0..4 { - o.state = i; - assert_eq!(o.interned(), i); - } - - for i in 0..8 { - o.state = i << 2; - assert_eq!(o.kind(), i); - } - - o.state = 1 << 5; - assert_eq!(o.compact(), 1); - - o.state = 1 << 6; - assert_eq!(o.ascii(), 1); - - o.state = 1 << 7; - assert_eq!(o.ready(), 1); - } - } - - #[test] - #[cfg_attr(Py_3_10, allow(deprecated))] - fn ascii() { - Python::with_gil(|py| { - // This test relies on implementation details of PyString. - let s = PyString::new(py, "hello, world"); - let ptr = s.as_ptr(); - - unsafe { - let ascii_ptr = ptr as *mut PyASCIIObject; - let ascii = ascii_ptr.as_ref().unwrap(); - - assert_eq!(ascii.interned(), 0); - assert_eq!(ascii.kind(), PyUnicode_1BYTE_KIND); - assert_eq!(ascii.compact(), 1); - assert_eq!(ascii.ascii(), 1); - assert_eq!(ascii.ready(), 1); - - assert_eq!(PyUnicode_IS_ASCII(ptr), 1); - assert_eq!(PyUnicode_IS_COMPACT(ptr), 1); - assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 1); - - assert!(!PyUnicode_1BYTE_DATA(ptr).is_null()); - // 2 and 4 byte macros return nonsense for this string instance. - assert_eq!(PyUnicode_KIND(ptr), PyUnicode_1BYTE_KIND); - - assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null()); - // _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings. - assert!(!PyUnicode_DATA(ptr).is_null()); - - assert_eq!(PyUnicode_GET_LENGTH(ptr), s.len().unwrap() as _); - assert_eq!(PyUnicode_IS_READY(ptr), 1); - - // This has potential to mutate object. But it should be a no-op since - // we're already ready. - assert_eq!(PyUnicode_READY(ptr), 0); - } - }) - } - - #[test] - #[cfg_attr(Py_3_10, allow(deprecated))] - fn ucs4() { - Python::with_gil(|py| { - let s = "哈哈🐈"; - let py_string = PyString::new(py, s); - let ptr = py_string.as_ptr(); - - unsafe { - let ascii_ptr = ptr as *mut PyASCIIObject; - let ascii = ascii_ptr.as_ref().unwrap(); - - assert_eq!(ascii.interned(), 0); - assert_eq!(ascii.kind(), PyUnicode_4BYTE_KIND); - assert_eq!(ascii.compact(), 1); - assert_eq!(ascii.ascii(), 0); - assert_eq!(ascii.ready(), 1); - - assert_eq!(PyUnicode_IS_ASCII(ptr), 0); - assert_eq!(PyUnicode_IS_COMPACT(ptr), 1); - assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 0); - - assert!(!PyUnicode_4BYTE_DATA(ptr).is_null()); - assert_eq!(PyUnicode_KIND(ptr), PyUnicode_4BYTE_KIND); - - assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null()); - // _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings. - assert!(!PyUnicode_DATA(ptr).is_null()); - - assert_eq!(PyUnicode_GET_LENGTH(ptr), py_string.len().unwrap() as _); - assert_eq!(PyUnicode_IS_READY(ptr), 1); - - // This has potential to mutate object. But it should be a no-op since - // we're already ready. - assert_eq!(PyUnicode_READY(ptr), 0); - } - }) - } -} diff --git a/src/ffi/datetime.rs b/src/ffi/datetime.rs deleted file mode 100644 index 8bc3a26c454..00000000000 --- a/src/ffi/datetime.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::ops::Deref; - -use super::*; -use crate::Python; - -// -- implementation details which are specific to Rust. -- - -#[doc(hidden)] -pub struct _PyDateTimeAPI_impl {} - -impl Deref for _PyDateTimeAPI_impl { - type Target = PyDateTime_CAPI; - - #[inline] - fn deref(&self) -> &'static PyDateTime_CAPI { - Python::with_gil(|_py| unsafe { - if pyo3_ffi::PyDateTimeAPI.is_null() { - PyDateTime_IMPORT() - } - - &*pyo3_ffi::PyDateTimeAPI - }) - } -} - -#[doc(hidden)] -#[cfg(not(all(PyPy, not(Py_3_8))))] -pub struct _PyDateTime_TimeZone_UTC_impl { - pub(super) inner: &'static _PyDateTimeAPI_impl, -} - -#[cfg(not(all(PyPy, not(Py_3_8))))] -impl Deref for _PyDateTime_TimeZone_UTC_impl { - type Target = crate::PyObject; - - #[inline] - fn deref(&self) -> &crate::PyObject { - unsafe { - &*((&self.inner.TimeZone_UTC) as *const *mut crate::ffi::PyObject - as *const crate::PyObject) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{types::PyDict, AsPyPointer, IntoPy, Py, PyAny, Python}; - - #[test] - fn test_datetime_fromtimestamp() { - Python::with_gil(|py| { - let args: Py = (100,).into_py(py); - unsafe { PyDateTime_IMPORT() }; - let dt: &PyAny = unsafe { py.from_owned_ptr(PyDateTime_FromTimestamp(args.as_ptr())) }; - let locals = PyDict::new(py); - locals.set_item("dt", dt).unwrap(); - py.run( - "import datetime; assert dt == datetime.datetime.fromtimestamp(100)", - None, - Some(locals), - ) - .unwrap(); - }) - } - - #[test] - fn test_date_fromtimestamp() { - Python::with_gil(|py| { - let args: Py = (100,).into_py(py); - unsafe { PyDateTime_IMPORT() }; - let dt: &PyAny = unsafe { py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr())) }; - let locals = PyDict::new(py); - locals.set_item("dt", dt).unwrap(); - py.run( - "import datetime; assert dt == datetime.date.fromtimestamp(100)", - None, - Some(locals), - ) - .unwrap(); - }) - } - - #[test] - #[cfg(not(all(PyPy, not(Py_3_8))))] - fn test_utc_timezone() { - Python::with_gil(|py| { - let utc_timezone = PyDateTime_TimeZone_UTC.as_ref(py); - let locals = PyDict::new(py); - locals.set_item("utc_timezone", utc_timezone).unwrap(); - py.run( - "import datetime; assert utc_timezone is datetime.timezone.utc", - None, - Some(locals), - ) - .unwrap(); - }) - } -} diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 85d63317cb4..81d6f38e4b7 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -21,25 +21,8 @@ //! //! [capi]: https://docs.python.org/3/c-api/index.html -#![allow( - missing_docs, - non_camel_case_types, - non_snake_case, - non_upper_case_globals, - clippy::upper_case_acronyms, - clippy::missing_safety_doc -)] - -#[cfg(not(Py_LIMITED_API))] -mod cpython; -#[cfg(not(Py_LIMITED_API))] -mod datetime; - -#[cfg(not(Py_LIMITED_API))] -pub use datetime::*; - -#[cfg(not(Py_LIMITED_API))] -use {libc::c_int, std::ops::Deref}; +#[cfg(all(not(Py_LIMITED_API), test))] +mod tests; // reexport raw bindings exposed in pyo3_ffi pub use pyo3_ffi::*; @@ -47,76 +30,3 @@ pub use pyo3_ffi::*; /// Helper to enable #\[pymethods\] to see the workaround for __ipow__ on Python 3.7 #[doc(hidden)] pub use crate::impl_::pymethods::ipowfunc; - -// Shadow raw ffi bindings for backwards compatability - -#[cfg(not(Py_LIMITED_API))] -pub static PyDateTimeAPI: _PyDateTimeAPI_impl = _PyDateTimeAPI_impl {}; - -#[cfg(any(Py_LIMITED_API, not(all(PyPy, not(Py_3_8)))))] -pub static PyDateTime_TimeZone_UTC: _PyDateTime_TimeZone_UTC_impl = _PyDateTime_TimeZone_UTC_impl { - inner: &PyDateTimeAPI, -}; - -#[cfg(not(Py_LIMITED_API))] -macro_rules! reexport_with_autoinit { - ($(#[$outer:meta] pub unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => { - $( - #[inline] - #[$outer] - /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. - pub unsafe fn $name($arg: *mut PyObject) -> $ret { - // Deref PyDateTimeAPI so that the mutable static is always initalized - let _ = PyDateTimeAPI.deref(); - pyo3_ffi::$name($arg) - } - )* - - - }; -} - -// Type Check macros -// -// These are bindings around the C API typecheck macros, all of them return -// `1` if True and `0` if False. In all type check macros, the argument (`op`) -// must not be `NULL`. - -#[cfg(not(Py_LIMITED_API))] -reexport_with_autoinit! { - /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. - pub unsafe fn PyDate_Check(op: *mut PyObject) -> c_int; - - /// Check if `op`'s type is exactly `PyDateTimeAPI.DateType`. - pub unsafe fn PyDate_CheckExact(op: *mut PyObject) -> c_int; - - /// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype. - pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int; - - /// Check if `op`'s type is exactly `PyDateTimeAPI.DateTimeType`. - pub unsafe fn PyDateTime_CheckExact(op: *mut PyObject) -> c_int; - - /// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype. - pub unsafe fn PyTime_Check(op: *mut PyObject) -> c_int; - - /// Check if `op`'s type is exactly `PyDateTimeAPI.TimeType`. - pub unsafe fn PyTime_CheckExact(op: *mut PyObject) -> c_int; - - /// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype. - pub unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int; - - /// Check if `op`'s type is exactly `PyDateTimeAPI.DeltaType`. - pub unsafe fn PyDelta_CheckExact(op: *mut PyObject) -> c_int; - - /// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype. - pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int; - - /// Check if `op`'s type is exactly `PyDateTimeAPI.TZInfoType`. - pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int; - - #[cfg(not(PyPy))] - pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject; - - #[cfg(not(PyPy))] - pub unsafe fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject; -} diff --git a/src/ffi/tests.rs b/src/ffi/tests.rs new file mode 100644 index 00000000000..15b1eb3623b --- /dev/null +++ b/src/ffi/tests.rs @@ -0,0 +1,183 @@ +use crate::ffi::*; +use crate::{types::PyDict, AsPyPointer, IntoPy, Py, PyAny, Python}; + +#[cfg(target_endian = "little")] +use crate::types::PyString; +#[cfg(target_endian = "little")] +use libc::wchar_t; + +#[test] +fn test_datetime_fromtimestamp() { + Python::with_gil(|py| { + let args: Py = (100,).into_py(py); + unsafe { PyDateTime_IMPORT() }; + let dt: &PyAny = unsafe { py.from_owned_ptr(PyDateTime_FromTimestamp(args.as_ptr())) }; + let locals = PyDict::new(py); + locals.set_item("dt", dt).unwrap(); + py.run( + "import datetime; assert dt == datetime.datetime.fromtimestamp(100)", + None, + Some(locals), + ) + .unwrap(); + }) +} + +#[test] +fn test_date_fromtimestamp() { + Python::with_gil(|py| { + let args: Py = (100,).into_py(py); + unsafe { PyDateTime_IMPORT() }; + let dt: &PyAny = unsafe { py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr())) }; + let locals = PyDict::new(py); + locals.set_item("dt", dt).unwrap(); + py.run( + "import datetime; assert dt == datetime.date.fromtimestamp(100)", + None, + Some(locals), + ) + .unwrap(); + }) +} + +#[test] +#[cfg(not(all(PyPy, not(Py_3_8))))] +fn test_utc_timezone() { + Python::with_gil(|py| { + let utc_timezone = unsafe { + PyDateTime_IMPORT(); + &*(&PyDateTime_TimeZone_UTC() as *const *mut crate::ffi::PyObject + as *const crate::PyObject) + }; + let locals = PyDict::new(py); + locals.set_item("utc_timezone", utc_timezone).unwrap(); + py.run( + "import datetime; assert utc_timezone is datetime.timezone.utc", + None, + Some(locals), + ) + .unwrap(); + }) +} + +#[cfg(target_endian = "little")] +#[test] +fn ascii_object_bitfield() { + let ob_base: PyObject = unsafe { std::mem::zeroed() }; + + let mut o = PyASCIIObject { + ob_base, + length: 0, + hash: 0, + state: 0, + wstr: std::ptr::null_mut() as *mut wchar_t, + }; + + unsafe { + assert_eq!(o.interned(), 0); + assert_eq!(o.kind(), 0); + assert_eq!(o.compact(), 0); + assert_eq!(o.ascii(), 0); + assert_eq!(o.ready(), 0); + + for i in 0..4 { + o.state = i; + assert_eq!(o.interned(), i); + } + + for i in 0..8 { + o.state = i << 2; + assert_eq!(o.kind(), i); + } + + o.state = 1 << 5; + assert_eq!(o.compact(), 1); + + o.state = 1 << 6; + assert_eq!(o.ascii(), 1); + + o.state = 1 << 7; + assert_eq!(o.ready(), 1); + } +} + +#[cfg(target_endian = "little")] +#[test] +#[cfg_attr(Py_3_10, allow(deprecated))] +fn ascii() { + Python::with_gil(|py| { + // This test relies on implementation details of PyString. + let s = PyString::new(py, "hello, world"); + let ptr = s.as_ptr(); + + unsafe { + let ascii_ptr = ptr as *mut PyASCIIObject; + let ascii = ascii_ptr.as_ref().unwrap(); + + assert_eq!(ascii.interned(), 0); + assert_eq!(ascii.kind(), PyUnicode_1BYTE_KIND); + assert_eq!(ascii.compact(), 1); + assert_eq!(ascii.ascii(), 1); + assert_eq!(ascii.ready(), 1); + + assert_eq!(PyUnicode_IS_ASCII(ptr), 1); + assert_eq!(PyUnicode_IS_COMPACT(ptr), 1); + assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 1); + + assert!(!PyUnicode_1BYTE_DATA(ptr).is_null()); + // 2 and 4 byte macros return nonsense for this string instance. + assert_eq!(PyUnicode_KIND(ptr), PyUnicode_1BYTE_KIND); + + assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null()); + // _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings. + assert!(!PyUnicode_DATA(ptr).is_null()); + + assert_eq!(PyUnicode_GET_LENGTH(ptr), s.len().unwrap() as _); + assert_eq!(PyUnicode_IS_READY(ptr), 1); + + // This has potential to mutate object. But it should be a no-op since + // we're already ready. + assert_eq!(PyUnicode_READY(ptr), 0); + } + }) +} + +#[cfg(target_endian = "little")] +#[test] +#[cfg_attr(Py_3_10, allow(deprecated))] +fn ucs4() { + Python::with_gil(|py| { + let s = "哈哈🐈"; + let py_string = PyString::new(py, s); + let ptr = py_string.as_ptr(); + + unsafe { + let ascii_ptr = ptr as *mut PyASCIIObject; + let ascii = ascii_ptr.as_ref().unwrap(); + + assert_eq!(ascii.interned(), 0); + assert_eq!(ascii.kind(), PyUnicode_4BYTE_KIND); + assert_eq!(ascii.compact(), 1); + assert_eq!(ascii.ascii(), 0); + assert_eq!(ascii.ready(), 1); + + assert_eq!(PyUnicode_IS_ASCII(ptr), 0); + assert_eq!(PyUnicode_IS_COMPACT(ptr), 1); + assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 0); + + assert!(!PyUnicode_4BYTE_DATA(ptr).is_null()); + assert_eq!(PyUnicode_KIND(ptr), PyUnicode_4BYTE_KIND); + + assert!(!_PyUnicode_COMPACT_DATA(ptr).is_null()); + // _PyUnicode_NONCOMPACT_DATA isn't valid for compact strings. + assert!(!PyUnicode_DATA(ptr).is_null()); + + assert_eq!(PyUnicode_GET_LENGTH(ptr), py_string.len().unwrap() as _); + assert_eq!(PyUnicode_IS_READY(ptr), 1); + + // This has potential to mutate object. But it should be a no-op since + // we're already ready. + assert_eq!(PyUnicode_READY(ptr), 0); + } + }) +} diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 990d82ee2e2..84b9444bdbd 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -5,10 +5,9 @@ use crate::err::PyResult; use crate::ffi; -#[cfg(PyPy)] -use crate::ffi::datetime::{PyDateTime_FromTimestamp, PyDate_FromTimestamp}; -use crate::ffi::PyDateTimeAPI; -use crate::ffi::{PyDateTime_Check, PyDate_Check, PyDelta_Check, PyTZInfo_Check, PyTime_Check}; +use crate::ffi::{ + PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp, +}; #[cfg(not(PyPy))] use crate::ffi::{PyDateTime_DATE_GET_FOLD, PyDateTime_TIME_GET_FOLD}; use crate::ffi::{ @@ -26,8 +25,62 @@ use crate::ffi::{ use crate::types::PyTuple; use crate::{AsPyPointer, PyAny, PyObject, Python, ToPyObject}; use std::os::raw::c_int; -#[cfg(not(PyPy))] -use std::ptr; + +fn ensure_datetime_api(_py: Python) -> &'static PyDateTime_CAPI { + unsafe { + if pyo3_ffi::PyDateTimeAPI.get().is_null() { + PyDateTime_IMPORT() + } + + &*pyo3_ffi::PyDateTimeAPI.get() + } +} + +// Type Check macros +// +// These are bindings around the C API typecheck macros, all of them return +// `1` if True and `0` if False. In all type check macros, the argument (`op`) +// must not be `NULL`. The reexported implementations here call ensure_datetime_api is initalized +// +// # Safety +// +// These functions must only be called when the GIL is held! + +macro_rules! reexport_with_autoinit { + ($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => { + $( + #[$outer] + #[allow(non_snake_case)] + /// # Safety + /// + /// Must only be called when the GIL is aquired + unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret { + + let _ = ensure_datetime_api(Python::assume_gil_acquired()); + crate::ffi::$name($arg) + } + )* + + + }; +} + +reexport_with_autoinit! { + /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. + unsafe fn PyDate_Check(op: *mut PyObject) -> c_int; + + /// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype. + unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int; + + /// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype. + unsafe fn PyTime_Check(op: *mut PyObject) -> c_int; + + /// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype. + unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int; + + /// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype. + unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int; +} // Access traits @@ -111,7 +164,9 @@ pub struct PyDate(PyAny); pyobject_native_type!( PyDate, crate::ffi::PyDateTime_Date, - *PyDateTimeAPI.DateType, + // REVIEW: this is save because this is only ever called when a py object is aqured we just + // have no way to access it due to the macro + *ensure_datetime_api(Python::assume_gil_acquired()).DateType, #module=Some("datetime"), #checkfunction=PyDate_Check ); @@ -120,11 +175,11 @@ impl PyDate { /// Creates a new `datetime.date`. pub fn new(py: Python, year: i32, month: u8, day: u8) -> PyResult<&PyDate> { unsafe { - let ptr = (PyDateTimeAPI.Date_FromDate)( + let ptr = (ensure_datetime_api(py).Date_FromDate)( year, c_int::from(month), c_int::from(day), - PyDateTimeAPI.DateType, + ensure_datetime_api(py).DateType, ); py.from_owned_ptr_or_err(ptr) } @@ -136,14 +191,11 @@ impl PyDate { pub fn from_timestamp(py: Python, timestamp: i64) -> PyResult<&PyDate> { let time_tuple = PyTuple::new(py, &[timestamp]); + // safety ensure that the API is loaded + let _api = ensure_datetime_api(py); + unsafe { - #[cfg(PyPy)] let ptr = PyDate_FromTimestamp(time_tuple.as_ptr()); - - #[cfg(not(PyPy))] - let ptr = - (PyDateTimeAPI.Date_FromTimestamp)(PyDateTimeAPI.DateType, time_tuple.as_ptr()); - py.from_owned_ptr_or_err(ptr) } } @@ -169,7 +221,9 @@ pub struct PyDateTime(PyAny); pyobject_native_type!( PyDateTime, crate::ffi::PyDateTime_DateTime, - *PyDateTimeAPI.DateTimeType, + // REVIEW: this is save because this is only ever called when a py object is aqured we just + // have no way to access it due to the macro + *ensure_datetime_api(Python::assume_gil_acquired()).DateType, #module=Some("datetime"), #checkfunction=PyDateTime_Check ); @@ -187,8 +241,9 @@ impl PyDateTime { microsecond: u32, tzinfo: Option<&PyObject>, ) -> PyResult<&'p PyDateTime> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.DateTime_FromDateAndTime)( + let ptr = (api.DateTime_FromDateAndTime)( year, c_int::from(month), c_int::from(day), @@ -197,7 +252,7 @@ impl PyDateTime { c_int::from(second), microsecond as c_int, opt_to_pyobj(py, tzinfo), - PyDateTimeAPI.DateTimeType, + api.DateTimeType, ); py.from_owned_ptr_or_err(ptr) } @@ -219,8 +274,9 @@ impl PyDateTime { tzinfo: Option<&PyObject>, fold: bool, ) -> PyResult<&'p PyDateTime> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.DateTime_FromDateAndTimeAndFold)( + let ptr = (api.DateTime_FromDateAndTimeAndFold)( year, c_int::from(month), c_int::from(day), @@ -230,7 +286,7 @@ impl PyDateTime { microsecond as c_int, opt_to_pyobj(py, tzinfo), c_int::from(fold), - PyDateTimeAPI.DateTimeType, + api.DateTimeType, ); py.from_owned_ptr_or_err(ptr) } @@ -253,19 +309,11 @@ impl PyDateTime { let args = PyTuple::new(py, &[timestamp, time_zone_info]); + // safety ensure API is loaded + let _api = ensure_datetime_api(py); + unsafe { - #[cfg(PyPy)] let ptr = PyDateTime_FromTimestamp(args.as_ptr()); - - #[cfg(not(PyPy))] - let ptr = { - (PyDateTimeAPI.DateTime_FromTimestamp)( - PyDateTimeAPI.DateTimeType, - args.as_ptr(), - ptr::null_mut(), - ) - }; - py.from_owned_ptr_or_err(ptr) } } @@ -314,7 +362,9 @@ pub struct PyTime(PyAny); pyobject_native_type!( PyTime, crate::ffi::PyDateTime_Time, - *PyDateTimeAPI.TimeType, + // REVIEW: this is save because this is only ever called when a py object is aqured we just + // have no way to access it due to the macro + *ensure_datetime_api(Python::assume_gil_acquired()).TimeType, #module=Some("datetime"), #checkfunction=PyTime_Check ); @@ -329,14 +379,15 @@ impl PyTime { microsecond: u32, tzinfo: Option<&PyObject>, ) -> PyResult<&'p PyTime> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.Time_FromTime)( + let ptr = (api.Time_FromTime)( c_int::from(hour), c_int::from(minute), c_int::from(second), microsecond as c_int, opt_to_pyobj(py, tzinfo), - PyDateTimeAPI.TimeType, + api.TimeType, ); py.from_owned_ptr_or_err(ptr) } @@ -353,15 +404,16 @@ impl PyTime { tzinfo: Option<&PyObject>, fold: bool, ) -> PyResult<&'p PyTime> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.Time_FromTimeAndFold)( + let ptr = (api.Time_FromTimeAndFold)( c_int::from(hour), c_int::from(minute), c_int::from(second), microsecond as c_int, opt_to_pyobj(py, tzinfo), fold as c_int, - PyDateTimeAPI.TimeType, + api.TimeType, ); py.from_owned_ptr_or_err(ptr) } @@ -399,7 +451,9 @@ pub struct PyTzInfo(PyAny); pyobject_native_type!( PyTzInfo, crate::ffi::PyObject, - *PyDateTimeAPI.TZInfoType, + // REVIEW: this is save because this is only ever called when a py object is aqured we just + // have no way to access it due to the macro + *ensure_datetime_api(Python::assume_gil_acquired()).TZInfoType, #module=Some("datetime"), #checkfunction=PyTZInfo_Check ); @@ -410,7 +464,9 @@ pub struct PyDelta(PyAny); pyobject_native_type!( PyDelta, crate::ffi::PyDateTime_Delta, - *PyDateTimeAPI.DeltaType, + // REVIEW: this is save because this is only ever called when a py object is aqured we just + // have no way to access it due to the macro + *ensure_datetime_api(Python::assume_gil_acquired()).DeltaType, #module=Some("datetime"), #checkfunction=PyDelta_Check ); @@ -424,13 +480,14 @@ impl PyDelta { microseconds: i32, normalize: bool, ) -> PyResult<&PyDelta> { + let api = ensure_datetime_api(py); unsafe { - let ptr = (PyDateTimeAPI.Delta_FromDelta)( + let ptr = (api.Delta_FromDelta)( days as c_int, seconds as c_int, microseconds as c_int, normalize as c_int, - PyDateTimeAPI.DeltaType, + api.DeltaType, ); py.from_owned_ptr_or_err(ptr) } diff --git a/tests/test_datetime.rs b/tests/test_datetime.rs index 0cb5eddf72a..976c04efd1b 100644 --- a/tests/test_datetime.rs +++ b/tests/test_datetime.rs @@ -2,6 +2,7 @@ use pyo3::prelude::*; use pyo3::types::IntoPyDict; +use pyo3_ffi::PyDateTime_IMPORT; fn _get_subclasses<'p>( py: &'p Python, @@ -57,7 +58,7 @@ fn test_date_check() { let gil = Python::acquire_gil(); let py = gil.python(); let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "date", "2018, 1, 1").unwrap(); - + unsafe { PyDateTime_IMPORT() } assert_check_exact!(PyDate_Check, PyDate_CheckExact, obj); assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_obj); assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_sub_obj); @@ -68,6 +69,7 @@ fn test_time_check() { let gil = Python::acquire_gil(); let py = gil.python(); let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "time", "12, 30, 15").unwrap(); + unsafe { PyDateTime_IMPORT() } assert_check_exact!(PyTime_Check, PyTime_CheckExact, obj); assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_obj); @@ -81,6 +83,7 @@ fn test_datetime_check() { let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "datetime", "2018, 1, 1, 13, 30, 15") .map_err(|e| e.print(py)) .unwrap(); + unsafe { PyDateTime_IMPORT() } assert_check_only!(PyDate_Check, PyDate_CheckExact, obj); assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj); @@ -93,6 +96,7 @@ fn test_delta_check() { let gil = Python::acquire_gil(); let py = gil.python(); let (obj, sub_obj, sub_sub_obj) = _get_subclasses(&py, "timedelta", "1, -3").unwrap(); + unsafe { PyDateTime_IMPORT() } assert_check_exact!(PyDelta_Check, PyDelta_CheckExact, obj); assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj);