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

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.

(cherry picked from commit c81fa2b9cd, AKA gh-133480)

Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
This commit is contained in:
Miss Islington (bot) 2025-05-08 18:05:34 +02:00 committed by GitHub
parent dc1a4dda88
commit 1059548686
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 366 additions and 0 deletions

View file

@ -6,8 +6,10 @@
#include "osdefs.h" // MAXPATHLEN
#include "pycore_ceval.h" // _Py_simple_func
#include "pycore_crossinterp.h" // _PyXIData_t
#include "pycore_function.h" // _PyFunction_VerifyStateless()
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_namespace.h" // _PyNamespace_New()
#include "pycore_pythonrun.h" // _Py_SourceAsString()
#include "pycore_typeobject.h" // _PyStaticType_InitBuiltin()
@ -784,6 +786,131 @@ _PyMarshal_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
}
/* script wrapper */
static int
verify_script(PyThreadState *tstate, PyCodeObject *co, int checked, int pure)
{
// Make sure it isn't a closure and (optionally) doesn't use globals.
PyObject *builtins = NULL;
if (pure) {
builtins = _PyEval_GetBuiltins(tstate);
assert(builtins != NULL);
}
if (checked) {
assert(_PyCode_VerifyStateless(tstate, co, NULL, NULL, builtins) == 0);
}
else if (_PyCode_VerifyStateless(tstate, co, NULL, NULL, builtins) < 0) {
return -1;
}
// Make sure it doesn't have args.
if (co->co_argcount > 0
|| co->co_posonlyargcount > 0
|| co->co_kwonlyargcount > 0
|| co->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
{
_PyErr_SetString(tstate, PyExc_ValueError,
"code with args not supported");
return -1;
}
// Make sure it doesn't return anything.
if (!_PyCode_ReturnsOnlyNone(co)) {
_PyErr_SetString(tstate, PyExc_ValueError,
"code that returns a value is not a script");
return -1;
}
return 0;
}
static int
get_script_xidata(PyThreadState *tstate, PyObject *obj, int pure,
_PyXIData_t *xidata)
{
// Get the corresponding code object.
PyObject *code = NULL;
int checked = 0;
if (PyCode_Check(obj)) {
code = obj;
Py_INCREF(code);
}
else if (PyFunction_Check(obj)) {
code = PyFunction_GET_CODE(obj);
assert(code != NULL);
Py_INCREF(code);
if (pure) {
if (_PyFunction_VerifyStateless(tstate, obj) < 0) {
goto error;
}
checked = 1;
}
}
else {
const char *filename = "<script>";
int optimize = 0;
PyCompilerFlags cf = _PyCompilerFlags_INIT;
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
PyObject *ref = NULL;
const char *script = _Py_SourceAsString(obj, "???", "???", &cf, &ref);
if (script == NULL) {
if (!_PyObject_SupportedAsScript(obj)) {
// We discard the raised exception.
_PyErr_Format(tstate, PyExc_TypeError,
"unsupported script %R", obj);
}
goto error;
}
code = Py_CompileStringExFlags(
script, filename, Py_file_input, &cf, optimize);
Py_XDECREF(ref);
if (code == NULL) {
goto error;
}
// Compiled text can't have args or any return statements,
// nor be a closure. It can use globals though.
if (!pure) {
// We don't need to check for globals either.
checked = 1;
}
}
// Make sure it's actually a script.
if (verify_script(tstate, (PyCodeObject *)code, checked, pure) < 0) {
goto error;
}
// Convert the code object.
int res = _PyCode_GetXIData(tstate, code, xidata);
Py_DECREF(code);
if (res < 0) {
return -1;
}
return 0;
error:
Py_XDECREF(code);
PyObject *cause = _PyErr_GetRaisedException(tstate);
assert(cause != NULL);
_set_xid_lookup_failure(
tstate, NULL, "object not a valid script", cause);
Py_DECREF(cause);
return -1;
}
int
_PyCode_GetScriptXIData(PyThreadState *tstate,
PyObject *obj, _PyXIData_t *xidata)
{
return get_script_xidata(tstate, obj, 0, xidata);
}
int
_PyCode_GetPureScriptXIData(PyThreadState *tstate,
PyObject *obj, _PyXIData_t *xidata)
{
return get_script_xidata(tstate, obj, 1, xidata);
}
/* using cross-interpreter data */
PyObject *

View file

@ -1524,6 +1524,26 @@ Py_CompileStringExFlags(const char *str, const char *filename_str, int start,
return co;
}
int
_PyObject_SupportedAsScript(PyObject *cmd)
{
if (PyUnicode_Check(cmd)) {
return 1;
}
else if (PyBytes_Check(cmd)) {
return 1;
}
else if (PyByteArray_Check(cmd)) {
return 1;
}
else if (PyObject_CheckBuffer(cmd)) {
return 1;
}
else {
return 0;
}
}
const char *
_Py_SourceAsString(PyObject *cmd, const char *funcname, const char *what, PyCompilerFlags *cf, PyObject **cmd_copy)
{