Skip to content

Commit 10953aa

Browse files
vstinnermiss-islington
authored andcommitted
gh-152132: Test Py_CompileString() in test_capi.test_run (GH-153470)
PyRun functions now raises ValueError if the start argument is invalid. * Add Modules/_testlimitedcapi/run.c. * Rename _Py_CompileStringObjectWithModule() to _Py_CompileStringObject(). (cherry picked from commit d83d267) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent cecafeb commit 10953aa

12 files changed

Lines changed: 243 additions & 46 deletions

File tree

Include/cpython/pythonrun.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ PyAPI_FUNC(PyObject *) PyRun_FileExFlags(
4040
PyCompilerFlags *flags);
4141

4242

43+
PyAPI_FUNC(PyObject *) Py_CompileStringFlags(
44+
const char *str,
45+
const char *filename,
46+
int start,
47+
PyCompilerFlags *flags);
4348
PyAPI_FUNC(PyObject *) Py_CompileStringExFlags(
4449
const char *str,
4550
const char *filename, /* decoded from the filesystem encoding */

Include/internal/pycore_pythonrun.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ extern const char* _Py_SourceAsString(
2828
PyCompilerFlags *cf,
2929
PyObject **cmd_copy);
3030

31-
extern PyObject * _Py_CompileStringObjectWithModule(
31+
extern PyObject * _Py_CompileString(
3232
const char *str,
3333
PyObject *filename, int start,
3434
PyCompilerFlags *flags, int optimize,

Lib/test/test_capi/test_run.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1+
import ast
12
import os
23
import sys
34
import tempfile
5+
import textwrap
46
import unittest
57
from collections import UserDict
68
from test import support
79
from test.support import import_helper
810
from test.support.os_helper import unlink, TESTFN, TESTFN_ASCII, TESTFN_UNDECODABLE
911

12+
_testcapi = import_helper.import_module('_testcapi')
13+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
14+
1015

1116
NULL = None
12-
_testcapi = import_helper.import_module('_testcapi')
1317
Py_single_input = _testcapi.Py_single_input
1418
Py_file_input = _testcapi.Py_file_input
1519
Py_eval_input = _testcapi.Py_eval_input
20+
INVALID_START = Py_single_input - 1
1621
STDIN = '<stdin>'
1722
STDERR_FD = 2
23+
PyCF_ONLY_AST = _testcapi.PyCF_ONLY_AST
24+
PyCF_IGNORE_COOKIE = _testcapi.PyCF_IGNORE_COOKIE
1825

1926
# Code raising a SyntaxError
2027
SYNTAX_ERROR = 'True = 1'
@@ -68,6 +75,9 @@ def run(s, *args):
6875
run(b'raise ValueError("BUG")', {})
6976
self.assertEqual(str(cm.exception), 'BUG')
7077

78+
with self.assertRaises(ValueError):
79+
func(b'x = 1', INVALID_START, {})
80+
7181
self.assertIsNone(run(b'a\n', dict(a=1)))
7282
self.assertIsNone(run(b'a\n', dict(a=1), {}))
7383
self.assertIsNone(run(b'a\n', {}, dict(a=1)))
@@ -118,6 +128,9 @@ def run(*args):
118128
closeit = 1
119129
self.assertIsNone(run(dict(a=1), {}, closeit))
120130

131+
with self.assertRaises(ValueError):
132+
func(filename, INVALID_START, {})
133+
121134
self.assertRaises(NameError, run, {})
122135
self.assertRaises(NameError, run, {}, {})
123136
self.assertRaises(TypeError, run, dict(a=1), [])
@@ -357,6 +370,65 @@ def test_run_simplestringflags(self):
357370
# Test PyRun_SimpleStringFlags()
358371
self.check_run_simplestring(_testcapi.run_simplestringflags)
359372

373+
def check_compilestring(self, compilestring, has_flags, encode_filename=True):
374+
filename_str = TESTFN
375+
if encode_filename:
376+
filename = os.fsencode(filename_str)
377+
else:
378+
filename = filename_str
379+
380+
def check_code(co, name, value):
381+
ns = {}
382+
exec(co, ns, ns)
383+
self.assertEqual(ns[name], value)
384+
385+
co = compilestring(b'x = 1', filename, Py_file_input)
386+
self.assertEqual(co.co_filename, filename_str)
387+
check_code(co, 'x', 1)
388+
389+
if has_flags:
390+
code = textwrap.dedent("""
391+
# encoding: latin1
392+
x = 'a\xe9'
393+
""")
394+
co = compilestring(code.encode(), filename, Py_file_input, PyCF_IGNORE_COOKIE)
395+
self.assertEqual(co.co_filename, filename_str)
396+
check_code(co, 'x', 'a\xe9')
397+
398+
co = compilestring(code.encode(), filename, Py_file_input)
399+
self.assertEqual(co.co_filename, filename_str)
400+
check_code(co, 'x', 'a\xc3\xa9')
401+
402+
tree = compilestring(b'x = 1', filename, Py_file_input, PyCF_ONLY_AST)
403+
self.assertIsInstance(tree, ast.AST)
404+
405+
co = compilestring(b'raise ValueError("BUG")', filename, Py_file_input)
406+
with self.assertRaises(ValueError):
407+
exec(co, {})
408+
409+
with self.assertRaises(SyntaxError) as cm:
410+
compilestring(SYNTAX_ERROR.encode(), filename, Py_file_input)
411+
412+
with self.assertRaises(ValueError):
413+
compilestring(b'x = 1', filename, INVALID_START)
414+
415+
def test_compilestring(self):
416+
# Test Py_CompileString()
417+
self.check_compilestring(_testlimitedcapi.run_compilestring, False)
418+
419+
def test_compilestringflags(self):
420+
# Test Py_CompileStringFlags()
421+
self.check_compilestring(_testcapi.run_compilestringflags, True)
422+
423+
def test_compilestringexflags(self):
424+
# Test Py_CompileStringExFlags()
425+
self.check_compilestring(_testcapi.run_compilestringexflags, True)
426+
427+
def test_compilestringobject(self):
428+
# Test Py_CompileStringObject()
429+
self.check_compilestring(_testcapi.run_compilestringobject, True,
430+
encode_filename=False)
431+
360432

361433
if __name__ == '__main__':
362434
unittest.main()

Modules/Setup.stdlib.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
177177
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c
178178
@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/run.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 _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
179-
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c
179+
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c
180180
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
181181
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
182182

Modules/_testcapi/run.c

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,16 @@
2424
#undef PyRun_InteractiveLoop
2525

2626

27-
// Some PyRun functions crash if start is invalid,
28-
// so validate the start argument.
29-
static int
30-
check_start(int start)
31-
{
32-
if (start == Py_single_input || start == Py_file_input
33-
|| start == Py_eval_input || start == Py_func_type_input)
34-
{
35-
return 0;
36-
}
37-
PyErr_SetString(PyExc_ValueError, "invalid start argument");
38-
return -1;
39-
}
40-
41-
4227
// Test PyRun_String()
4328
static PyObject*
4429
run_string(PyObject *mod, PyObject *args)
4530
{
4631
const char *str;
47-
int start = 0;
32+
int start;
4833
PyObject *globals = NULL;
4934
PyObject *locals = NULL;
5035

51-
if (!PyArg_ParseTuple(args, "y|iOO", &str, &start, &globals, &locals)) {
52-
return NULL;
53-
}
54-
if (check_start(start) < 0) {
36+
if (!PyArg_ParseTuple(args, "yi|OO", &str, &start, &globals, &locals)) {
5537
return NULL;
5638
}
5739
NULLABLE(globals);
@@ -79,9 +61,6 @@ run_stringflags(PyObject *mod, PyObject *args)
7961
&cf_flags, &cf_feature_version)) {
8062
return NULL;
8163
}
82-
if (check_start(start) < 0) {
83-
return NULL;
84-
}
8564
NULLABLE(globals);
8665
NULLABLE(locals);
8766
if (cf_flags || cf_feature_version) {
@@ -481,9 +460,6 @@ run_file(PyObject *mod, PyObject *args)
481460
&globals, &locals)) {
482461
return NULL;
483462
}
484-
if (check_start(start) < 0) {
485-
return NULL;
486-
}
487463
NULLABLE(globals);
488464
NULLABLE(locals);
489465

@@ -518,9 +494,6 @@ run_fileex(PyObject *mod, PyObject *args)
518494
&closeit)) {
519495
return NULL;
520496
}
521-
if (check_start(start) < 0) {
522-
return NULL;
523-
}
524497
NULLABLE(globals);
525498
NULLABLE(locals);
526499

@@ -560,9 +533,6 @@ run_fileflags(PyObject *mod, PyObject *args)
560533
&cf_flags, &cf_feature_version)) {
561534
return NULL;
562535
}
563-
if (check_start(start) < 0) {
564-
return NULL;
565-
}
566536
NULLABLE(globals);
567537
NULLABLE(locals);
568538
if (cf_flags || cf_feature_version) {
@@ -606,9 +576,6 @@ run_fileexflags(PyObject *mod, PyObject *args)
606576
&closeit, &cf_flags, &cf_feature_version)) {
607577
return NULL;
608578
}
609-
if (check_start(start) < 0) {
610-
return NULL;
611-
}
612579
NULLABLE(globals);
613580
NULLABLE(locals);
614581
if (cf_flags || cf_feature_version) {
@@ -733,6 +700,86 @@ run_interactiveloopflags(PyObject *mod, PyObject *args)
733700
}
734701

735702

703+
// Test Py_CompileStringFlags()
704+
static PyObject*
705+
run_compilestringflags(PyObject *mod, PyObject *args)
706+
{
707+
const char *str;
708+
const char *filename;
709+
int start;
710+
PyCompilerFlags flags = _PyCompilerFlags_INIT;
711+
PyCompilerFlags *pflags = NULL;
712+
int cf_flags = 0;
713+
int cf_feature_version = 0;
714+
715+
if (!PyArg_ParseTuple(args, "yyi|ii", &str, &filename, &start,
716+
&cf_flags, &cf_feature_version)) {
717+
return NULL;
718+
}
719+
if (cf_flags || cf_feature_version) {
720+
flags.cf_flags = cf_flags;
721+
flags.cf_feature_version = cf_feature_version;
722+
pflags = &flags;
723+
}
724+
725+
return Py_CompileStringFlags(str, filename, start, pflags);
726+
}
727+
728+
729+
// Test Py_CompileStringExFlags()
730+
static PyObject*
731+
run_compilestringexflags(PyObject *mod, PyObject *args)
732+
{
733+
const char *str;
734+
const char *filename;
735+
int start;
736+
PyCompilerFlags flags = _PyCompilerFlags_INIT;
737+
PyCompilerFlags *pflags = NULL;
738+
int cf_flags = 0;
739+
int cf_feature_version = 0;
740+
int optimize = -1;
741+
742+
if (!PyArg_ParseTuple(args, "yyi|iii", &str, &filename, &start,
743+
&cf_flags, &cf_feature_version, &optimize)) {
744+
return NULL;
745+
}
746+
if (cf_flags || cf_feature_version) {
747+
flags.cf_flags = cf_flags;
748+
flags.cf_feature_version = cf_feature_version;
749+
pflags = &flags;
750+
}
751+
752+
return Py_CompileStringExFlags(str, filename, start, pflags, optimize);
753+
}
754+
755+
756+
// Test Py_CompileStringObject()
757+
static PyObject*
758+
run_compilestringobject(PyObject *mod, PyObject *args)
759+
{
760+
const char *str;
761+
PyObject *filename;
762+
int start;
763+
PyCompilerFlags flags = _PyCompilerFlags_INIT;
764+
PyCompilerFlags *pflags = NULL;
765+
int cf_flags = 0;
766+
int cf_feature_version = 0;
767+
int optimize = -1;
768+
769+
if (!PyArg_ParseTuple(args, "yOi|iii", &str, &filename, &start,
770+
&cf_flags, &cf_feature_version, &optimize)) {
771+
return NULL;
772+
}
773+
if (cf_flags || cf_feature_version) {
774+
flags.cf_flags = cf_flags;
775+
flags.cf_feature_version = cf_feature_version;
776+
pflags = &flags;
777+
}
778+
779+
return Py_CompileStringObject(str, filename, start, pflags, optimize);
780+
}
781+
782+
736783
static PyMethodDef test_methods[] = {
737784
{"run_string", run_string, METH_VARARGS},
738785
{"run_stringflags", run_stringflags, METH_VARARGS},
@@ -754,6 +801,9 @@ static PyMethodDef test_methods[] = {
754801
{"run_simplestringflags", run_simplestringflags, METH_VARARGS},
755802
{"run_interactiveloop", run_interactiveloop, METH_VARARGS},
756803
{"run_interactiveloopflags", run_interactiveloopflags, METH_VARARGS},
804+
{"run_compilestringflags", run_compilestringflags, METH_VARARGS},
805+
{"run_compilestringexflags", run_compilestringexflags, METH_VARARGS},
806+
{"run_compilestringobject", run_compilestringobject, METH_VARARGS},
757807
{NULL},
758808
};
759809

@@ -763,5 +813,11 @@ _PyTestCapi_Init_Run(PyObject *mod)
763813
if (PyModule_AddFunctions(mod, test_methods) < 0) {
764814
return -1;
765815
}
816+
if (PyModule_AddIntMacro(mod, PyCF_ONLY_AST) < 0) {
817+
return -1;
818+
}
819+
if (PyModule_AddIntMacro(mod, PyCF_IGNORE_COOKIE) < 0) {
820+
return -1;
821+
}
766822
return 0;
767823
}

Modules/_testlimitedcapi.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,8 @@ PyInit__testlimitedcapi(void)
101101
if (_PyTestLimitedCAPI_Init_Weakref(mod) < 0) {
102102
return NULL;
103103
}
104+
if (_PyTestLimitedCAPI_Init_Run(mod) < 0) {
105+
return NULL;
106+
}
104107
return mod;
105108
}

Modules/_testlimitedcapi/parts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module);
4646
int _PyTestLimitedCAPI_Init_Version(PyObject *module);
4747
int _PyTestLimitedCAPI_Init_File(PyObject *module);
4848
int _PyTestLimitedCAPI_Init_Weakref(PyObject *module);
49+
int _PyTestLimitedCAPI_Init_Run(PyObject *module);
4950

5051
#endif // Py_TESTLIMITEDCAPI_PARTS_H

Modules/_testlimitedcapi/run.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include "parts.h"
2+
#include "util.h"
3+
4+
5+
// Test functions, not macros
6+
#undef Py_CompileString
7+
8+
9+
// Test Py_CompileString()
10+
static PyObject*
11+
run_compilestring(PyObject *mod, PyObject *args)
12+
{
13+
const char *str;
14+
const char *filename;
15+
int start;
16+
17+
if (!PyArg_ParseTuple(args, "yyi", &str, &filename, &start)) {
18+
return NULL;
19+
}
20+
21+
return Py_CompileString(str, filename, start);
22+
}
23+
24+
25+
static PyMethodDef test_methods[] = {
26+
{"run_compilestring", run_compilestring, METH_VARARGS},
27+
{NULL},
28+
};
29+
30+
int
31+
_PyTestLimitedCAPI_Init_Run(PyObject *m)
32+
{
33+
return PyModule_AddFunctions(m, test_methods);
34+
}
35+

0 commit comments

Comments
 (0)