Skip to content

[3.14] gh-132775: Add _PyCode_GetScriptXIData() (gh-133480) #133676

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 8 additions & 0 deletions Include/internal/pycore_crossinterp.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ PyAPI_FUNC(int) _PyCode_GetXIData(
PyThreadState *,
PyObject *,
_PyXIData_t *);
PyAPI_FUNC(int) _PyCode_GetScriptXIData(
PyThreadState *,
PyObject *,
_PyXIData_t *);
PyAPI_FUNC(int) _PyCode_GetPureScriptXIData(
PyThreadState *,
PyObject *,
_PyXIData_t *);


/* using cross-interpreter data */
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_pythonrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ extern int _PyRun_InteractiveLoopObject(
PyObject *filename,
PyCompilerFlags *flags);

extern int _PyObject_SupportedAsScript(PyObject *);
extern const char* _Py_SourceAsString(
PyObject *cmd,
const char *funcname,
Expand Down
53 changes: 53 additions & 0 deletions Lib/test/_code_definitions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@

def simple_script():
assert True


def complex_script():
obj = 'a string'
pickle = __import__('pickle')
def spam_minimal():
pass
spam_minimal()
data = pickle.dumps(obj)
res = pickle.loads(data)
assert res == obj, (res, obj)


def script_with_globals():
obj1, obj2 = spam(42)
assert obj1 == 42
assert obj2 is None


def script_with_explicit_empty_return():
return None


def script_with_return():
return True


def spam_minimal():
# no arg defaults or kwarg defaults
Expand Down Expand Up @@ -141,6 +169,11 @@ def ham_C_closure(z):

TOP_FUNCTIONS = [
# shallow
simple_script,
complex_script,
script_with_globals,
script_with_explicit_empty_return,
script_with_return,
spam_minimal,
spam_with_builtins,
spam_with_globals_and_builtins,
Expand Down Expand Up @@ -179,6 +212,10 @@ def ham_C_closure(z):
]

STATELESS_FUNCTIONS = [
simple_script,
complex_script,
script_with_explicit_empty_return,
script_with_return,
spam,
spam_minimal,
spam_with_builtins,
Expand All @@ -200,10 +237,26 @@ def ham_C_closure(z):
]
STATELESS_CODE = [
*STATELESS_FUNCTIONS,
script_with_globals,
spam_with_globals_and_builtins,
spam_full,
]

PURE_SCRIPT_FUNCTIONS = [
simple_script,
complex_script,
script_with_explicit_empty_return,
spam_minimal,
spam_with_builtins,
spam_with_inner_not_closure,
spam_with_inner_closure,
]
SCRIPT_FUNCTIONS = [
*PURE_SCRIPT_FUNCTIONS,
script_with_globals,
spam_with_globals_and_builtins,
]


# generators

Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,20 @@ def test_local_kinds(self):
VARKWARGS = CO_FAST_LOCAL | CO_FAST_ARG_VAR | CO_FAST_ARG_KW

funcs = {
defs.simple_script: {},
defs.complex_script: {
'obj': CO_FAST_LOCAL,
'pickle': CO_FAST_LOCAL,
'spam_minimal': CO_FAST_LOCAL,
'data': CO_FAST_LOCAL,
'res': CO_FAST_LOCAL,
},
defs.script_with_globals: {
'obj1': CO_FAST_LOCAL,
'obj2': CO_FAST_LOCAL,
},
defs.script_with_explicit_empty_return: {},
defs.script_with_return: {},
defs.spam_minimal: {},
defs.spam_with_builtins: {
'x': CO_FAST_LOCAL,
Expand Down Expand Up @@ -898,6 +912,19 @@ def new_var_counts(*,
}

funcs = {
defs.simple_script: new_var_counts(),
defs.complex_script: new_var_counts(
purelocals=5,
globalvars=1,
attrs=2,
),
defs.script_with_globals: new_var_counts(
purelocals=2,
globalvars=1,
),
defs.script_with_explicit_empty_return: new_var_counts(),
defs.script_with_return: new_var_counts(),
defs.spam_minimal: new_var_counts(),
defs.spam_minimal: new_var_counts(),
defs.spam_with_builtins: new_var_counts(
purelocals=4,
Expand Down
120 changes: 120 additions & 0 deletions Lib/test/test_crossinterp.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,126 @@ def test_other_objects(self):
])


class PureShareableScriptTests(_GetXIDataTests):

MODE = 'script-pure'

VALID_SCRIPTS = [
'',
'spam',
'# a comment',
'print("spam")',
'raise Exception("spam")',
"""if True:
do_something()
""",
"""if True:
def spam(x):
return x
class Spam:
def eggs(self):
return 42
x = Spam().eggs()
raise ValueError(spam(x))
""",
]
INVALID_SCRIPTS = [
' pass', # IndentationError
'----', # SyntaxError
"""if True:
def spam():
# no body
spam()
""", # IndentationError
]

def test_valid_str(self):
self.assert_roundtrip_not_equal([
*self.VALID_SCRIPTS,
], expecttype=types.CodeType)

def test_invalid_str(self):
self.assert_not_shareable([
*self.INVALID_SCRIPTS,
])

def test_valid_bytes(self):
self.assert_roundtrip_not_equal([
*(s.encode('utf8') for s in self.VALID_SCRIPTS),
], expecttype=types.CodeType)

def test_invalid_bytes(self):
self.assert_not_shareable([
*(s.encode('utf8') for s in self.INVALID_SCRIPTS),
])

def test_pure_script_code(self):
self.assert_roundtrip_equal_not_identical([
*(f.__code__ for f in defs.PURE_SCRIPT_FUNCTIONS),
])

def test_impure_script_code(self):
self.assert_not_shareable([
*(f.__code__ for f in defs.SCRIPT_FUNCTIONS
if f not in defs.PURE_SCRIPT_FUNCTIONS),
])

def test_other_code(self):
self.assert_not_shareable([
*(f.__code__ for f in defs.FUNCTIONS
if f not in defs.SCRIPT_FUNCTIONS),
*(f.__code__ for f in defs.FUNCTION_LIKE),
])

def test_pure_script_function(self):
self.assert_roundtrip_not_equal([
*defs.PURE_SCRIPT_FUNCTIONS,
], expecttype=types.CodeType)

def test_impure_script_function(self):
self.assert_not_shareable([
*(f for f in defs.SCRIPT_FUNCTIONS
if f not in defs.PURE_SCRIPT_FUNCTIONS),
])

def test_other_function(self):
self.assert_not_shareable([
*(f for f in defs.FUNCTIONS
if f not in defs.SCRIPT_FUNCTIONS),
*defs.FUNCTION_LIKE,
])

def test_other_objects(self):
self.assert_not_shareable([
None,
True,
False,
Ellipsis,
NotImplemented,
(),
[],
{},
object(),
])


class ShareableScriptTests(PureShareableScriptTests):

MODE = 'script'

def test_impure_script_code(self):
self.assert_roundtrip_equal_not_identical([
*(f.__code__ for f in defs.SCRIPT_FUNCTIONS
if f not in defs.PURE_SCRIPT_FUNCTIONS),
])

def test_impure_script_function(self):
self.assert_roundtrip_not_equal([
*(f for f in defs.SCRIPT_FUNCTIONS
if f not in defs.PURE_SCRIPT_FUNCTIONS),
], expecttype=types.CodeType)


class ShareableTypeTests(_GetXIDataTests):

MODE = 'xidata'
Expand Down
10 changes: 10 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1989,6 +1989,16 @@ get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs)
goto error;
}
}
else if (strcmp(mode, "script") == 0) {
if (_PyCode_GetScriptXIData(tstate, obj, xidata) != 0) {
goto error;
}
}
else if (strcmp(mode, "script-pure") == 0) {
if (_PyCode_GetPureScriptXIData(tstate, obj, xidata) != 0) {
goto error;
}
}
else {
PyErr_Format(PyExc_ValueError, "unsupported mode %R", modeobj);
goto error;
Expand Down
Loading
Loading