Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c81fa2b

Browse filesBrowse files
gh-132775: Add _PyCode_GetScriptXIData() (gh-133480)
This converts functions, code, str, bytes, bytearray, and memoryview objects to PyCodeObject, and ensure that the object looks like a script. That means no args, no return, and no closure. _PyCode_GetPureScriptXIData() takes it a step further and ensures there are no globals. We also add _PyObject_SupportedAsScript() to the internal C-API.
1 parent f0f93ba commit c81fa2b
Copy full SHA for c81fa2b

File tree

Expand file treeCollapse file tree

8 files changed

+366
-0
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+366
-0
lines changed

‎Include/internal/pycore_crossinterp.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_crossinterp.h
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,14 @@ 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 *);
198+
PyAPI_FUNC(int) _PyCode_GetPureScriptXIData(
199+
PyThreadState *,
200+
PyObject *,
201+
_PyXIData_t *);
194202

195203

196204
/* using cross-interpreter data */

‎Include/internal/pycore_pythonrun.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_pythonrun.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ extern int _PyRun_InteractiveLoopObject(
2525
PyObject *filename,
2626
PyCompilerFlags *flags);
2727

28+
extern int _PyObject_SupportedAsScript(PyObject *);
2829
extern const char* _Py_SourceAsString(
2930
PyObject *cmd,
3031
const char *funcname,

‎Lib/test/_code_definitions.py

Copy file name to clipboardExpand all lines: Lib/test/_code_definitions.py
+53Lines changed: 53 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,26 @@ 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+
PURE_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+
SCRIPT_FUNCTIONS = [
255+
*PURE_SCRIPT_FUNCTIONS,
256+
script_with_globals,
257+
spam_with_globals_and_builtins,
258+
]
259+
207260

208261
# generators
209262

‎Lib/test/test_code.py

Copy file name to clipboardExpand all lines: Lib/test/test_code.py
+27Lines 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

Copy file name to clipboardExpand all lines: Lib/test/test_crossinterp.py
+120Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,126 @@ def test_other_objects(self):
758758
])
759759

760760

761+
class PureShareableScriptTests(_GetXIDataTests):
762+
763+
MODE = 'script-pure'
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', # IndentationError
786+
'----', # SyntaxError
787+
"""if True:
788+
def spam():
789+
# no body
790+
spam()
791+
""", # IndentationError
792+
]
793+
794+
def test_valid_str(self):
795+
self.assert_roundtrip_not_equal([
796+
*self.VALID_SCRIPTS,
797+
], expecttype=types.CodeType)
798+
799+
def test_invalid_str(self):
800+
self.assert_not_shareable([
801+
*self.INVALID_SCRIPTS,
802+
])
803+
804+
def test_valid_bytes(self):
805+
self.assert_roundtrip_not_equal([
806+
*(s.encode('utf8') for s in self.VALID_SCRIPTS),
807+
], expecttype=types.CodeType)
808+
809+
def test_invalid_bytes(self):
810+
self.assert_not_shareable([
811+
*(s.encode('utf8') for s in self.INVALID_SCRIPTS),
812+
])
813+
814+
def test_pure_script_code(self):
815+
self.assert_roundtrip_equal_not_identical([
816+
*(f.__code__ for f in defs.PURE_SCRIPT_FUNCTIONS),
817+
])
818+
819+
def test_impure_script_code(self):
820+
self.assert_not_shareable([
821+
*(f.__code__ for f in defs.SCRIPT_FUNCTIONS
822+
if f not in defs.PURE_SCRIPT_FUNCTIONS),
823+
])
824+
825+
def test_other_code(self):
826+
self.assert_not_shareable([
827+
*(f.__code__ for f in defs.FUNCTIONS
828+
if f not in defs.SCRIPT_FUNCTIONS),
829+
*(f.__code__ for f in defs.FUNCTION_LIKE),
830+
])
831+
832+
def test_pure_script_function(self):
833+
self.assert_roundtrip_not_equal([
834+
*defs.PURE_SCRIPT_FUNCTIONS,
835+
], expecttype=types.CodeType)
836+
837+
def test_impure_script_function(self):
838+
self.assert_not_shareable([
839+
*(f for f in defs.SCRIPT_FUNCTIONS
840+
if f not in defs.PURE_SCRIPT_FUNCTIONS),
841+
])
842+
843+
def test_other_function(self):
844+
self.assert_not_shareable([
845+
*(f for f in defs.FUNCTIONS
846+
if f not in defs.SCRIPT_FUNCTIONS),
847+
*defs.FUNCTION_LIKE,
848+
])
849+
850+
def test_other_objects(self):
851+
self.assert_not_shareable([
852+
None,
853+
True,
854+
False,
855+
Ellipsis,
856+
NotImplemented,
857+
(),
858+
[],
859+
{},
860+
object(),
861+
])
862+
863+
864+
class ShareableScriptTests(PureShareableScriptTests):
865+
866+
MODE = 'script'
867+
868+
def test_impure_script_code(self):
869+
self.assert_roundtrip_equal_not_identical([
870+
*(f.__code__ for f in defs.SCRIPT_FUNCTIONS
871+
if f not in defs.PURE_SCRIPT_FUNCTIONS),
872+
])
873+
874+
def test_impure_script_function(self):
875+
self.assert_roundtrip_not_equal([
876+
*(f for f in defs.SCRIPT_FUNCTIONS
877+
if f not in defs.PURE_SCRIPT_FUNCTIONS),
878+
], expecttype=types.CodeType)
879+
880+
761881
class ShareableTypeTests(_GetXIDataTests):
762882

763883
MODE = 'xidata'

‎Modules/_testinternalcapi.c

Copy file name to clipboardExpand all lines: Modules/_testinternalcapi.c
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,16 @@ 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+
}
1997+
else if (strcmp(mode, "script-pure") == 0) {
1998+
if (_PyCode_GetPureScriptXIData(tstate, obj, xidata) != 0) {
1999+
goto error;
2000+
}
2001+
}
19922002
else {
19932003
PyErr_Format(PyExc_ValueError, "unsupported mode %R", modeobj);
19942004
goto error;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.