Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-116180: check if globals is NULL and set error in run_eval_code_obj() #116637

Merged
merged 50 commits into from May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9635b76
check if gobals is null
ngr-ilmarh Mar 12, 2024
3e97671
make NULL check earlier
ngr-ilmarh Mar 12, 2024
348d895
Merge branch 'main' into fix-issue-116180
NGRsoftlab Mar 12, 2024
259034c
PEP-7
ngr-ilmarh Mar 12, 2024
85d9f47
Merge branch 'main' into fix-issue-116180
NGRsoftlab Mar 13, 2024
cf578bd
Merge branch 'main' into fix-issue-116180
NGRsoftlab Mar 14, 2024
bba6927
Merge branch 'main' into fix-issue-116180
NGRsoftlab Mar 20, 2024
1b5fe4c
Squashed commit of the following:
ngr-ilmarh Apr 2, 2024
a016a54
Squashed commit of the following:
ngr-ilmarh Apr 2, 2024
173b9ce
Merge branch 'fix-issue-116180' of https://github.com/NGRsoftlab/cpyt…
ngr-ilmarh Apr 2, 2024
86c6e57
Merge branch 'main' into fix-issue-116180
ngr-ilmarh Apr 2, 2024
7503772
change if null to assert
ngr-ilmarh Apr 2, 2024
f175046
Revert "change if null to assert"
ngr-ilmarh Apr 2, 2024
dcab696
bring back error message instead of assert
ngr-ilmarh Apr 2, 2024
9532797
return error instead of segfault and test it
ngr-ilmarh Apr 5, 2024
ee0fd8a
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 5, 2024
7eca26f
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 5, 2024
08b5c3a
Update Python/pythonrun.c
NGRsoftlab Apr 5, 2024
23b9fe6
Update Modules/_testcapi/pyrun.c
NGRsoftlab Apr 5, 2024
acc7eb3
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 5, 2024
09c71a9
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 8, 2024
806d21c
refactor unittests through helper
ngr-ilmarh Apr 8, 2024
907c86d
fix closeit test when expect exception global == NULL
ngr-ilmarh Apr 8, 2024
af9e9fc
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 9, 2024
56f0d3c
refactor python test, add some error handling to module init
ngr-ilmarh Apr 9, 2024
b1d7ab2
fix lost bracket
ngr-ilmarh Apr 9, 2024
f8e29ce
cleanup (and rename)
ngr-ilmarh Apr 9, 2024
efe064d
make windows build tests
ngr-ilmarh Apr 9, 2024
8f33e7a
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 10, 2024
296705a
next round of refactoring
ngr-ilmarh Apr 10, 2024
cd58eb6
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 11, 2024
c461bd9
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 12, 2024
7db13d0
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 15, 2024
eef557c
Globals must be dict. Fix filename encoding and file closing in the w…
serhiy-storchaka Apr 15, 2024
66646e9
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 15, 2024
45cab7f
Skip tests with undecodable filename if not supported.
serhiy-storchaka Apr 15, 2024
1716ac6
Add a test for PyRun_StringFlags(). Test globals at the lower level.
serhiy-storchaka Apr 16, 2024
1a9532c
Pass filename as bytes.
serhiy-storchaka Apr 16, 2024
6f0a0a4
Merge branch 'main' into fix-issue-116180
NGRsoftlab Apr 17, 2024
2607806
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 17, 2024
7f4c496
Merge branch 'main' into fix-issue-116180
NGRsoftlab Apr 17, 2024
5974757
Merge branch 'python:main' into fix-issue-116180
NGRsoftlab Apr 17, 2024
2b4ba12
Update Lib/test/test_capi/test_pyrun.py
serhiy-storchaka Apr 17, 2024
aa73f40
Merge branch 'fix-issue-116180' of https://github.com/NGRsoftlab/cpyt…
ngr-ilmarh Apr 27, 2024
f6a8ee9
stashed changes
ngr-ilmarh Apr 27, 2024
ccf5871
remove renamed files
ngr-ilmarh Apr 27, 2024
abeea0a
Enable tests for NULL and non-dict globals.
serhiy-storchaka Apr 28, 2024
d579f21
Add tests for a dict subclass.
serhiy-storchaka Apr 28, 2024
d3938f5
Merge branch 'main' into fix-issue-116180
NGRsoftlab May 2, 2024
4a18dd2
Merge branch 'main' into fix-issue-116180
NGRsoftlab May 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 103 additions & 0 deletions Lib/test/test_capi/test_pyrun.py
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,103 @@
import unittest, os
serhiy-storchaka marked this conversation as resolved.
Show resolved Hide resolved
from collections import UserDict
from test.support import import_helper
from test.support.os_helper import unlink, TESTFN, TESTFN_UNDECODABLE

NULL = None
_testcapi = import_helper.import_module('_testcapi')
Py_single_input = _testcapi.Py_single_input
Py_file_input = _testcapi.Py_file_input
Py_eval_input = _testcapi.Py_eval_input

serhiy-storchaka marked this conversation as resolved.
Show resolved Hide resolved

class PyRunTest(unittest.TestCase):
# TODO: Test the following functions:
#
# PyRun_SimpleStringFlags
# PyRun_AnyFileExFlags
# PyRun_SimpleFileExFlags
# PyRun_InteractiveOneFlags
# PyRun_InteractiveOneObject
# PyRun_InteractiveLoopFlags
# PyRun_String (may be a macro)
# PyRun_AnyFile (may be a macro)
# PyRun_AnyFileEx (may be a macro)
# PyRun_AnyFileFlags (may be a macro)
# PyRun_SimpleString (may be a macro)
# PyRun_SimpleFile (may be a macro)
# PyRun_SimpleFileEx (may be a macro)
# PyRun_InteractiveOne (may be a macro)
# PyRun_InteractiveLoop (may be a macro)
# PyRun_File (may be a macro)
# PyRun_FileEx (may be a macro)
# PyRun_FileFlags (may be a macro)
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved

def test_pyrun_stringflags(self):
# Test PyRun_StringFlags().
def run(s, *args):
return _testcapi.run_stringflags(s, Py_file_input, *args)
source = b'a\n'

self.assertIsNone(run(b'a\n', dict(a=1)))
self.assertIsNone(run(b'a\n', dict(a=1), {}))
self.assertIsNone(run(b'a\n', {}, dict(a=1)))
self.assertIsNone(run(b'a\n', {}, UserDict(a=1)))

self.assertRaises(NameError, run, b'a\n', {})
self.assertRaises(NameError, run, b'a\n', {}, {})
self.assertRaises(TypeError, run, b'a\n', dict(a=1), [])
self.assertRaises(TypeError, run, b'a\n', dict(a=1), 1)

self.assertIsNone(run(b'\xc3\xa4\n', {'\xe4': 1}))
self.assertRaises(SyntaxError, run, b'\xe4\n', {})

self.assertRaises(SystemError, run, b'a\n', NULL)
self.assertRaises(SystemError, run, b'a\n', NULL, {})
self.assertRaises(SystemError, run, b'a\n', NULL, dict(a=1))
self.assertRaises(SystemError, run, b'a\n', UserDict())
self.assertRaises(SystemError, run, b'a\n', UserDict(), {})
self.assertRaises(SystemError, run, b'a\n', UserDict(), dict(a=1))

# CRASHES run(NULL, {})

def test_pyrun_fileexflags(self):
# Test PyRun_FileExFlags().
filename = os.fsencode(TESTFN)
with open(filename, 'wb') as fp:
fp.write(b'a\n')
self.addCleanup(unlink, filename)
def run(*args):
return _testcapi.run_fileexflags(filename, Py_file_input, *args)

self.assertIsNone(run(dict(a=1)))
self.assertIsNone(run(dict(a=1), {}))
self.assertIsNone(run({}, dict(a=1)))
self.assertIsNone(run({}, UserDict(a=1)))
self.assertIsNone(run(dict(a=1), {}, 1)) # closeit = True

self.assertRaises(NameError, run, {})
self.assertRaises(NameError, run, {}, {})
self.assertRaises(TypeError, run, dict(a=1), [])
self.assertRaises(TypeError, run, dict(a=1), 1)

self.assertRaises(SystemError, run, NULL)
self.assertRaises(SystemError, run, NULL, {})
self.assertRaises(SystemError, run, NULL, dict(a=1))
self.assertRaises(SystemError, run, UserDict())
self.assertRaises(SystemError, run, UserDict(), {})
self.assertRaises(SystemError, run, UserDict(), dict(a=1))

@unittest.skipUnless(TESTFN_UNDECODABLE, 'only works if there are undecodable paths')
def test_pyrun_fileexflags_with_undecodable_filename(self):
run = _testcapi.run_fileexflags
try:
with open(TESTFN_UNDECODABLE, 'wb') as fp:
fp.write(b'a\n')
self.addCleanup(unlink, TESTFN_UNDECODABLE)
except OSError:
self.skipTest('undecodable paths are not supported')
self.assertIsNone(run(TESTFN_UNDECODABLE, Py_file_input, dict(a=1)))


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Expand Up @@ -162,7 +162,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyrun.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
Expand Down
1 change: 1 addition & 0 deletions Modules/_testcapi/parts.h
Expand Up @@ -51,6 +51,7 @@ int _PyTestCapi_Init_Exceptions(PyObject *module);
int _PyTestCapi_Init_Code(PyObject *module);
int _PyTestCapi_Init_Buffer(PyObject *module);
int _PyTestCapi_Init_PyAtomic(PyObject *module);
int _PyTestCapi_Init_PyRun(PyObject *module);
int _PyTestCapi_Init_File(PyObject *module);
int _PyTestCapi_Init_Codec(PyObject *module);
int _PyTestCapi_Init_Immortal(PyObject *module);
Expand Down
112 changes: 112 additions & 0 deletions Modules/_testcapi/pyrun.c
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,112 @@
#include "parts.h"
#include "util.h"

#include <stdio.h>
#include <errno.h>


static PyObject *
run_stringflags(PyObject *mod, PyObject *pos_args)
{
const char *str;
Py_ssize_t size;
int start;
PyObject *globals = NULL;
PyObject *locals = NULL;
PyCompilerFlags flags = _PyCompilerFlags_INIT;
PyCompilerFlags *pflags = NULL;
int cf_flags = 0;
int cf_feature_version = 0;

if (!PyArg_ParseTuple(pos_args, "z#iO|Oii",
&str, &size, &start, &globals, &locals,
&cf_flags, &cf_feature_version)) {
return NULL;
}

NULLABLE(globals);
NULLABLE(locals);
if (cf_flags || cf_feature_version) {
flags.cf_flags = cf_flags;
flags.cf_feature_version = cf_feature_version;
pflags = &flags;
}

return PyRun_StringFlags(str, start, globals, locals, pflags);
}

static PyObject *
run_fileexflags(PyObject *mod, PyObject *pos_args)
{
PyObject *result = NULL;
const char *filename = NULL;
Py_ssize_t filename_size;
int start;
PyObject *globals = NULL;
PyObject *locals = NULL;
int closeit = 0;
PyCompilerFlags flags = _PyCompilerFlags_INIT;
PyCompilerFlags *pflags = NULL;
int cf_flags = 0;
int cf_feature_version = 0;

FILE *fp = NULL;

if (!PyArg_ParseTuple(pos_args, "z#iO|Oiii",
&filename, &filename_size, &start, &globals, &locals,
&closeit, &cf_flags, &cf_feature_version)) {
return NULL;
}

NULLABLE(globals);
NULLABLE(locals);
if (cf_flags || cf_feature_version) {
flags.cf_flags = cf_flags;
flags.cf_feature_version = cf_feature_version;
pflags = &flags;
}

fp = fopen(filename, "r");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if filename is NULL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linux will return something like EFAULT with fp = NULL.
And it is not NULLABLE. Anyway, filename = NULL made test fail:

OSError: [Errno 14] Bad address

But NULL can be passed, if you use fp of stdin for example

if (fp == NULL) {
PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
return NULL;
}

result = PyRun_FileExFlags(fp, filename, start, globals, locals, closeit, pflags);

#if !defined(__wasi__)
/* The behavior of fileno() after fclose() is undefined. */
if (closeit && result && fileno(fp) >= 0) {
PyErr_SetString(PyExc_AssertionError, "File was not closed after excution");
Py_DECREF(result);
fclose(fp);
return NULL;
}
#endif
if (!closeit && fileno(fp) < 0) {
PyErr_SetString(PyExc_AssertionError, "Bad file descriptor after excution");
Py_XDECREF(result);
return NULL;
}

if (!closeit) {
fclose(fp); /* don't need open file any more*/
}

return result;
}

static PyMethodDef test_methods[] = {
{"run_stringflags", run_stringflags, METH_VARARGS},
{"run_fileexflags", run_fileexflags, METH_VARARGS},
{NULL},
};

int
_PyTestCapi_Init_PyRun(PyObject *mod)
{
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}
return 0;
}
13 changes: 13 additions & 0 deletions Modules/_testcapimodule.c
Expand Up @@ -3904,6 +3904,16 @@ PyInit__testcapi(void)
PyModule_AddIntConstant(m, "the_number_three", 3);
PyModule_AddIntMacro(m, Py_C_RECURSION_LIMIT);

if (PyModule_AddIntMacro(m, Py_single_input)) {
return NULL;
}
if (PyModule_AddIntMacro(m, Py_file_input)) {
return NULL;
}
if (PyModule_AddIntMacro(m, Py_eval_input)) {
return NULL;
}

testcapistate_t *state = get_testcapi_state(m);
state->error = PyErr_NewException("_testcapi.error", NULL, NULL);
PyModule_AddObject(m, "error", state->error);
Expand Down Expand Up @@ -3998,6 +4008,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_PyAtomic(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_PyRun(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_Hash(m) < 0) {
return NULL;
}
Expand Down
1 change: 1 addition & 0 deletions PCbuild/_testcapi.vcxproj
Expand Up @@ -124,6 +124,7 @@
<ClCompile Include="..\Modules\_testcapi\object.c" />
<ClCompile Include="..\Modules\_testcapi\immortal.c" />
<ClCompile Include="..\Modules\_testcapi\gc.c" />
<ClCompile Include="..\Modules\_testcapi\pyrun.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
Expand Down
3 changes: 3 additions & 0 deletions PCbuild/_testcapi.vcxproj.filters
Expand Up @@ -105,6 +105,9 @@
<ClCompile Include="..\Modules\_testcapi\gc.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Modules\_testcapi\pyrun.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
Expand Down
21 changes: 12 additions & 9 deletions Python/pythonrun.c
Expand Up @@ -1275,17 +1275,20 @@ run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, Py
_PyRuntime.signals.unhandled_keyboard_interrupt = 0;

/* Set globals['__builtins__'] if it doesn't exist */
if (globals != NULL) {
int has_builtins = PyDict_ContainsString(globals, "__builtins__");
if (has_builtins < 0) {
if (!globals || !PyDict_Check(globals)) {
PyErr_SetString(PyExc_SystemError, "globals must be a real dict");
return NULL;
}
int has_builtins = PyDict_ContainsString(globals, "__builtins__");
if (has_builtins < 0) {
return NULL;
}
if (!has_builtins) {
if (PyDict_SetItemString(globals, "__builtins__",
tstate->interp->builtins) < 0)
{
return NULL;
}
if (!has_builtins) {
if (PyDict_SetItemString(globals, "__builtins__",
tstate->interp->builtins) < 0) {
return NULL;
}
}
}

v = PyEval_EvalCode((PyObject*)co, globals, locals);
Expand Down