Skip to content
Merged
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
3 changes: 2 additions & 1 deletion Include/internal/pycore_modsupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ extern "C" {
#endif


extern int _PyArg_NoKwnames(const char *funcname, PyObject *kwnames);
// Export for 'array' shared extension
PyAPI_FUNC(int) _PyArg_NoKwnames(const char *funcname, PyObject *kwnames);
#define _PyArg_NoKwnames(funcname, kwnames) \
((kwnames) == NULL || _PyArg_NoKwnames((funcname), (kwnames)))

Expand Down
20 changes: 5 additions & 15 deletions Lib/test/clinic.test.c
Original file line number Diff line number Diff line change
Expand Up @@ -6032,23 +6032,13 @@ static PyObject *
Test__pyarg_parsestackandkeywords(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
# define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
#else
# define KWTUPLE NULL
#endif

static const char * const _keywords[] = {"", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.format = "s#:_pyarg_parsestackandkeywords",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
const char *key;
Py_ssize_t key_length;

if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
if (!_PyArg_NoKwnames("_pyarg_parsestackandkeywords", kwnames)) {
goto exit;
}
if (!_PyArg_ParseStack(args, nargs, "s#:_pyarg_parsestackandkeywords",
&key, &key_length)) {
goto exit;
}
Expand All @@ -6062,7 +6052,7 @@ static PyObject *
Test__pyarg_parsestackandkeywords_impl(TestObj *self, PyTypeObject *cls,
const char *key,
Py_ssize_t key_length)
/*[clinic end generated code: output=7060c213d7b8200e input=fc72ef4b4cfafabc]*/
/*[clinic end generated code: output=21bbb9a8d0100d72 input=fc72ef4b4cfafabc]*/


/*[clinic input]
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5114,7 +5114,7 @@ def test_defclass_posonly_varpos(self):
cls = ac_tester.TestClass
obj = cls()
fn = obj.defclass_posonly_varpos
errmsg = 'takes at least 2 positional arguments'
errmsg = 'expected at least 2 arguments'
self.assertRaisesRegex(TypeError, errmsg, fn)
self.assertRaisesRegex(TypeError, errmsg, fn, 1)
self.assertEqual(fn(1, 2), (cls, 1, 2, ()))
Expand Down
16 changes: 15 additions & 1 deletion Lib/test/test_ctypes/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
c_short, c_ushort, c_int, c_uint,
c_long, c_longlong, c_ulonglong, c_ulong,
c_float, c_double, c_longdouble, py_object)
from ctypes.util import find_library
from ctypes.util import find_library, wrap_dll_function
from test import support
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
Expand Down Expand Up @@ -328,6 +328,20 @@ def func():
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)

@wrap_dll_function(CDLL(_ctypes_test.__file__))
def _testfunc_callback_int_to_longlong(func: CALLBACK) -> c_longlong:
pass

@CALLBACK
def cb():
return -1

self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)


if __name__ == '__main__':
unittest.main()
121 changes: 121 additions & 0 deletions Lib/test/test_external_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2327,6 +2327,127 @@ def get_trace_with_opcodes(pid):
location.col_offset, location.end_col_offset)
self.assertIn(actual, valid_locations)

@skip_if_not_supported
@unittest.skipIf(sys._is_gil_enabled(), "Requires free-threading")
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
"Requires process_vm_readv",
)
def test_tlbc_cache_refresh_after_growth(self):
# Reproducer from gh-157660.
script = textwrap.dedent("""\
import os, threading
from _remote_debugging import RemoteUnwinder
from test import support

go = threading.Event()
stop = threading.Event()

def leaf():
stop.wait()

def wait_for_leaf_frames(u, expected_count):
for _ in support.sleeping_retry(
support.SHORT_TIMEOUT,
f"Expected {expected_count} leaf frames",
):
try:
traces = u.get_stack_trace()
except RuntimeError as exc:
if str(exc) != "Failed to parse initial frame in chain":
raise
continue
count = sum(
f.funcname == "leaf"
for i in traces
for t in i.threads for f in t.frame_info
)
if count == expected_count:
return

threading.Thread(target=leaf, daemon=True).start()
for _ in range(16):
threading.Thread(target=stop.wait, daemon=True).start()
threading.Thread(target=lambda: (go.wait(), leaf()), daemon=True).start()

u = RemoteUnwinder(os.getpid(), all_threads=True, cache_frames=False)
wait_for_leaf_frames(u, 1)
go.set()
wait_for_leaf_frames(u, 2)
""")
result = subprocess.run(
[sys.executable, "-X", "gil=0", "-X", "tlbc=1", "-c", script],
capture_output=True,
text=True,
timeout=SHORT_TIMEOUT,
)
self.assertEqual(
result.returncode, 0,
f"stdout: {result.stdout}\nstderr: {result.stderr}",
)

@skip_if_not_supported
@unittest.skipIf(sys._is_gil_enabled(), "Requires free-threading")
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
"Requires process_vm_readv",
)
def test_tlbc_cache_refresh_after_slot_fill(self):
# Reproducer from gh-157660.
script = textwrap.dedent("""\
import os, threading
from _remote_debugging import RemoteUnwinder

go = threading.Event()
stop = threading.Event()

def leaf():
stop.wait()

from test import support

def lines(u, expected_count):
for _ in support.sleeping_retry(
support.SHORT_TIMEOUT,
f"Expected {expected_count} leaf frames",
):
try:
traces = u.get_stack_trace()
except RuntimeError as exc:
if str(exc) != "Failed to parse initial frame in chain":
raise
continue
result = sorted(
f.location.lineno
for i in traces
for t in i.threads for f in t.frame_info
if f.funcname == "leaf"
)
# A new frame can still point at the function definition.
if (len(result) == expected_count and
leaf.__code__.co_firstlineno not in result):
return result

threading.Thread(target=leaf, daemon=True).start()
threading.Thread(target=lambda: (go.wait(), leaf()), daemon=True).start()
u = RemoteUnwinder(os.getpid(), all_threads=True, cache_frames=False)
before = lines(u, 1)
assert before == [8], before
go.set()
cached = lines(u, 2)
assert cached == [8, 8], cached
""")
result = subprocess.run(
[sys.executable, "-X", "gil=0", "-X", "tlbc=1", "-c", script],
capture_output=True,
text=True,
timeout=SHORT_TIMEOUT,
)
self.assertEqual(
result.returncode, 0,
f"stdout: {result.stdout}\nstderr: {result.stderr}",
)


class TestUnsupportedPlatformHandling(unittest.TestCase):
@unittest.skipIf(
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sqlite3/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ def test_trace_bad_handler(self):

def test_set_trace_callback_keyword_args(self):
with self.assertRaisesRegex(TypeError,
'takes exactly 1 positional argument'):
'takes no keyword arguments'):
self.con.set_trace_callback(trace_callback=lambda: None)

# When a handler has an invalid signature, the exception raised is
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_sqlite3/test_userfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ def test_aggr_text(self):

def test_agg_keyword_args(self):
with self.assertRaisesRegex(TypeError,
'takes exactly 3 positional arguments'):
'takes no keyword arguments'):
self.con.create_aggregate("test", 1, aggregate_class=AggrText)


Expand Down Expand Up @@ -767,7 +767,7 @@ def test_clear_authorizer(self):

def test_authorizer_keyword_args(self):
with self.assertRaisesRegex(TypeError,
'takes exactly 1 positional argument'):
'takes no keyword arguments'):
self.con.set_authorizer(authorizer_callback=lambda: None)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Argument Clinic now parses arguments of positional-only methods with a
*defining class* directly, without a :c:type:`!_PyArg_Parser`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix incorrect integer return values from :mod:`ctypes` callbacks on some
platforms, such as s390x.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :mod:`profiling.sampling` reporting errors or incorrect line numbers in free-threaded
builds when a thread-local bytecode array grows or gains entries after being cached.
5 changes: 5 additions & 0 deletions Modules/_ctypes/_ctypes_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,11 @@ EXPORT(long long) _testfunc_callback_q_qf(long long value,
return sum;
}

EXPORT(long long) _testfunc_callback_int_to_longlong(int (*func)(void))
{
return func();
}

typedef struct {
char *name;
char *value;
Expand Down
67 changes: 59 additions & 8 deletions Modules/_ctypes/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@ TryAddRef(PyObject *cnv, CDataObject *obj)
}
#endif

static int
is_narrow_int_ffi_type(int type)
{
switch (type) {
case FFI_TYPE_SINT8:
case FFI_TYPE_UINT8:
case FFI_TYPE_SINT16:
case FFI_TYPE_UINT16:
case FFI_TYPE_SINT32:
case FFI_TYPE_UINT32:
return 1;
default:
return 0;
}
}

/******************************************************************************
*
* Call the python object with all arguments
Expand Down Expand Up @@ -222,13 +238,21 @@ static void _CallPythonObject(ctypes_state *st,
if (restype != &ffi_type_void && result) {
assert(setfunc);

#ifdef WORDS_BIGENDIAN
/* See the corresponding code in _ctypes_callproc():
in callproc.c, around line 1219. */
if (restype->type != FFI_TYPE_FLOAT && restype->size < sizeof(ffi_arg)) {
mem = (char *)mem + sizeof(ffi_arg) - restype->size;
}
#endif
/* libffi's closure contract requires integral results narrower
than ffi_arg to fill a whole register, sign-extended if signed;
setfunc() only writes restype->size bytes. */
union {
ffi_arg arg;
int8_t s8;
uint8_t u8;
int16_t s16;
uint16_t u16;
int32_t s32;
uint32_t u32;
} narrow_res = {0};
int narrow = restype->size < sizeof(ffi_arg) &&
is_narrow_int_ffi_type(restype->type);
void *resmem = narrow ? (void *)&narrow_res : mem;

/* keep is an object we have to keep alive so that the result
stays valid. If there is no such object, the setfunc will
Expand All @@ -239,7 +263,34 @@ static void _CallPythonObject(ctypes_state *st,
be the result. EXCEPT when restype is py_object - Python
itself knows how to manage the refcount of these objects.
*/
PyObject *keep = setfunc(mem, result, restype->size);
PyObject *keep = setfunc(resmem, result, restype->size);

if (narrow && keep != NULL) {
ffi_arg widened;
switch (restype->type) {
case FFI_TYPE_SINT8:
widened = (ffi_arg)(ffi_sarg)narrow_res.s8;
break;
case FFI_TYPE_SINT16:
widened = (ffi_arg)(ffi_sarg)narrow_res.s16;
break;
case FFI_TYPE_SINT32:
widened = (ffi_arg)(ffi_sarg)narrow_res.s32;
break;
case FFI_TYPE_UINT8:
widened = narrow_res.u8;
break;
case FFI_TYPE_UINT16:
widened = narrow_res.u16;
break;
case FFI_TYPE_UINT32:
widened = narrow_res.u32;
break;
default:
Py_UNREACHABLE();
}
memcpy(mem, &widened, sizeof(ffi_arg));
}

if (keep == NULL) {
/* Could not convert callback result. */
Expand Down
Loading
Loading