diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b24ee45388..493081df7dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the internal `IntoPyResult` trait to give better error messages when function return types do not implement `IntoPy`. [#2326](https://github.com/PyO3/pyo3/pull/2326) - Add `PyDictKeys`, `PyDictValues` and `PyDictItems` Rust types to represent `dict_keys`, `dict_values` and `dict_items` types. [#2358](https://github.com/PyO3/pyo3/pull/2358) - Add an experimental `generate-import-lib` feature to support auto-generating non-abi3 python import libraries for Windows targets. [#2364](https://github.com/PyO3/pyo3/pull/2364) +- Add FFI definition `Py_ExitStatusException`. [#2370](https://github.com/PyO3/pyo3/pull/2370) ### Changed @@ -28,7 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed incorrectly disabled FFI definition `PyThreadState_DeleteCurrent`. [#2357](https://github.com/PyO3/pyo3/pull/2357) - Correct FFI definition `PyEval_EvalCodeEx` to take `*const *mut PyObject` array arguments instead of `*mut *mut PyObject` (this was changed in CPython 3.6). [#2368](https://github.com/PyO3/pyo3/pull/2368) - +- Added missing `warn_default_encoding` field to `PyConfig` on 3.10+. The previously missing field could result in incorrect behavior or crashes. [#2370](https://github.com/PyO3/pyo3/pull/2370) +- Fixed order of `pathconfig_warnings` and `program_name` fields of `PyConfig` on 3.10+. Previously, the order of the fields was swapped and this could lead to incorrect behavior or crashes. [#2370](https://github.com/PyO3/pyo3/pull/2370) ## [0.16.4] - 2022-04-14 diff --git a/Cargo.toml b/Cargo.toml index 182234f5076..90edab33547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ send_wrapper = "0.5" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.61" rayon = "1.0.2" +widestring = "0.5.1" [build-dependencies] pyo3-build-config = { path = "pyo3-build-config", version = "0.16.4", features = ["resolve-config"] } diff --git a/pyo3-ffi/src/cpython/initconfig.rs b/pyo3-ffi/src/cpython/initconfig.rs index 552a3664191..fca7ca39602 100644 --- a/pyo3-ffi/src/cpython/initconfig.rs +++ b/pyo3-ffi/src/cpython/initconfig.rs @@ -110,6 +110,8 @@ pub struct PyConfig { pub warnoptions: PyWideStringList, pub site_import: c_int, pub bytes_warning: c_int, + #[cfg(Py_3_10)] + pub warn_default_encoding: c_int, pub inspect: c_int, pub interactive: c_int, pub optimization_level: c_int, @@ -127,9 +129,9 @@ pub struct PyConfig { pub legacy_windows_stdio: c_int, pub check_hash_pycs_mode: *mut wchar_t, + pub pathconfig_warnings: c_int, #[cfg(Py_3_10)] pub program_name: *mut wchar_t, - pub pathconfig_warnings: c_int, pub pythonpath_env: *mut wchar_t, pub home: *mut wchar_t, #[cfg(Py_3_10)] diff --git a/pyo3-ffi/src/cpython/pylifecycle.rs b/pyo3-ffi/src/cpython/pylifecycle.rs index 3f1f16e26a4..08c47881e03 100644 --- a/pyo3-ffi/src/cpython/pylifecycle.rs +++ b/pyo3-ffi/src/cpython/pylifecycle.rs @@ -23,7 +23,7 @@ extern "C" { pub fn Py_RunMain() -> c_int; - // skipped Py_ExitStatusException + pub fn Py_ExitStatusException(status: PyStatus) -> !; // skipped _Py_RestoreSignals diff --git a/tests/test_pep_587.rs b/tests/test_pep_587.rs new file mode 100644 index 00000000000..7cb584ca164 --- /dev/null +++ b/tests/test_pep_587.rs @@ -0,0 +1,76 @@ +#![cfg(all(Py_3_8, not(any(PyPy, Py_LIMITED_API))))] + +use pyo3::ffi; + +#[cfg(Py_3_10)] +use widestring::WideCString; + +#[test] +fn test_default_interpreter() { + macro_rules! ensure { + ($py_call:expr) => {{ + let status = $py_call; + unsafe { + if ffi::PyStatus_Exception(status) != 0 { + ffi::Py_ExitStatusException(status); + } + } + }}; + } + + let mut preconfig = unsafe { std::mem::zeroed() }; + + unsafe { ffi::PyPreConfig_InitPythonConfig(&mut preconfig) }; + preconfig.utf8_mode = 1; + + ensure!(unsafe { ffi::Py_PreInitialize(&preconfig) }); + + let mut config = unsafe { std::mem::zeroed() }; + unsafe { ffi::PyConfig_InitPythonConfig(&mut config) }; + + // Require manually calling _Py_InitializeMain to exercise more ffi code + config._init_main = 0; + + // Set program_name as regression test for #2370 + #[cfg(all(Py_3_10, windows))] + { + use libc::wchar_t; + use std::ffi::OsStr; + use std::os::windows::ffi::OsStrExt; + let mut value: Vec = OsStr::new("some_test\0").encode_wide().collect(); + + unsafe { + ffi::PyConfig_SetString(&mut config, &mut config.program_name, value.as_ptr()); + } + } + #[cfg(all(Py_3_10, unix))] + { + unsafe { + ffi::PyConfig_SetBytesString( + &mut config, + &mut config.program_name, + "some_test\0".as_ptr().cast(), + ); + } + } + + ensure!(unsafe { ffi::Py_InitializeFromConfig(&config) }); + + // The GIL is held. + assert_eq!(unsafe { ffi::PyGILState_Check() }, 1); + + // Now proceed with the Python main initialization. This will initialize + // importlib. And if the custom importlib bytecode was registered above, + // our extension module will get imported and initialized. + ensure!(unsafe { ffi::_Py_InitializeMain() }); + + // The GIL is held after finishing initialization. + assert_eq!(unsafe { ffi::PyGILState_Check() }, 1); + + // Confirm program name set above was picked up correctly + #[cfg(Py_3_10)] + { + let program_name = unsafe { WideCString::from_ptr_str(ffi::Py_GetProgramName().cast()) }; + assert_eq!(program_name.to_string().unwrap(), "some_test"); + } +}