Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell.dict/cpython.txt
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ pybuilddir
pycore
pyinner
pydecimal
pyerrors
Pyfunc
pylifecycle
pymain
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,15 @@ jobs:
uses: ./.github/actions/install-macos-deps

- name: run rust tests
run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --features threading ${{ env.CARGO_ARGS }}
run: cargo test --workspace --exclude rustpython-capi ${{ env.WORKSPACE_EXCLUDES }} --features threading ${{ env.CARGO_ARGS }}
Comment thread
bschoenmaeckers marked this conversation as resolved.
env:
INSTA_WORKSPACE_ROOT: ${{ github.workspace }}

- name: run c-api tests
working-directory: crates/capi
run: cargo test
if: runner.os != 'Windows' # Requires pyo3 0.29+ on Windows

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- run: cargo doc --locked
if: runner.os == 'Linux'

Expand Down
16 changes: 15 additions & 1 deletion crates/capi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
#![allow(clippy::missing_safety_doc)]

use crate::pyerrors::init_exception_statics;
use crate::pylifecycle::MAIN_INTERP;
use rustpython_vm::Interpreter;
pub use rustpython_vm::PyObject;
use rustpython_vm::{Context, Interpreter};
use std::sync::MutexGuard;

extern crate alloc;

pub mod object;
pub mod pyerrors;
pub mod pylifecycle;
pub mod pystate;
pub mod refcount;
mod util;

/// Get main interpreter of this process. Will be None if it has not been initialized yet.
pub fn get_main_interpreter() -> MutexGuard<'static, Option<Interpreter>> {
MAIN_INTERP
.lock()
.expect("Failed to lock interpreter mutex")
}

/// Set the main interpreter of this process. This method will panic when there is already an
/// interpreter set.
pub fn set_main_interpreter(interpreter: Interpreter) {
let mut interp = get_main_interpreter();
assert!(interp.is_none(), "Main interpreter is already set");
// Safety: Interpreter was not initialized before, so we can safely assume the statics are not used
unsafe { init_exception_statics(&Context::genesis().exceptions) };
*interp = Some(interpreter);
}
48 changes: 48 additions & 0 deletions crates/capi/src/object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use crate::PyObject;
use crate::pystate::with_vm;
use core::ffi::{c_int, c_uint, c_ulong};
use rustpython_vm::builtins::PyType;
use rustpython_vm::{AsObject, Py};

pub type PyTypeObject = Py<PyType>;

#[unsafe(no_mangle)]
pub unsafe extern "C" fn Py_TYPE(op: *mut PyObject) -> *const PyTypeObject {
unsafe { (*op).class() }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn Py_IS_TYPE(op: *mut PyObject, ty: *mut PyTypeObject) -> c_int {
with_vm(|_vm| {
let obj = unsafe { &*op };
let ty = unsafe { &*ty };
obj.class().is(ty)
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyType_GetFlags(ptr: *const PyTypeObject) -> c_ulong {
let ty = unsafe { &*ptr };
ty.slots.flags.bits() as u32 as c_ulong
}

#[unsafe(no_mangle)]
pub extern "C" fn Py_GetConstantBorrowed(constant_id: c_uint) -> *mut PyObject {
with_vm(|vm| {
let ctx = &vm.ctx;
let constant = match constant_id {
0 => ctx.none.as_object(),
1 => ctx.false_value.as_object(),
2 => ctx.true_value.as_object(),
3 => ctx.ellipsis.as_object(),
4 => ctx.not_implemented.as_object(),
_ => {
return Err(
vm.new_system_error("Invalid constant ID passed to Py_GetConstantBorrowed")
);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.as_raw();
Ok(constant)
})
}
277 changes: 277 additions & 0 deletions crates/capi/src/pyerrors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
use crate::PyObject;
use crate::pystate::with_vm;
use core::convert::Infallible;
use core::ffi::{CStr, c_char, c_int};
use core::ptr::NonNull;
use rustpython_vm::builtins::{PyBaseException, PyTuple, PyType};
use rustpython_vm::convert::IntoObject;
use rustpython_vm::exceptions::ExceptionZoo;
use rustpython_vm::{AsObject, PyObjectRef, PyResult};

macro_rules! define_exception_statics {
($( $(#[$meta:meta])* $export:ident => $exc:ident ),* $(,)?) => {
$(
$(#[$meta])*
#[unsafe(no_mangle)]
pub static mut $export: *mut PyObject = core::ptr::null_mut();
)*

#[allow(static_mut_refs)]
pub(crate) unsafe fn init_exception_statics(zoo: &'static ExceptionZoo) {
unsafe {
$(
$export = zoo.$exc.as_object().as_raw().cast_mut();
)*
}
}
};
}

define_exception_statics! {
PyExc_BaseException => base_exception_type,
PyExc_BaseExceptionGroup => base_exception_group,
PyExc_SystemExit => system_exit,
PyExc_KeyboardInterrupt => keyboard_interrupt,
PyExc_GeneratorExit => generator_exit,
PyExc_Exception => exception_type,
PyExc_StopIteration => stop_iteration,
PyExc_StopAsyncIteration => stop_async_iteration,
PyExc_ArithmeticError => arithmetic_error,
PyExc_FloatingPointError => floating_point_error,
PyExc_SystemError => system_error,
PyExc_TypeError => type_error,
PyExc_OverflowError => overflow_error,
PyExc_ZeroDivisionError => zero_division_error,
PyExc_AssertionError => assertion_error,
PyExc_IndexError => index_error,
PyExc_KeyError => key_error,
PyExc_LookupError => lookup_error,
PyExc_AttributeError => attribute_error,
PyExc_BufferError => buffer_error,
PyExc_EOFError => eof_error,
PyExc_ImportError => import_error,
PyExc_ModuleNotFoundError => module_not_found_error,
PyExc_MemoryError => memory_error,
PyExc_NameError => name_error,
PyExc_UnboundLocalError => unbound_local_error,
PyExc_OSError => os_error,
PyExc_BlockingIOError => blocking_io_error,
PyExc_ChildProcessError => child_process_error,
PyExc_ConnectionError => connection_error,
PyExc_BrokenPipeError => broken_pipe_error,
PyExc_ConnectionAbortedError => connection_aborted_error,
PyExc_ConnectionRefusedError => connection_refused_error,
PyExc_ConnectionResetError => connection_reset_error,
PyExc_FileExistsError => file_exists_error,
PyExc_FileNotFoundError => file_not_found_error,
PyExc_InterruptedError => interrupted_error,
PyExc_IsADirectoryError => is_a_directory_error,
PyExc_NotADirectoryError => not_a_directory_error,
PyExc_PermissionError => permission_error,
PyExc_ProcessLookupError => process_lookup_error,
PyExc_TimeoutError => timeout_error,
PyExc_ReferenceError => reference_error,
PyExc_RuntimeError => runtime_error,
PyExc_NotImplementedError => not_implemented_error,
PyExc_RecursionError => recursion_error,
PyExc_SyntaxError => syntax_error,
PyExc_IndentationError => indentation_error,
PyExc_TabError => tab_error,
PyExc_ValueError => value_error,
PyExc_UnicodeError => unicode_error,
PyExc_UnicodeDecodeError => unicode_decode_error,
PyExc_UnicodeEncodeError => unicode_encode_error,
PyExc_UnicodeTranslateError => unicode_translate_error,
PyExc_Warning => warning,
PyExc_DeprecationWarning => deprecation_warning,
PyExc_PendingDeprecationWarning => pending_deprecation_warning,
PyExc_RuntimeWarning => runtime_warning,
PyExc_SyntaxWarning => syntax_warning,
PyExc_UserWarning => user_warning,
PyExc_FutureWarning => future_warning,
PyExc_ImportWarning => import_warning,
PyExc_UnicodeWarning => unicode_warning,
PyExc_BytesWarning => bytes_warning,
PyExc_ResourceWarning => resource_warning,
PyExc_EncodingWarning => encoding_warning,
}

#[unsafe(no_mangle)]
pub extern "C" fn PyErr_Occurred() -> *mut PyObject {
with_vm(|vm| {
vm.current_exception()
.map(|exc| exc.class().as_object().as_raw())
.unwrap_or_default()
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

#[unsafe(no_mangle)]
pub extern "C" fn PyErr_GetRaisedException() -> *mut PyObject {
with_vm(|vm| {
vm.take_raised_exception()
.map(|exc| exc.into_object().into_raw().as_ptr())
.unwrap_or_default()
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyErr_SetRaisedException(exc: *mut PyObject) {
with_vm(|vm| {
if let Some(exc) = NonNull::new(exc) {
let exception = unsafe { PyObjectRef::from_raw(exc).downcast_unchecked() };
vm.set_exception(Some(exception));
} else {
vm.set_exception(None);
}
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyErr_SetObject(exception: *mut PyObject, value: *mut PyObject) {
with_vm::<PyResult<Infallible>, _>(|vm| {
let exc_type = unsafe { (&*exception).to_owned() };
let exc_val = unsafe { (&*value).to_owned() };

let normalized = vm.normalize_exception(exc_type, exc_val, vm.ctx.none())?;
Err(normalized)
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyErr_SetString(exception: *mut PyObject, message: *const c_char) {
with_vm::<PyResult<Infallible>, _>(|vm| {
let exc_type = unsafe { &*exception }.try_downcast_ref::<PyType>(vm)?;

let Ok(message) = unsafe { CStr::from_ptr(message) }.to_str() else {
return Err(vm.new_type_error("Exception message is not valid UTF-8"));
};

Comment thread
coderabbitai[bot] marked this conversation as resolved.
let exc = vm.invoke_exception(
exc_type.to_owned(),
vec![vm.ctx.new_str(message).into_object()],
)?;

Err(exc)
})
}

#[unsafe(no_mangle)]
pub extern "C" fn PyErr_PrintEx(_set_sys_last_vars: c_int) {
with_vm(|vm| {
let exception = vm
.take_raised_exception()
.expect("No exception set in PyErr_PrintEx");

vm.print_exception(exception);
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyErr_DisplayException(exc: *mut PyObject) {
with_vm(|vm| {
let exception = unsafe { &*exc }
.downcast_ref::<PyBaseException>()
.expect("PyErr_DisplayException exc must be an exception instance")
.to_owned();

vm.print_exception(exception);
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyErr_WriteUnraisable(obj: *mut PyObject) {
with_vm(|vm| {
let exception = vm
.take_raised_exception()
.expect("No exception set in PyErr_WriteUnraisable");

let object = unsafe { vm.unwrap_or_none(obj.as_ref().map(|obj| obj.to_owned())) };

vm.run_unraisable(exception, None, object)
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyErr_NewException(
name: *const c_char,
base: *mut PyObject,
dict: *mut PyObject,
) -> *mut PyObject {
with_vm(|vm| {
let (module, name) = unsafe {
CStr::from_ptr(name)
.to_str()
.expect("Exception name is not valid UTF-8")
.rsplit_once('.')
.expect("Exception name must be of the form 'module.ExceptionName'")
};

let bases = unsafe { base.as_ref() }.map(|bases| {
if let Some(ty) = bases.downcast_ref::<PyType>() {
vec![ty.to_owned()]
} else if let Some(tuple) = bases.downcast_ref::<PyTuple>() {
tuple
.iter()
.map(|item| item.to_owned().downcast())
.collect::<Result<Vec<_>, _>>()
.expect("PyErr_NewException base tuple must contain only types")
} else {
panic!("PyErr_NewException base must be a type or a tuple of types");
}
});

assert!(
dict.is_null(),
"PyErr_NewException with non-null dict is not supported yet"
);

vm.ctx.new_exception_type(module, name, bases)
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyErr_NewExceptionWithDoc(
name: *const c_char,
_doc: *const c_char,
base: *mut PyObject,
dict: *mut PyObject,
) -> *mut PyObject {
unsafe { PyErr_NewException(name, base, dict) }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyErr_GivenExceptionMatches(
given: *mut PyObject,
exc: *mut PyObject,
) -> c_int {
with_vm(|vm| {
let given = unsafe { &*given };
let exc = unsafe { &*exc };

given.is_subclass(exc, vm)
})
}

#[cfg(test)]
mod tests {
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;

#[test]
fn test_raised_exception() {
Python::attach(|py| {
PyTypeError::new_err(py.None()).restore(py);
assert!(PyErr::occurred(py));
assert!(unsafe { !pyo3::ffi::PyErr_GetRaisedException().is_null() });
assert!(!PyErr::occurred(py));
})
}

#[test]
fn test_error_is_instance() {
Python::attach(|py| {
let err = PyTypeError::new_err(py.None());
assert!(err.is_instance_of::<PyTypeError>(py));
})
}
}
Loading
Loading