8000 Add _PyCode_GetScriptXIData(). · python/cpython@49c4abb · GitHub
[go: up one dir, main page]

Skip to content

Commit 49c4abb

Browse files
Add _PyCode_GetScriptXIData().
1 parent ea59873 commit 49c4abb

File tree

6 files changed

+274
-0
lines changed

6 files changed

+274
-0
lines changed

Include/internal/pycore_crossinterp.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ PyAPI_FUNC(int) _PyCode_GetXIData(
191191
PyThreadState *,
192192
PyObject *,
193193
_PyXIData_t *);
194+
PyAPI_FUNC(int) _PyCode_GetScriptXIData(
195+
PyThreadState *,
196+
PyObject *,
197+
_PyXIData_t *);
194198

195199

196200
/* using cross-interpreter data */

Lib/test/_code_definitions.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,32 @@
11

2+
def simple_script():
3+
assert True
4+
5+
6+
def complex_script():
7+
obj = 'a string'
8+
pickle = __import__('pickle')
9+
def spam_minimal():
10+
pass
11+
spam_minimal()
12+
data = pickle.dumps(obj)
13+
res = pickle.loads(data)
14+
assert res == obj, (res, obj)
15+
16+
17+
def script_with_globals():
18+
obj1, obj2 = spam(42)
19+
assert obj1 == 42
20+
assert obj2 is None
21+
22+
23+
def script_with_explicit_empty_return():
24+
return None
25+
26+
27+
def script_with_return():
28+
return True
29+
230

331
def spam_minimal():
432
# no arg defaults or kwarg defaults
@@ -141,6 +169,11 @@ def ham_C_closure(z):
141169

142170
TOP_FUNCTIONS = [
143171
# shallow
172+
simple_script,
173+
complex_script,
174+
script_with_globals,
175+
script_with_explicit_empty_return,
176+
script_with_return,
144177
spam_minimal,
145178
spam_with_builtins,
146179
spam_with_globals_and_builtins,
@@ -179,6 +212,10 @@ def ham_C_closure(z):
179212
]
180213

181214
STATELESS_FUNCTIONS = [
215+
simple_script,
216+
complex_script,
217+
script_with_explicit_empty_return,
218+
script_with_return,
182219
spam,
183220
spam_minimal,
184221
spam_with_builtins,
@@ -200,10 +237,21 @@ def ham_C_closure(z):
200237
]
201238
STATELESS_CODE = [
202239
*STATELESS_FUNCTIONS,
240+
script_with_globals,
203241
spam_with_globals_and_builtins,
204242
spam_full,
205243
]
206244

245+
SCRIPT_FUNCTIONS = [
246+
simple_script,
247+
complex_script,
248+
script_with_explicit_empty_return,
249+
spam_minimal,
250+
spam_with_builtins,
251+
spam_with_inner_not_closure,
252+
spam_with_inner_closure,
253+
]
254+
207255

208256
# generators
209257

Lib/test/test_code.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,20 @@ def test_local_kinds(self):
673673
VARKWARGS = CO_FAST_LOCAL | CO_FAST_ARG_VAR | CO_FAST_ARG_KW
674674

675675
funcs = {
676+
defs.simple_script: {},
677+
defs.complex_script: {
678+
'obj': CO_FAST_LOCAL,
679+
'pickle': CO_FAST_LOCAL,
680+
'spam_minimal': CO_FAST_LOCAL,
681+
'data': CO_FAST_LOCAL,
682+
'res': CO_FAST_LOCAL,
683+
},
684+
defs.script_with_globals: {
685+
'obj1': CO_FAST_LOCAL,
686+
'obj2': CO_FAST_LOCAL,
687+
},
688+
defs.script_with_explicit_empty_return: {},
689+
defs.script_with_return: {},
676690
defs.spam_minimal: {},
677691
defs.spam_with_builtins: {
678692
'x': CO_FAST_LOCAL,
@@ -898,6 +912,19 @@ def new_var_counts(*,
898912
}
899913

900914
funcs = {
915+
defs.simple_script: new_var_counts(),
916+
defs.complex_script: new_var_counts(
917+
purelocals=5,
918+
globalvars=1,
919+
attrs=2,
920+
),
921+
defs.script_with_globals: new_var_counts(
922+
purelocals=2,
923+
globalvars=1,
924+
),
925+
defs.script_with_explicit_empty_return: new_var_counts(),
926+
defs.script_with_return: new_var_counts(),
927+
defs.spam_minimal: new_var_counts(),
901928
defs.spam_minimal: new_var_counts(),
902929
defs.spam_with_builtins: new_var_counts(
903930
purelocals=4,

Lib/test/test_crossinterp.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,97 @@ def test_other_objects(self):
758758
])
759759

760760

761+
class ShareableScriptTests(_GetXIDataTests):
762+
763+
MODE = 'script'
764+
765+
VALID_SCRIPTS = [
766+
'',
767+
'spam',
768+
'# a comment',
769+
'print("spam")',
770+
'raise Exception("spam")',
771+
"""if True:
772+
do_something()
773+
""",
774+
"""if True:
775+
def spam(x):
776+
return x
777+
class Spam:
778+
def eggs(self):
779+
return 42
780+
x = Spam().eggs()
781+
raise ValueError(spam(x))
782+
""",
783+
]
784+
INVALID_SCRIPTS = [
785+
' pass',
786+
'----',
787+
"""if True:
788+
# do something
789+
""",
790+
]
791+
792+
def test_valid_str(self):
793+
self.assert_roundtrip_not_equal([
794+
*self.VALID_SCRIPTS,
795+
], expecttype=types.CodeType)
796+
797+
def test_invalid_str(self):
798+
self.assert_not_shareable([
799+
*self.INVALID_SCRIPTS,
800+
])
801+
802+
def test_valid_bytes(self):
803+
self.assert_roundtrip_not_equal([
804+
*(s.encode('utf8') for s in self.VALID_SCRIPTS),
805+
], expecttype=types.CodeType)
806+
807+
def test_invalid_bytes(self):
808+
self.assert_not_shareable([
809+
*(s.encode('utf8') for s in self.INVALID_SCRIPTS),
810+
])
811+
812+
def test_script_code(self):
813+
self.assert_roundtrip_equal_not_identical([
814+
*(f.__code__ for f in defs.SCRIPT_FUNCTIONS),
815+
defs.script_with_globals.__code__,
816+
])
817+
818+
def test_other_code(self):
819+
self.assert_not_shareable([
820+
*(f.__code__ for f in defs.FUNCTIONS
821+
if f not in defs.SCRIPT_FUNCTIONS and
822+
f is not defs.script_with_globals),
823+
*(f.__code__ for f in defs.FUNCTION_LIKE),
824+
])
825+
826+
def test_script_function(self):
827+
self.assert_roundtrip_not_equal([
828+
*defs.SCRIPT_FUNCTIONS,
829+
], expecttype=types.CodeType)
830+
831+
def test_other_function(self):
832+
self.assert_not_shareable([
833+
*(f for f in defs.FUNCTIONS
834+
if f not in defs.SCRIPT_FUNCTIONS),
835+
*defs.FUNCTION_LIKE,
836+
])
837+
838+
def test_other_objects(self):
839+
self.assert_not_shareable([
840+
None,
841+
True,
842+
False,
843+
Ellipsis,
844+
NotImplemented,
845+
(),
846+
[],
847+
{},
848+
object(),
849+
])
850+
851+
761852
class ShareableTypeTests(_GetXIDataTests):
762853

763854
MODE = 'xidata'

Modules/_testinternalcapi.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,11 @@ get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs)
19891989
goto error;
19901990
}
19911991
}
1992+
else if (strcmp(mode, "script") == 0) {
1993+
if (_PyCode_GetScriptXIData(tstate, obj, xidata) != 0) {
1994+
goto error;
1995+
}
1996+
}
19921997
else {
19931998
PyErr_Format(PyExc_ValueError, "unsupported mode %R", modeobj);
19941999
goto error;

Python/crossinterp.c

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
#include "osdefs.h" // MAXPATHLEN
77
#include "pycore_ceval.h" // _Py_simple_func
88
#include "pycore_crossinterp.h" // _PyXIData_t
9+
#include "pycore_function.h" // _PyFunction_VerifyStateless()
910
#include "pycore_initconfig.h" // _PyStatus_OK()
1011
#include "pycore_namespace.h" // _PyNamespace_New()
12+
#include "pycore_pythonrun.h" // _Py_SourceAsString()
1113
#include "pycore_typeobject.h" // _PyStaticType_InitBuiltin()
1214

1315

@@ -784,6 +786,103 @@ _PyMarshal_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
784786
}
785787

786788

789+
/* script wrapper */
790+
791+
static int
792+
verify_script(PyThreadState *tstate, PyCodeObject *co)
793+
{
794+
assert(_PyCode_VerifyStateless(
795+
tstate, co, NULL, NULL, _PyEval_GetBuiltins(tstate)) == 0);
796+
if (co->co_argcount > 0
797+
|| co->co_posonlyargcount > 0
798+
|| co->co_kwonlyargcount > 0
799+
|| co->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
800+
{
801+
_PyErr_SetString(tstate, PyExc_ValueError,
802+
"code with args not supported");
803+
return -1;
804+
}
805+
if (!_PyCode_ReturnsOnlyNone(co)) {
806+
_PyErr_SetString(tstate, PyExc_ValueError,
807+
"code that returns a value is not a script");
808+
return -1;
809+
}
810+
return 0;
811+
}
812+
813+
int
814+
_PyCode_GetScriptXIData(PyThreadState *tstate,
815+
PyObject *obj, _PyXIData_t *xidata)
816+
{
817+
// Get the corresponding code object.
818+
PyObject *code = NULL;
819+
if (PyCode_Check(obj)) {
820+
code = obj;
821+
Py_INCREF(code);
822+
PyObject *builtins = _PyEval_GetBuiltins(tstate);
823+
assert(builtins != NULL);
824+
if (_PyCode_VerifyStateless(
825+
tstate, (PyCodeObject *)code, NULL, NULL, builtins) < 0)
826+
{
827+
goto error;
828+
}
829+
if (verify_script(tstate, (PyCodeObject *)code) < 0) {
830+
goto error;
831+
}
832+
}
833+
else if (PyFunction_Check(obj)) {
834+
code = PyFunction_GET_CODE(obj);
835+
assert(code != NULL);
836+
Py_INCREF(code);
837+
if (_PyFunction_VerifyStateless(tstate, obj) < 0) {
838+
goto error;
839+
}
840+
if (verify_script(tstate, (PyCodeObject *)code) < 0) {
841+
goto error;
842+
}
843+
}
844+
else {
845+
const char *filename = "<script>";
846+
PyCompilerFlags cf = _PyCompilerFlags_INIT;
847+
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
848+
PyObject *ref = NULL;
849+
const char *script =
850+
_Py_SourceAsString(obj, "???", "???", &cf, &ref);
851+
if (script == NULL) {
852+
if (!PyBytes_Check(obj) && !PyUnicode_Check(obj)
853+
&& !PyByteArray_Check(obj) && !PyObject_CheckBuffer(obj))
854+
{
855+
// We discard the raised exception.
856+
_PyErr_Format(tstate, PyExc_TypeError, "unsupported script %R", obj);
857+
}
858+
goto error;
859+
}
860+
code = Py_CompileStringExFlags(script, filename, Py_file_input, &cf, 0);
861+
Py_XDECREF(ref);
862+
if (code == NULL) {
863+
goto error;
864+
}
865+
}
866+
867+
// Convert the code object.
868+
int res = _PyCode_GetXIData(tstate, code, xidata);
869+
Py_DECREF(code);
870+
if (res < 0) {
871+
return -1;
872+
}
873+
return 0;
874+
875+
error:
876+
Py_XDECREF(code);
877+
PyObject *cause = _PyErr_GetRaisedException(tstate);
878+
assert(cause != NULL);
879+
_set_xid_lookup_failure(
880+
tstate, NULL, "object not a valid script", cause);
881+
Py_DECREF(cause);
882+
return -1;
883+
}
884+
885+
787886
/* using cross-interpreter data */
788887

789888
PyObject *

0 commit comments

Comments
 (0)
0