mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Align ctypes call ownership and C error state with Python 3.15 (#9218)
* Align ctypes call ownership and C error state with Python 3.15 Keep native raised errors separate from handled exceptions and preserve them across teardown callbacks. Import the related canonical ctypes call and type contracts. Assisted-by: OpenAI Codex: model version unavailable * Enumerate ctypes shared libraries natively on Linux and Android Copy loader-owned names before Python filesystem decoding and update the related canonical ctypes utility and discovery tests from CPython 3.15. Preserve existing platform paths and dllist test markers. Assisted-by: OpenAI Codex: model version unavailable * Enable canonical dllist system enumeration coverage on Linux Regenerate the pinned test through update_lib patches with reviewed marker data. Preserve other platform conditions and the unavailable extension-fixture guard. Assisted-by: OpenAI Codex: model version unavailable * Match Python float and long-double C argument representations Use the existing Python float formatter for float and double C argument representations. Keep long-double arguments on the canonical generic-address representation path. Preserve the unchanged canonical parameter repr assertions. Assisted-by: OpenAI Codex: model version unavailable
This commit is contained in:
1 parent
1ffba21557
commit
1ba1107115
26 files changed
+569
-232
No files matched your search
Vendored
+13
-15
@@ -2,15 +2,13 @@
|
||||
|
||||
import os as _os
|
||||
import sys as _sys
|
||||
import sysconfig as _sysconfig
|
||||
import types as _types
|
||||
|
||||
__version__ = "1.1.0"
|
||||
lazy import sysconfig as _sysconfig
|
||||
|
||||
from _ctypes import Union, Structure, Array
|
||||
from _ctypes import _Pointer
|
||||
from _ctypes import CFuncPtr as _CFuncPtr
|
||||
from _ctypes import __version__ as _ctypes_version
|
||||
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
|
||||
from _ctypes import ArgumentError
|
||||
from _ctypes import SIZEOF_TIME_T
|
||||
@@ -18,9 +16,6 @@ from _ctypes import CField
|
||||
|
||||
from struct import calcsize as _calcsize
|
||||
|
||||
if __version__ != _ctypes_version:
|
||||
raise Exception("Version number mismatch", __version__, _ctypes_version)
|
||||
|
||||
if _os.name == "nt":
|
||||
from _ctypes import COMError, CopyComPointer, FormatError
|
||||
|
||||
@@ -211,13 +206,13 @@ if sizeof(c_longdouble) == sizeof(c_double):
|
||||
|
||||
try:
|
||||
class c_double_complex(_SimpleCData):
|
||||
_type_ = "D"
|
||||
_type_ = "Zd"
|
||||
_check_size(c_double_complex)
|
||||
class c_float_complex(_SimpleCData):
|
||||
_type_ = "F"
|
||||
_type_ = "Zf"
|
||||
_check_size(c_float_complex)
|
||||
class c_longdouble_complex(_SimpleCData):
|
||||
_type_ = "G"
|
||||
_type_ = "Zg"
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -381,12 +376,6 @@ def create_unicode_buffer(init, size=None):
|
||||
return buf
|
||||
raise TypeError(init)
|
||||
|
||||
|
||||
def SetPointerType(pointer, cls):
|
||||
import warnings
|
||||
warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
|
||||
pointer.set_type(cls)
|
||||
|
||||
def ARRAY(typ, len):
|
||||
return typ * len
|
||||
|
||||
@@ -681,3 +670,12 @@ else:
|
||||
raise SystemError(f"Unexpected sizeof(time_t): {SIZEOF_TIME_T=}")
|
||||
|
||||
_reset_cache()
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "__version__":
|
||||
from warnings import _deprecated
|
||||
|
||||
_deprecated("__version__", remove=(3, 20))
|
||||
return "1.1.0" # Do not change
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
Vendored
+2
-1
@@ -5,11 +5,12 @@ may change at any time.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from _ctypes import CField, buffer_info
|
||||
import ctypes
|
||||
|
||||
lazy import warnings
|
||||
|
||||
def round_down(n, multiple):
|
||||
assert n >= 0
|
||||
assert multiple > 0
|
||||
|
||||
Vendored
+10
-51
@@ -1,8 +1,9 @@
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
lazy import shutil
|
||||
lazy import subprocess
|
||||
|
||||
# find_library(name) returns the pathname of a library, or None.
|
||||
if os.name == "nt":
|
||||
|
||||
@@ -421,8 +422,7 @@ elif os.name == "posix":
|
||||
result = None
|
||||
try:
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
stderr=subprocess.PIPE)
|
||||
out, _ = p.communicate()
|
||||
res = re.findall(expr, os.fsdecode(out))
|
||||
for file in res:
|
||||
@@ -442,53 +442,12 @@ elif os.name == "posix":
|
||||
_get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
|
||||
|
||||
|
||||
# Listing loaded libraries on other systems will try to use
|
||||
# functions common to Linux and a few other Unix-like systems.
|
||||
# See the following for several platforms' documentation of the same API:
|
||||
# https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
|
||||
# https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
|
||||
# https://man.openbsd.org/dl_iterate_phdr
|
||||
# https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
|
||||
if (os.name == "posix" and
|
||||
sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
|
||||
import ctypes
|
||||
if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
|
||||
|
||||
class _dl_phdr_info(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dlpi_addr", ctypes.c_void_p),
|
||||
("dlpi_name", ctypes.c_char_p),
|
||||
("dlpi_phdr", ctypes.c_void_p),
|
||||
("dlpi_phnum", ctypes.c_ushort),
|
||||
]
|
||||
|
||||
_dl_phdr_callback = ctypes.CFUNCTYPE(
|
||||
ctypes.c_int,
|
||||
ctypes.POINTER(_dl_phdr_info),
|
||||
ctypes.c_size_t,
|
||||
ctypes.POINTER(ctypes.py_object),
|
||||
)
|
||||
|
||||
@_dl_phdr_callback
|
||||
def _info_callback(info, _size, data):
|
||||
libraries = data.contents.value
|
||||
name = os.fsdecode(info.contents.dlpi_name)
|
||||
libraries.append(name)
|
||||
return 0
|
||||
|
||||
_dl_iterate_phdr = _libc["dl_iterate_phdr"]
|
||||
_dl_iterate_phdr.argtypes = [
|
||||
_dl_phdr_callback,
|
||||
ctypes.POINTER(ctypes.py_object),
|
||||
]
|
||||
_dl_iterate_phdr.restype = ctypes.c_int
|
||||
|
||||
def dllist():
|
||||
"""Return a list of loaded shared libraries in the current process."""
|
||||
libraries = []
|
||||
_dl_iterate_phdr(_info_callback,
|
||||
ctypes.byref(ctypes.py_object(libraries)))
|
||||
return libraries
|
||||
# On platforms which provide dl_iterate_phdr(), dllist() is implemented
|
||||
# in _ctypes.
|
||||
try:
|
||||
from _ctypes import dllist
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
################################################################
|
||||
# test code
|
||||
|
||||
Vendored
+17
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import unittest
|
||||
from test import support
|
||||
from test.support import import_helper
|
||||
|
||||
@@ -6,5 +7,21 @@ from test.support import import_helper
|
||||
# skip tests if the _ctypes extension was not built
|
||||
import_helper.import_module('ctypes')
|
||||
|
||||
|
||||
class TestModule(unittest.TestCase):
|
||||
def test_deprecated__version__(self):
|
||||
import ctypes
|
||||
import _ctypes
|
||||
|
||||
for mod in (ctypes, _ctypes):
|
||||
with self.subTest(mod=mod):
|
||||
with self.assertWarnsRegex(
|
||||
DeprecationWarning,
|
||||
"'__version__' is deprecated and slated for removal in Python 3.20",
|
||||
) as cm:
|
||||
getattr(mod, "__version__")
|
||||
self.assertEqual(cm.filename, __file__)
|
||||
|
||||
|
||||
def load_tests(*args):
|
||||
return support.load_package_tests(os.path.dirname(__file__), *args)
|
||||
Vendored
-1
@@ -3,7 +3,6 @@
|
||||
import ctypes
|
||||
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr
|
||||
import sys
|
||||
from test import support
|
||||
|
||||
|
||||
_CData = Structure.__base__
|
||||
|
||||
+4
-1
@@ -5,7 +5,8 @@ from ctypes import (Structure, CDLL, CFUNCTYPE,
|
||||
c_short, c_int, c_long, c_longlong,
|
||||
c_byte, c_wchar, c_float, c_double,
|
||||
ArgumentError)
|
||||
from test.support import import_helper, skip_if_sanitizer
|
||||
from test.support import (import_helper, skip_if_sanitizer,
|
||||
skip_emscripten_stack_overflow, skip_if_huge_c_stack)
|
||||
_ctypes_test = import_helper.import_module("_ctypes_test")
|
||||
|
||||
|
||||
@@ -193,6 +194,8 @@ class BasicWrapTestCase(unittest.TestCase):
|
||||
(9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9))
|
||||
|
||||
@skip_if_sanitizer('requires deep stack', thread=True)
|
||||
@skip_if_huge_c_stack()
|
||||
@skip_emscripten_stack_overflow()
|
||||
def test_recursive_as_param(self):
|
||||
class A:
|
||||
pass
|
||||
|
||||
+3
-5
@@ -213,14 +213,12 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
||||
class F(metaclass=PyCSimpleType):
|
||||
_type_ = "\0"
|
||||
message = str(cm.exception)
|
||||
expected_type_chars = list('cbBhHiIlLdDFGfuzZqQPXOv?g')
|
||||
if not hasattr(ctypes, 'c_float_complex'):
|
||||
expected_type_chars.remove('F')
|
||||
expected_type_chars.remove('D')
|
||||
expected_type_chars.remove('G')
|
||||
expected_type_chars = list('cbBhHiIlLdfuzZqQPXOv?g')
|
||||
if not MS_WINDOWS:
|
||||
expected_type_chars.remove('X')
|
||||
self.assertIn("'" + ''.join(expected_type_chars) + "'", message)
|
||||
if hasattr(ctypes, 'c_float_complex'):
|
||||
self.assertIn("'Zf', 'Zd', 'Zg'", message)
|
||||
|
||||
def test_creating_pointer_in_dunder_init_3(self):
|
||||
"""Check if interfcase subclasses properly creates according internal
|
||||
|
||||
+15
@@ -328,6 +328,21 @@ class SampleCallbacksTestCase(unittest.TestCase):
|
||||
f"of ctypes callback function {func!r}")
|
||||
self.assertIsNone(cm.unraisable.object)
|
||||
|
||||
def test_narrow_int_return_widened(self):
|
||||
# gh-156933: Narrow integers were not widened on s390x
|
||||
CALLBACK = CFUNCTYPE(c_int)
|
||||
|
||||
dll = CDLL(_ctypes_test.__file__)
|
||||
_testfunc_callback_int_to_longlong = dll._testfunc_callback_int_to_longlong
|
||||
_testfunc_callback_int_to_longlong.argtypes = [CALLBACK]
|
||||
_testfunc_callback_int_to_longlong.restype = c_longlong
|
||||
|
||||
@CALLBACK
|
||||
def cb():
|
||||
return -1
|
||||
|
||||
self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Vendored
+1
-1
@@ -26,7 +26,7 @@ else:
|
||||
)
|
||||
class ListSharedLibraries(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(not APPLE, "TODO: RUSTPYTHON")
|
||||
@unittest.skipIf(not APPLE and sys.platform != "linux", "TODO: RUSTPYTHON")
|
||||
def test_lists_system(self):
|
||||
dlls = ctypes.util.dllist()
|
||||
|
||||
|
||||
Vendored
+17
-1
@@ -1,4 +1,5 @@
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
import test.support
|
||||
import unittest
|
||||
@@ -78,9 +79,24 @@ class Test_OpenGL_libs(unittest.TestCase):
|
||||
@unittest.skipUnless(sys.platform.startswith('linux'),
|
||||
'Test only valid for Linux')
|
||||
class FindLibraryLinux(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
try:
|
||||
p = subprocess.run(['ld', '--version'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
if p.stdout.startswith('mold '):
|
||||
# The mold linker is known to be incompatible with
|
||||
# the soft-deprecated ctypes.util.find_library
|
||||
# (which uses `ld -t`).
|
||||
raise unittest.SkipTest('Fails when ld is mold')
|
||||
|
||||
@thread_unsafe('uses setenv')
|
||||
def test_find_on_libpath(self):
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Run this module to regenerate the files:
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from test.support import import_helper, verbose
|
||||
from test.support import import_helper
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
|
||||
+27
-8
@@ -1,7 +1,6 @@
|
||||
import ctypes
|
||||
import unittest
|
||||
import warnings
|
||||
from ctypes import Structure, POINTER, pointer, c_char_p
|
||||
from ctypes import Structure, POINTER, pointer, c_char_p, c_int
|
||||
|
||||
# String-based "incomplete pointers" were implemented in ctypes 0.6.3 (2003, when
|
||||
# ctypes was an external project). They made obsolete by the current
|
||||
@@ -21,9 +20,7 @@ class TestSetPointerType(unittest.TestCase):
|
||||
_fields_ = [("name", c_char_p),
|
||||
("next", lpcell)]
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
ctypes.SetPointerType(lpcell, cell)
|
||||
lpcell.set_type(cell)
|
||||
|
||||
self.assertIs(POINTER(cell), lpcell)
|
||||
|
||||
@@ -50,10 +47,32 @@ class TestSetPointerType(unittest.TestCase):
|
||||
_fields_ = [("name", c_char_p),
|
||||
("next", lpcell)]
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
ctypes.SetPointerType(lpcell, cell)
|
||||
|
||||
lpcell.set_type(cell)
|
||||
self.assertIs(POINTER(cell), lpcell)
|
||||
|
||||
def test_set_type_updates_format(self):
|
||||
# gh-142966: set_type should update StgInfo.format
|
||||
# to match the element type's format
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
lp = POINTER("node")
|
||||
|
||||
class node(Structure):
|
||||
_fields_ = [("value", c_int)]
|
||||
|
||||
# Get the expected format before set_type
|
||||
node_format = memoryview(node()).format
|
||||
expected_format = "&" + node_format
|
||||
|
||||
lp.set_type(node)
|
||||
|
||||
# Create instance to check format via memoryview
|
||||
n = node(42)
|
||||
p = lp(n)
|
||||
actual_format = memoryview(p).format
|
||||
|
||||
# After set_type, the pointer's format should be "&<element_format>"
|
||||
self.assertEqual(actual_format, expected_format)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Vendored
+11
-2
@@ -119,8 +119,11 @@ class NumberTestCase(unittest.TestCase, ComplexesAreIdenticalMixin):
|
||||
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
|
||||
"requires C11 complex type")
|
||||
def test_complex(self):
|
||||
for t in [ctypes.c_double_complex, ctypes.c_float_complex,
|
||||
ctypes.c_longdouble_complex]:
|
||||
for format, t in [
|
||||
('Zd', ctypes.c_double_complex),
|
||||
('Zf', ctypes.c_float_complex),
|
||||
('Zg', ctypes.c_longdouble_complex),
|
||||
]:
|
||||
self.assertEqual(t(1).value, 1+0j)
|
||||
self.assertEqual(t(1.0).value, 1+0j)
|
||||
self.assertEqual(t(1+0.125j).value, 1+0.125j)
|
||||
@@ -128,6 +131,12 @@ class NumberTestCase(unittest.TestCase, ComplexesAreIdenticalMixin):
|
||||
self.assertEqual(t(FloatLike()).value, 2+0j)
|
||||
self.assertEqual(t(ComplexLike()).value, 1+1j)
|
||||
|
||||
prefix = '>' if sys.byteorder == 'big' else '<'
|
||||
num = t(1.0)
|
||||
self.assertEqual(memoryview(num).format, prefix + format)
|
||||
array = (t * 3)()
|
||||
self.assertEqual(memoryview(array).format, prefix + format)
|
||||
|
||||
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
|
||||
"requires C11 complex type")
|
||||
def test_complex_round_trip(self):
|
||||
|
||||
+8
@@ -1,6 +1,7 @@
|
||||
import sys
|
||||
import unittest
|
||||
import test.support
|
||||
import ctypes
|
||||
from ctypes import (CDLL, PyDLL, ArgumentError,
|
||||
Structure, Array, Union,
|
||||
_Pointer, _SimpleCData, _CFuncPtr,
|
||||
@@ -247,6 +248,13 @@ class SimpleTypesTestCase(unittest.TestCase):
|
||||
self.assertRegex(repr(c_char_p.from_param(b'hihi')), r"^<cparam 'z' \(0x[A-Fa-f0-9]+\)>$")
|
||||
self.assertRegex(repr(c_wchar_p.from_param('hihi')), r"^<cparam 'Z' \(0x[A-Fa-f0-9]+\)>$")
|
||||
self.assertRegex(repr(c_void_p.from_param(0x12)), r"^<cparam 'P' \(0x0*12\)>$")
|
||||
if hasattr(ctypes, 'c_double_complex'):
|
||||
self.assertRegex(repr(ctypes.c_double_complex.from_param(0)),
|
||||
r"^<cparam 'Zd' at 0x[A-Fa-f0-9]+>$")
|
||||
self.assertRegex(repr(ctypes.c_float_complex.from_param(0)),
|
||||
r"^<cparam 'Zf' at 0x[A-Fa-f0-9]+>$")
|
||||
self.assertRegex(repr(ctypes.c_longdouble_complex.from_param(0)),
|
||||
r"^<cparam 'Zg' at 0x[A-Fa-f0-9]+>$")
|
||||
|
||||
@test.support.cpython_only
|
||||
def test_from_param_result_refcount(self):
|
||||
|
||||
+7
@@ -299,9 +299,16 @@ class StructureTestCase(unittest.TestCase, StructCheckMixin):
|
||||
self.assertEqual(s.first, got.first)
|
||||
self.assertEqual(s.second, got.second)
|
||||
|
||||
@unittest.skipIf(support.is_wasm32, "wasm ABI is incompatible with test expectations")
|
||||
def _test_issue18060(self, Vector):
|
||||
# Regression tests for gh-62260
|
||||
|
||||
# This test passes a struct of two doubles by value to atan2(), whose C
|
||||
# signature is atan2(double, double), so it only works on platforms
|
||||
# where the abi of a function that takes a struct with two doubles
|
||||
# matches the abi of a function that takes two doubles. The wasm32 ABI
|
||||
# does not satisfy this condition and the test breaks.
|
||||
|
||||
# The call to atan2() should succeed if the
|
||||
# class fields were correctly cloned in the
|
||||
# subclasses. Otherwise, it will segfault.
|
||||
|
||||
@@ -101,7 +101,7 @@ define_py_check!(fn PyExceptionInstance_Check, exceptions.base_exception_type);
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn PyErr_Occurred() -> *mut PyObject {
|
||||
with_vm(|vm| {
|
||||
vm.current_exception()
|
||||
vm.raised_exception()
|
||||
.map(|exc| exc.class().as_object().as_raw())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
@@ -121,7 +121,7 @@ pub unsafe extern "C" fn PyErr_SetRaisedException(exc: *mut PyObject) {
|
||||
with_vm(|vm| {
|
||||
let exception =
|
||||
unsafe { exc.assume_owned_or_opt() }.map(|exc| unsafe { exc.downcast_unchecked() });
|
||||
vm.set_exception(exception);
|
||||
vm.set_raised_exception(exception);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -131,6 +131,15 @@ pub unsafe extern "C" fn PyErr_SetObject(exception: *mut PyObject, value: *mut P
|
||||
let exc_type = unsafe { exception.assume_borrowed() }.to_owned();
|
||||
let exc_val = unsafe { value.assume_borrowed() }.to_owned();
|
||||
|
||||
if let Some(cls) = exc_type.downcast_ref::<PyType>()
|
||||
&& cls.fast_issubclass(vm.ctx.exceptions.base_exception_type)
|
||||
&& !exc_val
|
||||
.downcast_ref::<PyBaseException>()
|
||||
.is_some_and(|exc| exc.fast_isinstance(cls))
|
||||
{
|
||||
// Exception constructors must run without a pending C error.
|
||||
vm.set_raised_exception(None);
|
||||
}
|
||||
let normalized = vm.normalize_exception(exc_type, exc_val, vm.ctx.none())?;
|
||||
Err(normalized)
|
||||
})
|
||||
@@ -142,6 +151,7 @@ pub unsafe extern "C" fn PyErr_SetString(exception: *mut PyObject, message: *con
|
||||
let exc_type = unsafe { exception.assume_borrowed_and_cast::<PyType>(vm) }?;
|
||||
let message = unsafe { message.try_as_str(vm) }?;
|
||||
|
||||
vm.set_raised_exception(None);
|
||||
let exc = vm.invoke_exception(exc_type, vec![vm.ctx.new_str(message).into_object()])?;
|
||||
|
||||
Err(exc)
|
||||
|
||||
@@ -186,7 +186,7 @@ impl FfiResult<()> for PyResult<Infallible> {
|
||||
|
||||
fn into_output(self, vm: &VirtualMachine) {
|
||||
match self {
|
||||
Err(err) => vm.set_exception(Some(err)),
|
||||
Err(err) => vm.set_raised_exception(Some(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ where
|
||||
fn into_output(self, vm: &VirtualMachine) -> Output {
|
||||
self.map_or_else(
|
||||
|err| {
|
||||
vm.set_exception(Some(err));
|
||||
vm.set_raised_exception(Some(err));
|
||||
T::ERR_VALUE
|
||||
},
|
||||
|obj| obj.into_output(vm),
|
||||
|
||||
@@ -221,6 +221,36 @@ const LONG_DOUBLE_SIZE: usize = core::mem::size_of::<c_double>();
|
||||
)))]
|
||||
const LONG_DOUBLE_SIZE: usize = core::mem::size_of::<c_double>();
|
||||
|
||||
/// Return the loader's current shared-object names, including its empty main entry.
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn dllist() -> Vec<std::ffi::OsString> {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
unsafe extern "C" fn collect(
|
||||
info: *mut libc::dl_phdr_info,
|
||||
_size: libc::size_t,
|
||||
data: *mut c_void,
|
||||
) -> c_int {
|
||||
// dl_iterate_phdr supplies a valid record and calls synchronously while
|
||||
// the loader keeps the object and its NUL-terminated name alive.
|
||||
let name = unsafe { (*info).dlpi_name };
|
||||
let names = unsafe { &mut *data.cast::<Vec<std::ffi::OsString>>() };
|
||||
if !name.is_null() {
|
||||
let bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec();
|
||||
names.push(std::ffi::OsString::from_vec(bytes));
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
let mut names = Vec::new();
|
||||
// The callback only copies loader-owned bytes into this live, exclusive
|
||||
// vector. It never invokes Python or mutates the loader's object list.
|
||||
unsafe {
|
||||
libc::dl_iterate_phdr(Some(collect), core::ptr::from_mut(&mut names).cast());
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
pub fn simple_type_size(ty: &str) -> Option<usize> {
|
||||
match ty {
|
||||
"c" | "b" => Some(core::mem::size_of::<c_schar>()),
|
||||
@@ -2011,7 +2041,7 @@ pub enum CallRet<'a> {
|
||||
Aggregate(&'a CTypeLayout),
|
||||
}
|
||||
|
||||
/// Per-call error-swapping options.
|
||||
/// Per-call ABI and error-swapping options.
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
@@ -2023,6 +2053,10 @@ pub enum CallRet<'a> {
|
||||
))]
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct CallOptions {
|
||||
/// Declared argument count for a variadic call. Values must already have
|
||||
/// their promoted C types; libffi rejects explicit narrow ctypes values.
|
||||
/// `None` uses a fixed-arity CIF.
|
||||
pub fixed_arg_count: Option<usize>,
|
||||
/// Swap the ctypes-local errno around the raw call (unix; ignored on windows).
|
||||
pub use_errno: bool,
|
||||
/// Swap the ctypes-local last error around the raw call (windows; ignored
|
||||
@@ -2066,6 +2100,9 @@ pub enum CallValue {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CallError {
|
||||
NullFunctionPointer,
|
||||
InvalidCallInterface {
|
||||
variadic: bool,
|
||||
},
|
||||
UnknownTypeCode(String),
|
||||
/// An aggregate argument's buffer was shorter than its layout size.
|
||||
BufferTooSmall {
|
||||
@@ -2156,7 +2193,13 @@ pub fn call(
|
||||
})
|
||||
.collect();
|
||||
|
||||
let cif = Cif::new(ffi_arg_types, ffi_return_type);
|
||||
let cif = match options.fixed_arg_count {
|
||||
Some(fixed) => Cif::try_new_variadic(ffi_arg_types, fixed, ffi_return_type),
|
||||
None => Cif::try_new(ffi_arg_types, ffi_return_type),
|
||||
}
|
||||
.map_err(|_| CallError::InvalidCallInterface {
|
||||
variadic: options.fixed_arg_count.is_some(),
|
||||
})?;
|
||||
|
||||
// Allocate the aggregate return buffer outside the error-swap window so no
|
||||
// allocation runs between the raw call and the errno/last-error capture.
|
||||
@@ -3673,6 +3716,7 @@ mod tests {
|
||||
CallOptions {
|
||||
use_errno: true,
|
||||
use_last_error: false,
|
||||
..CallOptions::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1627,6 +1627,7 @@ pub(crate) fn release_buffer_from_python(
|
||||
// releasebuffer_call_python, for a buffer acquired from a native exporter
|
||||
pub(crate) fn release_buffer_call_python(buffer: &PyBuffer) {
|
||||
crate::vm::thread::try_with_current_vm(|vm| {
|
||||
let _raised = vm.preserve_raised_exception();
|
||||
let exporter = buffer.obj.clone();
|
||||
let window = PyBufferWindow {
|
||||
source: buffer.detached(),
|
||||
@@ -1655,9 +1656,12 @@ pub(crate) fn release_buffer_call_python(buffer: &PyBuffer) {
|
||||
|
||||
fn call_python_release_buffer(exporter: &PyObject, mv: PyRef<PyMemoryView>) {
|
||||
crate::vm::thread::try_with_current_vm(|vm| {
|
||||
let _raised = vm.preserve_raised_exception();
|
||||
// Drop the captured view before restoring the pending error.
|
||||
let owned_view = mv;
|
||||
let method = vm.get_special_method(exporter, identifier!(vm, __release_buffer__));
|
||||
if let Ok(Some(method)) = method
|
||||
&& let Err(exc) = method.invoke((mv,), vm)
|
||||
&& let Err(exc) = method.invoke((owned_view,), vm)
|
||||
{
|
||||
let msg = format!(
|
||||
"Exception ignored in __release_buffer__ of {}",
|
||||
|
||||
@@ -827,11 +827,12 @@ impl GcState {
|
||||
all_callbacks.extend(callbacks);
|
||||
}
|
||||
for (wr, cb) in all_callbacks {
|
||||
if let Some(Err(e)) = crate::vm::thread::with_vm(&cb, |vm| cb.call((wr.clone(),), vm)) {
|
||||
crate::vm::thread::with_vm(&cb, |vm| {
|
||||
vm.run_unraisable(e.clone(), Some("weakref callback".to_owned()), cb.clone());
|
||||
});
|
||||
}
|
||||
crate::vm::thread::with_vm(&cb, |vm| {
|
||||
let _raised = vm.preserve_raised_exception();
|
||||
if let Err(e) = cb.call((wr.clone(),), vm) {
|
||||
vm.run_unraisable(e, Some("weakref callback".to_owned()), cb.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 6d: Call __del__ on unreachable objects (skip already-finalized).
|
||||
|
||||
@@ -924,6 +924,7 @@ impl WeakRefList {
|
||||
for (wr, callback) in callbacks {
|
||||
if let Some(cb) = callback {
|
||||
crate::vm::thread::with_vm(&cb, |vm| {
|
||||
let _raised = vm.preserve_raised_exception();
|
||||
let _ = cb.call((wr.clone(),), vm);
|
||||
});
|
||||
}
|
||||
@@ -2154,13 +2155,16 @@ impl PyObject {
|
||||
// during __del__, preventing safe_inc from seeing 0.
|
||||
zelf.0.ref_count.inc_by(2);
|
||||
|
||||
let del_method = zelf.get_class_attr(identifier!(vm, __del__)).unwrap();
|
||||
if let Err(e) = slot_del(zelf, vm) {
|
||||
let msg = del_method
|
||||
.repr(vm)
|
||||
.ok()
|
||||
.map(|r| format!("Exception ignored while calling deallocator {r}"));
|
||||
vm.run_unraisable(e, msg, del_method);
|
||||
{
|
||||
let _raised = vm.preserve_raised_exception();
|
||||
let del_method = zelf.get_class_attr(identifier!(vm, __del__)).unwrap();
|
||||
if let Err(e) = slot_del(zelf, vm) {
|
||||
let msg = del_method
|
||||
.repr(vm)
|
||||
.ok()
|
||||
.map(|r| format!("Exception ignored while calling deallocator {r}"));
|
||||
vm.run_unraisable(e, msg, del_method);
|
||||
}
|
||||
}
|
||||
|
||||
// Undo the temporary resurrection. Always remove both
|
||||
@@ -2315,6 +2319,7 @@ impl PyObject {
|
||||
// This ensures drop_slow_inner() won't call __del__ again
|
||||
self.set_gc_finalized();
|
||||
let result = crate::vm::thread::with_vm(self, |vm| {
|
||||
let _raised = vm.preserve_raised_exception();
|
||||
if let Err(e) = slot_del(self, vm)
|
||||
&& let Some(del_method) = self.get_class_attr(identifier!(vm, __del__))
|
||||
{
|
||||
|
||||
@@ -98,7 +98,7 @@ pub(crate) use _ctypes::module_def;
|
||||
#[pymodule]
|
||||
pub(crate) mod _ctypes {
|
||||
use super::{PyCArray, PyCData, PyCPointer, PyCSimple, PyCStructure, PyCUnion};
|
||||
use crate::builtins::{PyType, PyTypeRef};
|
||||
use crate::builtins::{PyStr, PyType, PyTypeRef};
|
||||
use crate::class::StaticType;
|
||||
#[cfg(windows)]
|
||||
use crate::convert::ToPyException;
|
||||
@@ -110,12 +110,14 @@ pub(crate) mod _ctypes {
|
||||
|
||||
// CArgObject - returned by byref() and paramfunc
|
||||
// tagPyCArgObject
|
||||
#[pyclass(name = "CArgObject", module = "_ctypes", no_attr)]
|
||||
#[pyclass(name = "CArgObject", module = "_ctypes", no_attr, traverse)]
|
||||
#[derive(Debug, PyPayload)]
|
||||
pub(crate) struct CArgObject {
|
||||
/// Type tag ('P', 'V', 'i', 'd', etc.)
|
||||
#[pytraverse(skip)]
|
||||
pub tag: u8,
|
||||
/// The actual foreign-call value (mirrors union value)
|
||||
#[pytraverse(skip)]
|
||||
pub value: super::CArgValue,
|
||||
/// Reference to original object (for memory safety)
|
||||
pub obj: PyObjectRef,
|
||||
@@ -124,8 +126,11 @@ pub(crate) mod _ctypes {
|
||||
pub keep: Option<PyObjectRef>,
|
||||
/// Size for struct/union ('V' tag)
|
||||
#[allow(dead_code)]
|
||||
#[pytraverse(skip)]
|
||||
pub size: usize,
|
||||
/// Offset for byref()
|
||||
/// Offset for byref(), read by Windows CopyComPointer.
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
#[pytraverse(skip)]
|
||||
pub offset: isize,
|
||||
}
|
||||
|
||||
@@ -183,14 +188,16 @@ pub(crate) mod _ctypes {
|
||||
FfiValue::F32(v) => v as f64,
|
||||
_ => 0.0,
|
||||
};
|
||||
let v = crate::literal::float::to_string(v);
|
||||
Ok(format!("<cparam '{tag_char}' ({v})>"))
|
||||
}
|
||||
b'd' | b'g' => {
|
||||
b'd' => {
|
||||
let v = match ffi_val {
|
||||
FfiValue::F64(v) => v,
|
||||
FfiValue::F32(v) => v as f64,
|
||||
_ => 0.0,
|
||||
};
|
||||
let v = crate::literal::float::to_string(v);
|
||||
Ok(format!("<cparam '{tag_char}' ({v})>"))
|
||||
}
|
||||
b'c' => {
|
||||
@@ -239,8 +246,22 @@ pub(crate) mod _ctypes {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyattr(name = "__version__")]
|
||||
const __VERSION__: &str = "1.1.0";
|
||||
#[pyfunction]
|
||||
fn __getattr__(name: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
if name.downcast_ref::<PyStr>().and_then(|name| name.to_str()) == Some("__version__") {
|
||||
crate::stdlib::_warnings::warn(
|
||||
vm.ctx.exceptions.deprecation_warning,
|
||||
"'__version__' is deprecated and slated for removal in Python 3.20".to_owned(),
|
||||
1,
|
||||
vm,
|
||||
)?;
|
||||
return Ok(vm.ctx.new_str("1.1.0").into());
|
||||
}
|
||||
Err(vm.new_attribute_error(format!(
|
||||
"module '_ctypes' has no attribute {}",
|
||||
name.repr(vm)?
|
||||
)))
|
||||
}
|
||||
|
||||
// TODO: get properly
|
||||
#[pyattr]
|
||||
@@ -406,6 +427,15 @@ pub(crate) mod _ctypes {
|
||||
Err(vm.new_type_error("this type has no size"))
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[pyfunction]
|
||||
fn dllist(vm: &VirtualMachine) -> Vec<PyObjectRef> {
|
||||
rustpython_host_env::ctypes::dllist()
|
||||
.into_iter()
|
||||
.map(|name| vm.fsdecode(name).into())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[pyfunction(name = "LoadLibrary")]
|
||||
fn load_library_windows(
|
||||
|
||||
@@ -219,7 +219,7 @@ impl StgInfo {
|
||||
size,
|
||||
align,
|
||||
length,
|
||||
proto: None,
|
||||
proto: Some(element_type.clone()),
|
||||
flags,
|
||||
scalar_code: None,
|
||||
element_type: Some(element_type),
|
||||
@@ -2007,14 +2007,17 @@ fn array_paramfunc(obj: &PyObject, vm: &VirtualMachine) -> PyResult<CArgObject>
|
||||
|
||||
/// PyCPointerType_paramfunc
|
||||
fn pointer_paramfunc(obj: &PyObject, vm: &VirtualMachine) -> PyResult<CArgObject> {
|
||||
use super::function::PyCFuncPtr;
|
||||
use super::pointer::PyCPointer;
|
||||
|
||||
let ptr = obj
|
||||
.downcast_ref::<PyCPointer>()
|
||||
.ok_or_else(|| vm.new_type_error("expected pointer"))?;
|
||||
|
||||
// parg->value.p = *(void **)self->b_ptr
|
||||
let ptr_val = ptr.get_ptr_value();
|
||||
let ptr_val = if let Some(ptr) = obj.downcast_ref::<PyCPointer>() {
|
||||
ptr.get_ptr_value()
|
||||
} else if let Some(funcptr) = obj.downcast_ref::<PyCFuncPtr>() {
|
||||
funcptr.get_func_ptr()
|
||||
} else {
|
||||
return Err(vm.new_type_error("expected pointer"));
|
||||
};
|
||||
|
||||
Ok(CArgObject {
|
||||
tag: b'P',
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
vm::thread::with_current_vm,
|
||||
};
|
||||
use alloc::borrow::Cow;
|
||||
use core::ffi::c_void;
|
||||
use core::ffi::{c_int, c_long, c_ulong, c_void};
|
||||
use core::fmt::Debug;
|
||||
use core::ptr::NonNull;
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
@@ -41,42 +41,36 @@ pub(super) const INTERNAL_MEMORYVIEW_AT_ADDR: usize = 4;
|
||||
|
||||
/// Convert any object to a pointer value for c_void_p arguments
|
||||
/// Follows ConvParam logic for pointer types
|
||||
fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult<CArgValue> {
|
||||
fn convert_to_pointer(
|
||||
value: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<(CArgValue, Option<PyObjectRef>)> {
|
||||
let pointer = |address| Ok((CArgValue::pointer(address), Some(value.to_owned())));
|
||||
// 0. CArgObject (from byref()) -> buffer address + offset
|
||||
if let Some(carg) = value.downcast_ref::<CArgObject>() {
|
||||
// Get buffer address from the underlying object
|
||||
let base_addr = if let Some(cdata) = carg.obj.downcast_ref::<PyCData>() {
|
||||
cdata.buffer.read().as_ptr() as usize
|
||||
} else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"byref() argument must be a ctypes instance, not '{}'",
|
||||
carg.obj.class().name()
|
||||
)));
|
||||
};
|
||||
let addr = (base_addr as isize + carg.offset) as usize;
|
||||
return Ok(CArgValue::pointer(addr));
|
||||
return Ok((carg.value.clone(), Some(value.to_owned())));
|
||||
}
|
||||
|
||||
// 1. None -> NULL
|
||||
if value.is(&vm.ctx.none) {
|
||||
return Ok(CArgValue::pointer(0));
|
||||
return Ok((CArgValue::pointer(0), None));
|
||||
}
|
||||
|
||||
// 2. PyCArray -> buffer address (PyCArrayType_paramfunc)
|
||||
if let Some(array) = value.downcast_ref::<PyCArray>() {
|
||||
let addr = array.0.buffer.read().as_ptr() as usize;
|
||||
return Ok(CArgValue::pointer(addr));
|
||||
return pointer(addr);
|
||||
}
|
||||
|
||||
// 3. PyCPointer -> stored pointer value
|
||||
if let Some(ptr) = value.downcast_ref::<PyCPointer>() {
|
||||
return Ok(CArgValue::pointer(ptr.get_ptr_value()));
|
||||
return pointer(ptr.get_ptr_value());
|
||||
}
|
||||
|
||||
// 4. PyCStructure -> buffer address
|
||||
if let Some(struct_obj) = value.downcast_ref::<PyCStructure>() {
|
||||
let addr = struct_obj.0.buffer.read().as_ptr() as usize;
|
||||
return Ok(CArgValue::pointer(addr));
|
||||
return pointer(addr);
|
||||
}
|
||||
|
||||
// 5. PyCSimple (c_void_p, c_char_p, etc.) -> value from buffer
|
||||
@@ -84,14 +78,14 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult<CArgVal
|
||||
let buffer = simple.0.buffer.read();
|
||||
if has_pointer_width(&buffer) {
|
||||
let addr = rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer);
|
||||
return Ok(CArgValue::pointer(addr));
|
||||
return pointer(addr);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. bytes -> buffer address (PyBytes_AsString)
|
||||
// 6. bytes -> owned, null-terminated buffer
|
||||
if let Some(bytes) = value.downcast_ref::<crate::builtins::PyBytes>() {
|
||||
let addr = bytes.as_bytes().as_ptr() as usize;
|
||||
return Ok(CArgValue::pointer(addr));
|
||||
let (owner, address) = super::base::ensure_z_null_terminated(bytes, vm);
|
||||
return Ok((CArgValue::pointer(address), Some(owner)));
|
||||
}
|
||||
|
||||
// 7. Integer -> direct value (PyLong_AsVoidPtr behavior)
|
||||
@@ -100,10 +94,10 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult<CArgVal
|
||||
// Negative values: use signed conversion (allows -1 as 0xFFFF...)
|
||||
if bigint.is_negative() {
|
||||
if let Some(signed_val) = bigint.to_isize() {
|
||||
return Ok(CArgValue::pointer(signed_val as usize));
|
||||
return pointer(signed_val as usize);
|
||||
}
|
||||
} else if let Some(unsigned_val) = bigint.to_usize() {
|
||||
return Ok(CArgValue::pointer(unsigned_val));
|
||||
return pointer(unsigned_val);
|
||||
}
|
||||
// Value out of range - raise OverflowError
|
||||
return Err(vm.new_overflow_error("int too large to convert to pointer"));
|
||||
@@ -126,7 +120,9 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<Argument> {
|
||||
// 1. CArgObject (from byref() or paramfunc) -> use stored value
|
||||
if let Some(carg) = value.downcast_ref::<CArgObject>() {
|
||||
return Ok(Argument {
|
||||
keep: carg.keep.clone(),
|
||||
// Keep the wrapper, which owns both the source and any auxiliary
|
||||
// pointer storage. `_as_parameter_` may have created it on demand.
|
||||
keep: Some(value.to_owned()),
|
||||
value: carg.value.clone(),
|
||||
});
|
||||
}
|
||||
@@ -141,9 +137,10 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<Argument> {
|
||||
|
||||
// 3. ctypes objects -> use paramfunc
|
||||
if let Ok(carg) = super::base::call_paramfunc(value, vm) {
|
||||
let value = carg.value.clone();
|
||||
return Ok(Argument {
|
||||
keep: carg.keep,
|
||||
value: carg.value,
|
||||
keep: Some(carg.into_ref(&vm.ctx).into()),
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,7 +172,26 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<Argument> {
|
||||
// `__int__` would accept a float and pass its truncated value where the
|
||||
// callee expects a pointer.
|
||||
if let Some(int_val) = value.downcast_ref::<PyInt>() {
|
||||
let val = int_val.as_bigint().to_i32().unwrap_or(0);
|
||||
// CPython first accepts the platform's unsigned long / signed long
|
||||
// range, then stores the low C-int bits in the argument carrier.
|
||||
let bigint = int_val.as_bigint();
|
||||
#[allow(
|
||||
clippy::useless_conversion,
|
||||
clippy::unnecessary_cast,
|
||||
reason = "C long is 32 bits on Windows and 64 bits on LP64 targets"
|
||||
)]
|
||||
let val = if bigint.is_negative() {
|
||||
bigint
|
||||
.to_i64()
|
||||
.and_then(|value| c_long::try_from(value).ok())
|
||||
.map(|value| value as c_int)
|
||||
} else {
|
||||
bigint
|
||||
.to_u64()
|
||||
.and_then(|value| c_ulong::try_from(value).ok())
|
||||
.map(|value| value as c_int)
|
||||
}
|
||||
.ok_or_else(|| vm.new_overflow_error("int too long to convert"))?;
|
||||
return Ok(Argument {
|
||||
keep: None,
|
||||
value: CArgValue::Int(val),
|
||||
@@ -193,6 +209,29 @@ fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult<Argument> {
|
||||
)))
|
||||
}
|
||||
|
||||
fn conv_param_for_call(value: &PyObject, index: usize, vm: &VirtualMachine) -> PyResult<Argument> {
|
||||
match conv_param(value, vm) {
|
||||
Ok(argument) => Ok(argument),
|
||||
Err(error) => {
|
||||
let error_type = vm
|
||||
.import("_ctypes", 0)?
|
||||
.get_attr("ArgumentError", vm)?
|
||||
.downcast::<PyType>()
|
||||
.map_err(|_| vm.new_type_error("ArgumentError is not a type"))?;
|
||||
Err(vm.new_exception_msg(
|
||||
error_type,
|
||||
format!(
|
||||
"argument {}: {}: {}",
|
||||
index + 1,
|
||||
error.class().name(),
|
||||
error.as_object().str(vm)?,
|
||||
)
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ArgumentType {
|
||||
/// Convert an argument for this type into a foreign-call value plus an
|
||||
/// optional owner keeping any referenced memory alive.
|
||||
@@ -214,6 +253,7 @@ impl ArgumentType for PyTypeRef {
|
||||
// type must carry a known _type_ code; anything else is unsupported.
|
||||
let type_code = if self.fast_issubclass(CArgObject::static_type())
|
||||
|| self.fast_issubclass(PyCPointer::static_type())
|
||||
|| self.fast_issubclass(PyCFuncPtr::static_type())
|
||||
|| self.fast_issubclass(PyCStructure::static_type())
|
||||
|| self.fast_issubclass(PyCUnion::static_type())
|
||||
{
|
||||
@@ -246,7 +286,7 @@ impl ArgumentType for PyTypeRef {
|
||||
// Then pass the converted value to ConvParam logic
|
||||
// CArgObject (from from_param) -> use stored value and keepalive directly
|
||||
if let Some(carg) = converted.downcast_ref::<CArgObject>() {
|
||||
return Ok((carg.value.clone(), carg.keep.clone()));
|
||||
return Ok((carg.value.clone(), Some(converted.clone())));
|
||||
}
|
||||
|
||||
// None -> NULL pointer
|
||||
@@ -254,12 +294,20 @@ impl ArgumentType for PyTypeRef {
|
||||
return Ok((CArgValue::pointer(0), None));
|
||||
}
|
||||
|
||||
if self.fast_issubclass(PyCFuncPtr::static_type()) {
|
||||
let argument = conv_param(&converted, vm)?;
|
||||
return Ok((argument.value, argument.keep));
|
||||
}
|
||||
|
||||
// For pointer types (POINTER(T)), we need to pass the pointer VALUE stored in buffer
|
||||
if self.fast_issubclass(PyCPointer::static_type()) {
|
||||
if let Some(pointer) = converted.downcast_ref::<PyCPointer>() {
|
||||
return Ok((CArgValue::pointer(pointer.get_ptr_value()), None));
|
||||
return Ok((
|
||||
CArgValue::pointer(pointer.get_ptr_value()),
|
||||
Some(converted.clone()),
|
||||
));
|
||||
}
|
||||
return Ok((convert_to_pointer(&converted, vm)?, None));
|
||||
return convert_to_pointer(&converted, vm);
|
||||
}
|
||||
|
||||
// For structure/union types, pass the aggregate by value: snapshot the
|
||||
@@ -279,12 +327,12 @@ impl ArgumentType for PyTypeRef {
|
||||
// owns, which must outlive the foreign call.
|
||||
return Ok((CArgValue::aggregate(layout, bytes), Some(converted.clone())));
|
||||
}
|
||||
return Ok((convert_to_pointer(&converted, vm)?, None));
|
||||
return convert_to_pointer(&converted, vm);
|
||||
}
|
||||
|
||||
// For pointer types (c_void_p, c_char_p, c_wchar_p), handle as pointer
|
||||
if matches!(type_code.as_deref(), Some("P" | "z" | "Z")) {
|
||||
return Ok((convert_to_pointer(&converted, vm)?, None));
|
||||
return convert_to_pointer(&converted, vm);
|
||||
}
|
||||
|
||||
// PyCSimple (already a ctypes instance from from_param)
|
||||
@@ -293,7 +341,7 @@ impl ArgumentType for PyTypeRef {
|
||||
.as_deref()
|
||||
.and_then(|s| s.chars().next())
|
||||
.ok_or_else(|| vm.new_type_error("Unsupported argument type"))?;
|
||||
return Ok((simple.to_carg_value(code), None));
|
||||
return Ok((simple.to_carg_value(code), Some(simple.into())));
|
||||
}
|
||||
|
||||
Err(vm.new_type_error("Unsupported argument type"))
|
||||
@@ -333,6 +381,25 @@ impl Initializer for PyCFuncPtrType {
|
||||
|
||||
#[pyclass(flags(IMMUTABLETYPE), with(AsSequence, Initializer))]
|
||||
impl PyCFuncPtrType {
|
||||
#[pymethod]
|
||||
fn from_param(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
if value.is_instance(zelf.as_object(), vm)? {
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
if let Some(as_parameter) = vm.get_attribute_opt(&value, "_as_parameter_")? {
|
||||
return vm.with_recursion(" while processing _as_parameter_", || {
|
||||
Self::from_param(zelf, as_parameter, vm)
|
||||
});
|
||||
}
|
||||
|
||||
Err(vm.new_type_error(format!(
|
||||
"expected {} instance instead of {}",
|
||||
zelf.name(),
|
||||
value.class().name()
|
||||
)))
|
||||
}
|
||||
|
||||
#[pygetset(name = "__pointer_type__")]
|
||||
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
|
||||
super::base::pointer_type_get(&zelf, vm)
|
||||
@@ -664,7 +731,7 @@ impl PyCFuncPtr {
|
||||
}
|
||||
|
||||
/// Get function pointer address from buffer
|
||||
fn get_func_ptr(&self) -> usize {
|
||||
pub(super) fn get_func_ptr(&self) -> usize {
|
||||
let buffer = self._base.buffer.read();
|
||||
rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer)
|
||||
}
|
||||
@@ -1216,7 +1283,8 @@ fn build_callargs_no_argtypes(
|
||||
let arguments: Vec<Argument> = args
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| conv_param(arg, vm))
|
||||
.enumerate()
|
||||
.map(|(index, arg)| conv_param_for_call(arg, index, vm))
|
||||
.collect::<PyResult<Vec<_>>>()?;
|
||||
Ok((arguments, Vec::new()))
|
||||
}
|
||||
@@ -1225,18 +1293,30 @@ fn build_callargs_no_argtypes(
|
||||
fn build_callargs_simple(
|
||||
args: &FuncArgs,
|
||||
arg_types: &[PyTypeRef],
|
||||
allow_variadic: bool,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<(Vec<Argument>, OutBuffers)> {
|
||||
if args.args.len() < arg_types.len() || (!allow_variadic && args.args.len() != arg_types.len())
|
||||
{
|
||||
return Err(vm.new_type_error(format!(
|
||||
"this function takes {}{} argument{} ({} given)",
|
||||
if allow_variadic { "at least " } else { "" },
|
||||
arg_types.len(),
|
||||
if arg_types.len() == 1 { "" } else { "s" },
|
||||
args.args.len(),
|
||||
)));
|
||||
}
|
||||
let arguments: Vec<Argument> = args
|
||||
.args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(n, arg)| {
|
||||
let arg_type = arg_types
|
||||
.get(n)
|
||||
.ok_or_else(|| vm.new_type_error("argument amount mismatch"))?;
|
||||
let (value, keep) = arg_type.convert_object(arg.clone(), vm)?;
|
||||
Ok(Argument { value, keep })
|
||||
if let Some(arg_type) = arg_types.get(n) {
|
||||
let (value, keep) = arg_type.convert_object(arg.clone(), vm)?;
|
||||
Ok(Argument { value, keep })
|
||||
} else {
|
||||
conv_param_for_call(arg, n, vm)
|
||||
}
|
||||
})
|
||||
.collect::<PyResult<Vec<_>>>()?;
|
||||
Ok((arguments, Vec::new()))
|
||||
@@ -1312,6 +1392,7 @@ fn build_callargs(
|
||||
call_info: &CallInfo,
|
||||
paramflags: Option<&ParsedParamFlags>,
|
||||
is_com_method: bool,
|
||||
allow_variadic: bool,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<(Vec<Argument>, OutBuffers)> {
|
||||
let Some(ref arg_types) = call_info.explicit_arg_types else {
|
||||
@@ -1338,7 +1419,7 @@ fn build_callargs(
|
||||
Ok((arguments, Vec::new()))
|
||||
} else {
|
||||
// Regular function
|
||||
build_callargs_simple(args, arg_types, vm)
|
||||
build_callargs_simple(args, arg_types, allow_variadic, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1410,11 +1491,11 @@ fn convert_raw_result(
|
||||
result: &CallValue,
|
||||
call_info: &CallInfo,
|
||||
vm: &VirtualMachine,
|
||||
) -> Option<PyObjectRef> {
|
||||
) -> PyResult<Option<PyObjectRef>> {
|
||||
// Result register image as bytes + size (None for void): pointer/scalar
|
||||
// returns are pointer/register sized.
|
||||
let (result_bytes, result_size) = match result {
|
||||
CallValue::Void => return None,
|
||||
CallValue::Void => return Ok(None),
|
||||
CallValue::Pointer(ptr) => (ptr.to_ne_bytes().to_vec(), size_of::<usize>()),
|
||||
CallValue::Scalar(bytes) | CallValue::Aggregate(bytes) => (bytes.clone(), bytes.len()),
|
||||
};
|
||||
@@ -1435,14 +1516,14 @@ fn convert_raw_result(
|
||||
let restype = match &call_info.restype_obj {
|
||||
None => {
|
||||
// Default: return as int
|
||||
return Some(vm.ctx.new_int(result_word as isize).into());
|
||||
return Ok(Some(vm.ctx.new_int(result_word as isize).into()));
|
||||
}
|
||||
Some(r) => r,
|
||||
};
|
||||
|
||||
// 2. restype is None → return None
|
||||
if restype.is(&vm.ctx.none()) {
|
||||
return None;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 3. Get restype as PyType
|
||||
@@ -1450,7 +1531,7 @@ fn convert_raw_result(
|
||||
Ok(t) => t,
|
||||
Err(_) => {
|
||||
// Not a type, call it with int result
|
||||
return restype.call((result_word as isize,), vm).ok();
|
||||
return restype.call((result_word as isize,), vm).map(Some);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1462,7 +1543,7 @@ fn convert_raw_result(
|
||||
return restype_type
|
||||
.as_object()
|
||||
.call((result_word as isize,), vm)
|
||||
.ok();
|
||||
.map(Some);
|
||||
}
|
||||
|
||||
let info = stg_info.unwrap();
|
||||
@@ -1480,24 +1561,22 @@ fn convert_raw_result(
|
||||
&& let Some(type_str) = type_attr.downcast_ref::<PyStr>()
|
||||
&& type_str.to_str() == Some("O")
|
||||
{
|
||||
let ptr = NonNull::new(result_word as *mut PyObject).or_else(|| {
|
||||
vm.set_exception(Some(vm.new_value_error("PyObject is NULL")));
|
||||
None
|
||||
})?;
|
||||
let ptr = NonNull::new(result_word as *mut PyObject)
|
||||
.ok_or_else(|| vm.new_value_error("PyObject is NULL"))?;
|
||||
unsafe {
|
||||
let obj = PyObjectRef::from_raw(ptr);
|
||||
return Some(obj);
|
||||
return Ok(Some(obj));
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Simple type with getfunc → use bytes_to_pyobject (info->getfunc)
|
||||
// is_simple_instance returns TRUE for c_int, c_void_p, etc.
|
||||
if super::base::is_simple_instance(&restype_type) {
|
||||
return Some(super::base::bytes_to_pyobject(
|
||||
return Ok(Some(super::base::bytes_to_pyobject(
|
||||
&restype_type,
|
||||
&result_bytes,
|
||||
vm,
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
// 6. Complex type → create ctypes instance (PyCData_FromBaseObj)
|
||||
@@ -1507,16 +1586,16 @@ fn convert_raw_result(
|
||||
if is_pointer_type
|
||||
&& has_proto
|
||||
&& let CallValue::Pointer(ptr) = result
|
||||
&& let Ok(instance) = restype_type.as_object().call((), vm)
|
||||
{
|
||||
let instance = restype_type.as_object().call((), vm)?;
|
||||
if let Some(pointer) = instance.downcast_ref::<PyCPointer>() {
|
||||
pointer.set_ptr_value(*ptr);
|
||||
}
|
||||
return Some(instance);
|
||||
return Ok(Some(instance));
|
||||
}
|
||||
|
||||
// Create instance and copy result data
|
||||
pycdata_from_ffi_result(&restype_type, &result_bytes, result_size, vm).ok()
|
||||
pycdata_from_ffi_result(&restype_type, &result_bytes, result_size, vm).map(Some)
|
||||
}
|
||||
|
||||
/// Create a ctypes instance from FFI result (PyCData_FromBaseObj equivalent)
|
||||
@@ -1575,7 +1654,7 @@ fn build_result(
|
||||
}
|
||||
|
||||
// Convert the foreign-call result to a Python object
|
||||
let mut result = convert_raw_result(&call_result, call_info, vm);
|
||||
let mut result = convert_raw_result(&call_result, call_info, vm)?;
|
||||
|
||||
// Apply errcheck if set
|
||||
if let Some(errcheck) = zelf.errcheck.read().as_ref() {
|
||||
@@ -1622,8 +1701,16 @@ impl Callable for PyCFuncPtr {
|
||||
let paramflags = parse_paramflags(zelf, vm)?;
|
||||
|
||||
// 5. Build call arguments
|
||||
let (arguments, out_buffers) =
|
||||
build_callargs(&args, &call_info, paramflags.as_ref(), is_com_method, vm)?;
|
||||
let flags = Py::<Self>::_flags_(zelf, vm);
|
||||
let allow_variadic = flags & StgInfoFlags::FUNCFLAG_CDECL.bits() != 0;
|
||||
let (arguments, out_buffers) = build_callargs(
|
||||
&args,
|
||||
&call_info,
|
||||
paramflags.as_ref(),
|
||||
is_com_method,
|
||||
allow_variadic,
|
||||
vm,
|
||||
)?;
|
||||
|
||||
// 6. Function address (usize); the unified `call` rejects a NULL address.
|
||||
let addr = match func_ptr {
|
||||
@@ -1632,24 +1719,47 @@ impl Callable for PyCFuncPtr {
|
||||
};
|
||||
|
||||
// 7. Errno / last-error swap options from flags
|
||||
let flags = Py::<Self>::_flags_(zelf, vm);
|
||||
let options = CallOptions {
|
||||
fixed_arg_count: call_info
|
||||
.explicit_arg_types
|
||||
.as_ref()
|
||||
.map(Vec::len)
|
||||
.filter(|&fixed| allow_variadic && fixed != 0 && arguments.len() > fixed),
|
||||
use_errno: flags & super::base::StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0,
|
||||
use_last_error: flags & super::base::StgInfoFlags::FUNCFLAG_USE_LASTERROR.bits() != 0,
|
||||
};
|
||||
|
||||
// 8. Call the function through the unified entry point.
|
||||
let call_result = ctypes_callproc(addr, &arguments, &call_info.ret, options).map_err(
|
||||
|err| match err {
|
||||
CallError::NullFunctionPointer => vm.new_value_error("NULL function pointer"),
|
||||
CallError::UnknownTypeCode(code) => {
|
||||
vm.new_type_error(format!("Unsupported argument type: {code}"))
|
||||
}
|
||||
CallError::BufferTooSmall { expected, got } => vm.new_value_error(format!(
|
||||
"argument buffer too small: expected {expected}, got {got}"
|
||||
)),
|
||||
},
|
||||
)?;
|
||||
// Native calls may block while another thread runs Python. Callbacks
|
||||
// reattach before executing Python; PythonAPI calls stay attached.
|
||||
let native_call = || ctypes_callproc(addr, &arguments, &call_info.ret, options);
|
||||
let call_result = if flags & StgInfoFlags::FUNCFLAG_PYTHONAPI.bits() != 0 {
|
||||
native_call()
|
||||
} else {
|
||||
vm.allow_threads(native_call)
|
||||
}
|
||||
.map_err(|err| match err {
|
||||
CallError::NullFunctionPointer => vm.new_value_error("NULL function pointer"),
|
||||
CallError::InvalidCallInterface { variadic } => vm.new_runtime_error(if variadic {
|
||||
"ffi_prep_cif_var failed"
|
||||
} else {
|
||||
"ffi_prep_cif failed"
|
||||
}),
|
||||
CallError::UnknownTypeCode(code) => {
|
||||
vm.new_type_error(format!("Unsupported argument type: {code}"))
|
||||
}
|
||||
CallError::BufferTooSmall { expected, got } => vm.new_value_error(format!(
|
||||
"argument buffer too small: expected {expected}, got {got}"
|
||||
)),
|
||||
})?;
|
||||
|
||||
// PythonAPI functions report errors through the native raised-error
|
||||
// slot. Check it before a NULL PyObject* can be decoded, and consume
|
||||
// the exception exactly once without touching handled exceptions.
|
||||
if flags & StgInfoFlags::FUNCFLAG_PYTHONAPI.bits() != 0
|
||||
&& let Some(exception) = vm.take_raised_exception()
|
||||
{
|
||||
return Err(exception);
|
||||
}
|
||||
|
||||
// 9. Build result
|
||||
build_result(call_result, &call_info, out_buffers, zelf, &args, vm)
|
||||
@@ -1808,7 +1918,23 @@ fn ffi_to_python(
|
||||
args: *const *const c_void,
|
||||
index: usize,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyObjectRef {
|
||||
) -> PyResult {
|
||||
if ty.fast_issubclass(PyCPointer::static_type()) {
|
||||
// POINTER(T) uses a pointer argument in the CIF, even though its
|
||||
// `_type_` is a type object rather than a simple type code.
|
||||
let rustpython_host_env::ctypes::DecodedValue::Pointer(address) =
|
||||
(unsafe { rustpython_host_env::ctypes::callback_arg_value_at(Some("P"), args, index) })
|
||||
else {
|
||||
return Err(vm.new_type_error("invalid pointer callback argument"));
|
||||
};
|
||||
let instance = ty.as_object().call((), vm)?;
|
||||
let pointer = instance
|
||||
.downcast_ref::<PyCPointer>()
|
||||
.ok_or_else(|| vm.new_type_error("pointer constructor returned a non-pointer"))?;
|
||||
pointer.set_ptr_value(address);
|
||||
return Ok(instance);
|
||||
}
|
||||
|
||||
let type_code = ty.type_code(vm);
|
||||
let raw_value: PyObjectRef = match unsafe {
|
||||
rustpython_host_env::ctypes::callback_arg_value_at(type_code.as_deref(), args, index)
|
||||
@@ -1824,11 +1950,12 @@ fn ffi_to_python(
|
||||
};
|
||||
|
||||
if !is_simple_subclass(ty, vm) {
|
||||
return raw_value;
|
||||
return Ok(raw_value);
|
||||
}
|
||||
ty.as_object()
|
||||
Ok(ty
|
||||
.as_object()
|
||||
.call((raw_value.clone(),), vm)
|
||||
.unwrap_or(raw_value)
|
||||
.unwrap_or(raw_value))
|
||||
}
|
||||
|
||||
/// Convert a Python object to a C value and store it at the result pointer
|
||||
@@ -1910,32 +2037,35 @@ unsafe extern "C" fn thunk_callback(
|
||||
userdata: &ThunkUserData,
|
||||
) {
|
||||
with_current_vm(|vm| {
|
||||
let use_errno = userdata.flags & StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0;
|
||||
let py_result =
|
||||
rustpython_host_env::ctypes::with_callback_errno_preserved(use_errno, || {
|
||||
let py_args: Vec<PyObjectRef> = userdata
|
||||
.arg_types
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ty)| ffi_to_python(ty, args, i, vm))
|
||||
.collect();
|
||||
vm.attach_for_callback(|| {
|
||||
let use_errno = userdata.flags & StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0;
|
||||
let py_result =
|
||||
rustpython_host_env::ctypes::with_callback_errno_preserved(use_errno, || {
|
||||
let py_args: Vec<PyObjectRef> = userdata
|
||||
.arg_types
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ty)| ffi_to_python(ty, args, i, vm))
|
||||
.collect::<PyResult<Vec<_>>>()?;
|
||||
|
||||
userdata.callable.call(py_args, vm)
|
||||
});
|
||||
userdata.callable.call(py_args, vm)
|
||||
});
|
||||
|
||||
// Call unraisable hook if exception occurred
|
||||
if let Err(exc) = &py_result {
|
||||
let repr = userdata
|
||||
.callable
|
||||
.repr(vm)
|
||||
.map_or_else(|_| "<unknown>".to_string(), |s| s.to_string());
|
||||
let msg = format!("Exception ignored while calling ctypes callback function {repr}");
|
||||
vm.run_unraisable(exc.clone(), Some(msg), vm.ctx.none());
|
||||
}
|
||||
// Call unraisable hook if exception occurred
|
||||
if let Err(exc) = &py_result {
|
||||
let repr = userdata
|
||||
.callable
|
||||
.repr(vm)
|
||||
.map_or_else(|_| "<unknown>".to_string(), |s| s.to_string());
|
||||
let msg =
|
||||
format!("Exception ignored while calling ctypes callback function {repr}");
|
||||
vm.run_unraisable(exc.clone(), Some(msg), vm.ctx.none());
|
||||
}
|
||||
|
||||
if let Some(ref res_type) = userdata.res_type {
|
||||
python_to_ffi(py_result, res_type, result as *mut c_void, vm);
|
||||
}
|
||||
if let Some(ref res_type) = userdata.res_type {
|
||||
python_to_ffi(py_result, res_type, result as *mut c_void, vm);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1982,9 +2112,13 @@ impl PyCThunk {
|
||||
let ffi_arg_types: Vec<FfiType> = arg_type_vec
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
ty.type_code(vm)
|
||||
.and_then(|code| ffi_type_from_code(&code))
|
||||
.unwrap_or_else(ffi_pointer_type)
|
||||
if ty.fast_issubclass(PyCPointer::static_type()) {
|
||||
ffi_pointer_type()
|
||||
} else {
|
||||
ty.type_code(vm)
|
||||
.and_then(|code| ffi_type_from_code(&code))
|
||||
.unwrap_or_else(ffi_pointer_type)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -59,6 +59,12 @@ fn new_simple_type(
|
||||
|
||||
let tp_str = _type_.str(vm)?.to_string();
|
||||
|
||||
// Complex ctypes storage and FFI are not implemented. Preserve the
|
||||
// optional-type AttributeError contract for the PEP 3118 codes.
|
||||
if matches!(tp_str.as_str(), "Zd" | "Zf" | "Zg") {
|
||||
return Err(vm.new_attribute_error("complex ctypes types are not supported"));
|
||||
}
|
||||
|
||||
if tp_str.len() != 1 {
|
||||
return Err(vm.new_value_error(format!(
|
||||
"class must define a '_type_' attribute which must be a string of length 1, str: {tp_str}"
|
||||
@@ -540,6 +546,12 @@ impl Initializer for PyCSimpleType {
|
||||
let type_str = type_attr.str(vm)?.to_string();
|
||||
|
||||
// Validate _type_ is a single character
|
||||
// Complex ctypes storage and FFI are not implemented. Preserve the
|
||||
// optional-type AttributeError contract for the PEP 3118 codes.
|
||||
if matches!(type_str.as_str(), "Zd" | "Zf" | "Zg") {
|
||||
return Err(vm.new_attribute_error("complex ctypes types are not supported"));
|
||||
}
|
||||
|
||||
if type_str.len() != 1 {
|
||||
return Err(vm.new_value_error(
|
||||
"class must define a '_type_' attribute which must be a string of length 1",
|
||||
|
||||
+56
-11
@@ -186,6 +186,8 @@ unsafe impl Send for FramePtr {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ExceptionStack {
|
||||
/// Pending native C API error, independent of handled `sys.exception()`.
|
||||
raised: Option<PyBaseExceptionRef>,
|
||||
/// Linked list of handled-exception slots (`_PyErr_StackItem` chain).
|
||||
/// Bottom element is the thread's base slot; generator/coroutine resume
|
||||
/// pushes an additional slot. Normal frame calls do **not** push/pop.
|
||||
@@ -195,7 +197,10 @@ struct ExceptionStack {
|
||||
impl Default for ExceptionStack {
|
||||
fn default() -> Self {
|
||||
// Thread's base `_PyErr_StackItem` – always present.
|
||||
Self { stack: vec![None] }
|
||||
Self {
|
||||
raised: None,
|
||||
stack: vec![None],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3957,17 +3962,27 @@ impl VirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep teardown callbacks from observing or replacing a pending C error.
|
||||
/// Create this guard before callback-owned locals so they drop before restoration.
|
||||
#[must_use]
|
||||
pub(crate) fn preserve_raised_exception(&self) -> impl Drop + '_ {
|
||||
scopeguard::guard(self.take_raised_exception(), |saved| {
|
||||
self.set_raised_exception(saved);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn raised_exception(&self) -> Option<PyBaseExceptionRef> {
|
||||
self.exceptions.borrow().raised.clone()
|
||||
}
|
||||
|
||||
pub fn set_raised_exception(&self, exc: Option<PyBaseExceptionRef>) {
|
||||
let previous = core::mem::replace(&mut self.exceptions.borrow_mut().raised, exc);
|
||||
// Release the RefCell borrow before dropping a potentially final reference.
|
||||
drop(previous);
|
||||
}
|
||||
|
||||
pub fn take_raised_exception(&self) -> Option<PyBaseExceptionRef> {
|
||||
let mut excs = self.exceptions.borrow_mut();
|
||||
if let Some(top) = excs.stack.last_mut() {
|
||||
let exc = top.take();
|
||||
drop(excs);
|
||||
#[cfg(feature = "threading")]
|
||||
thread::update_thread_exception(self.topmost_exception());
|
||||
exc
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.exceptions.borrow_mut().raised.take()
|
||||
}
|
||||
|
||||
/// `_PyErr_ChainStackItem`: if the current `exc_info` slot is occupied,
|
||||
@@ -4189,6 +4204,36 @@ pub fn resolve_frozen_alias(name: &str) -> &str {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn raised_error_guards_preserve_storage_on_unwind() {
|
||||
Interpreter::builder(Default::default())
|
||||
.build()
|
||||
.enter(|vm| {
|
||||
let handled = vm.new_type_error("handled");
|
||||
let outer = vm.new_type_error("outer");
|
||||
let inner = vm.new_type_error("inner");
|
||||
vm.set_exception(Some(handled.clone()));
|
||||
vm.set_raised_exception(Some(outer.clone()));
|
||||
let result = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
|
||||
let _outer_guard = vm.preserve_raised_exception();
|
||||
assert!(vm.raised_exception().is_none());
|
||||
vm.set_raised_exception(Some(inner.clone()));
|
||||
{
|
||||
let _inner_guard = vm.preserve_raised_exception();
|
||||
assert!(vm.raised_exception().is_none());
|
||||
assert!(vm.current_exception().unwrap().is(&handled));
|
||||
}
|
||||
assert!(vm.raised_exception().unwrap().is(&inner));
|
||||
panic!("exercise raised-error guard unwinding");
|
||||
}));
|
||||
assert!(result.is_err());
|
||||
assert!(vm.take_raised_exception().unwrap().is(&outer));
|
||||
assert!(vm.raised_exception().is_none());
|
||||
assert!(vm.current_exception().unwrap().is(&handled));
|
||||
vm.set_exception(None);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stdio"))]
|
||||
#[test]
|
||||
fn no_stdio_streams_are_none_singleton() {
|
||||
|
||||
Reference in new issue
Block a user