Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2827,6 +2827,8 @@ def test_cli_converters(self):
"slice_index",
"str",
"unicode",
"unicode_fs_decoded",
"unicode_fs_encoded",
"unsigned_char",
"unsigned_int",
"unsigned_long",
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,21 @@ def test_compile_filename(self):
compile('pass', filename, 'exec')
self.assertRaises(TypeError, compile, 'pass', list(b'file.py'), 'exec')

def test_compile_filename_refleak(self):
# Regression tests for reference leak in PyUnicode_FSDecoder.
# See https://github.com/python/cpython/issues/139748.
mortal_str = 'this is a mortal string'
# check error path when 'mode' AC conversion failed
self.assertRaises(TypeError, compile, b'', mortal_str, mode=1234)
# check error path when 'optimize' AC conversion failed
self.assertRaises(OverflowError, compile, b'', mortal_str,
'exec', optimize=1 << 1000)
# check error path when 'dont_inherit' AC conversion failed
class EvilBool:
def __bool__(self): raise ValueError
self.assertRaises(ValueError, compile, b'', mortal_str,
'exec', dont_inherit=EvilBool())

@support.cpython_only
def test_same_filename_used(self):
s = """def f(): pass\ndef g(): pass"""
Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_symtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,34 @@ def test_symtable_entry_repr(self):
expected = f"<symtable entry top({self.top.get_id()}), line {self.top.get_lineno()}>"
self.assertEqual(repr(self.top._table), expected)

def test__symtable_refleak(self):
# Regression test for reference leak in PyUnicode_FSDecoder.
# See https://github.com/python/cpython/issues/139748.
mortal_str = 'this is a mortal string'
# check error path when 'compile_type' AC conversion failed
self.assertRaises(TypeError, symtable.symtable, '', mortal_str, 1)


class ComprehensionTests(unittest.TestCase):
def get_identifiers_recursive(self, st, res):
res.extend(st.get_identifiers())
for ch in st.get_children():
self.get_identifiers_recursive(ch, res)

def test_loopvar_in_only_one_scope(self):
# ensure that the loop variable appears only once in the symtable
comps = [
"[x for x in [1]]",
"{x for x in [1]}",
"{x:x*x for x in [1]}",
]
for comp in comps:
with self.subTest(comp=comp):
st = symtable.symtable(comp, "?", "exec")
ids = []
self.get_identifiers_recursive(st, ids)
self.assertEqual(len([x for x in ids if x == 'x']), 1)


class CommandLineTest(unittest.TestCase):
maxDiff = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix reference leaks in error branches of functions accepting path strings or
bytes such as :func:`compile` and :func:`os.system`. Patch by Bénédikt Tran.
5 changes: 2 additions & 3 deletions Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1782,14 +1782,14 @@ _certificate_to_der(_sslmodulestate *state, X509 *certificate)

/*[clinic input]
_ssl._test_decode_cert
path: object(converter="PyUnicode_FSConverter")
path: unicode_fs_encoded
/

[clinic start generated code]*/

static PyObject *
_ssl__test_decode_cert_impl(PyObject *module, PyObject *path)
/*[clinic end generated code: output=96becb9abb23c091 input=cdeaaf02d4346628]*/
/*[clinic end generated code: output=96becb9abb23c091 input=cb4988d5e651a4f8]*/
{
PyObject *retval = NULL;
X509 *x=NULL;
Expand Down Expand Up @@ -1819,7 +1819,6 @@ _ssl__test_decode_cert_impl(PyObject *module, PyObject *path)
X509_free(x);

fail0:
Py_DECREF(path);
if (cert != NULL) BIO_free(cert);
return retval;
}
Expand Down
7 changes: 5 additions & 2 deletions Modules/clinic/_ssl.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Modules/clinic/symtablemodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 14 additions & 25 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3044,17 +3044,6 @@ class dev_t_return_converter(unsigned_long_return_converter):
conversion_fn = '_PyLong_FromDev'
unsigned_cast = '(dev_t)'

class FSConverter_converter(CConverter):
type = 'PyObject *'
converter = 'PyUnicode_FSConverter'
def converter_init(self):
if self.default is not unspecified:
fail("FSConverter_converter does not support default values")
self.c_default = 'NULL'

def cleanup(self):
return "Py_XDECREF(" + self.name + ");\n"

class pid_t_converter(CConverter):
type = 'pid_t'
format_unit = '" _Py_PARSE_PID "'
Expand Down Expand Up @@ -3093,7 +3082,7 @@ class confname_converter(CConverter):
""", argname=argname, converter=self.converter, table=self.table)

[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=a6199b1618d73f53]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=1a078a0f42a586c1]*/

/*[clinic input]

Expand Down Expand Up @@ -6013,14 +6002,14 @@ os_system_impl(PyObject *module, const wchar_t *command)
/*[clinic input]
os.system -> long

command: FSConverter
command: unicode_fs_encoded

Execute the command in a subshell.
[clinic start generated code]*/

static long
os_system_impl(PyObject *module, PyObject *command)
/*[clinic end generated code: output=290fc437dd4f33a0 input=86a58554ba6094af]*/
/*[clinic end generated code: output=290fc437dd4f33a0 input=47c6f24b6dc92881]*/
{
long result;
const char *bytes = PyBytes_AsString(command);
Expand Down Expand Up @@ -9200,7 +9189,7 @@ os_getgroups_impl(PyObject *module)
/*[clinic input]
os.initgroups

username as oname: FSConverter
username as oname: unicode_fs_encoded
gid: int
/

Expand All @@ -9213,12 +9202,12 @@ group id.

static PyObject *
os_initgroups_impl(PyObject *module, PyObject *oname, int gid)
/*[clinic end generated code: output=7f074d30a425fd3a input=df3d54331b0af204]*/
/*[clinic end generated code: output=7f074d30a425fd3a input=984e60c7fed88cb4]*/
#else
/*[clinic input]
os.initgroups

username as oname: FSConverter
username as oname: unicode_fs_encoded
gid: gid_t
/

Expand All @@ -9231,7 +9220,7 @@ group id.

static PyObject *
os_initgroups_impl(PyObject *module, PyObject *oname, gid_t gid)
/*[clinic end generated code: output=59341244521a9e3f input=0cb91bdc59a4c564]*/
/*[clinic end generated code: output=59341244521a9e3f input=17d8fbe2dea42ca4]*/
#endif
{
const char *username = PyBytes_AS_STRING(oname);
Expand Down Expand Up @@ -12956,16 +12945,16 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
/*[clinic input]
os.putenv

name: FSConverter
value: FSConverter
name: unicode_fs_encoded
value: unicode_fs_encoded
/

Change or add an environment variable.
[clinic start generated code]*/

static PyObject *
os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
/*[clinic end generated code: output=d29a567d6b2327d2 input=a97bc6152f688d31]*/
/*[clinic end generated code: output=d29a567d6b2327d2 input=84fcd30f873c8c45]*/
{
const char *name_string = PyBytes_AS_STRING(name);
const char *value_string = PyBytes_AS_STRING(value);
Expand Down Expand Up @@ -13008,15 +12997,15 @@ os_unsetenv_impl(PyObject *module, PyObject *name)
#else
/*[clinic input]
os.unsetenv
name: FSConverter
name: unicode_fs_encoded
/

Delete an environment variable.
[clinic start generated code]*/

static PyObject *
os_unsetenv_impl(PyObject *module, PyObject *name)
/*[clinic end generated code: output=54c4137ab1834f02 input=2bb5288a599c7107]*/
/*[clinic end generated code: output=54c4137ab1834f02 input=78ff12e505ade80a]*/
{
if (PySys_Audit("os.unsetenv", "(O)", name) < 0) {
return NULL;
Expand Down Expand Up @@ -15074,14 +15063,14 @@ os_urandom_impl(PyObject *module, Py_ssize_t size)
/*[clinic input]
os.memfd_create

name: FSConverter
name: unicode_fs_encoded
flags: unsigned_int(bitwise=True, c_default="MFD_CLOEXEC") = MFD_CLOEXEC

[clinic start generated code]*/

static PyObject *
os_memfd_create_impl(PyObject *module, PyObject *name, unsigned int flags)
/*[clinic end generated code: output=6681ede983bdb9a6 input=a42cfc199bcd56e9]*/
/*[clinic end generated code: output=6681ede983bdb9a6 input=cd0eb092cfac474b]*/
{
int fd;
const char *bytes = PyBytes_AS_STRING(name);
Expand Down
1 change: 0 additions & 1 deletion Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7115,7 +7115,6 @@ _socket_socket_if_nametoindex_impl(PySocketSockObject *self, PyObject *oname)
#endif

index = if_nametoindex(PyBytes_AS_STRING(oname));
Py_DECREF(oname);
if (index == 0) {
/* if_nametoindex() doesn't set errno */
PyErr_SetString(PyExc_OSError, "no interface with this name");
Expand Down
6 changes: 2 additions & 4 deletions Modules/symtablemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module _symtable
_symtable.symtable

source: object
filename: object(converter='PyUnicode_FSDecoder')
filename: unicode_fs_decoded
startstr: str
/

Expand All @@ -23,7 +23,7 @@ Return symbol and scope dictionaries used internally by compiler.
static PyObject *
_symtable_symtable_impl(PyObject *module, PyObject *source,
PyObject *filename, const char *startstr)
/*[clinic end generated code: output=59eb0d5fc7285ac4 input=9dd8a50c0c36a4d7]*/
/*[clinic end generated code: output=59eb0d5fc7285ac4 input=436ffff90d02e4f6]*/
{
struct symtable *st;
PyObject *t;
Expand All @@ -47,12 +47,10 @@ _symtable_symtable_impl(PyObject *module, PyObject *source,
else {
PyErr_SetString(PyExc_ValueError,
"symtable() arg 3 must be 'exec' or 'eval' or 'single'");
Py_DECREF(filename);
Py_XDECREF(source_copy);
return NULL;
}
st = _Py_SymtableStringObjectFlags(str, filename, start, &cf);
Py_DECREF(filename);
Py_XDECREF(source_copy);
if (st == NULL) {
return NULL;
Expand Down
5 changes: 2 additions & 3 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ builtin_chr(PyObject *module, PyObject *i)
compile as builtin_compile

source: object
filename: object(converter="PyUnicode_FSDecoder")
filename: unicode_fs_decoded
mode: str
flags: int = 0
dont_inherit: bool = False
Expand All @@ -759,7 +759,7 @@ static PyObject *
builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
const char *mode, int flags, int dont_inherit,
int optimize, int feature_version)
/*[clinic end generated code: output=b0c09c84f116d3d7 input=cc78e20e7c7682ba]*/
/*[clinic end generated code: output=b0c09c84f116d3d7 input=8f0069edbdac381b]*/
{
PyObject *source_copy;
const char *str;
Expand Down Expand Up @@ -885,7 +885,6 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
error:
result = NULL;
finally:
Py_DECREF(filename);
return result;
}

Expand Down
7 changes: 5 additions & 2 deletions Python/clinic/bltinmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions Tools/clinic/libclinic/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,26 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st
return super().parse_arg(argname, displayname, limited_capi=limited_capi)


class _unicode_fs_converter_base(CConverter):
type = 'PyObject *'

def converter_init(self) -> None:
if self.default is not unspecified:
fail(f"{self.__class__.__name__} does not support default values")
self.c_default = 'NULL'

def cleanup(self) -> str:
return f"Py_XDECREF({self.parser_name});"


class unicode_fs_encoded_converter(_unicode_fs_converter_base):
converter = 'PyUnicode_FSConverter'


class unicode_fs_decoded_converter(_unicode_fs_converter_base):
converter = 'PyUnicode_FSDecoder'


@add_legacy_c_converter('u')
@add_legacy_c_converter('u#', zeroes=True)
@add_legacy_c_converter('Z', accept={str, NoneType})
Expand Down
Loading