mirror of https://github.com/python/cpython.git
3116 lines
84 KiB
C
3116 lines
84 KiB
C
|
|
/* API for managing interactions between isolated interpreters */
|
|
|
|
#include "Python.h"
|
|
#include "marshal.h" // PyMarshal_WriteObjectToString()
|
|
#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_setobject.h" // _PySet_NextEntry()
|
|
#include "pycore_typeobject.h" // _PyStaticType_InitBuiltin()
|
|
|
|
|
|
static Py_ssize_t
|
|
_Py_GetMainfile(char *buffer, size_t maxlen)
|
|
{
|
|
// We don't expect subinterpreters to have the __main__ module's
|
|
// __name__ set, but proceed just in case.
|
|
PyThreadState *tstate = _PyThreadState_GET();
|
|
PyObject *module = _Py_GetMainModule(tstate);
|
|
if (_Py_CheckMainModule(module) < 0) {
|
|
return -1;
|
|
}
|
|
Py_ssize_t size = _PyModule_GetFilenameUTF8(module, buffer, maxlen);
|
|
Py_DECREF(module);
|
|
return size;
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
import_get_module(PyThreadState *tstate, const char *modname)
|
|
{
|
|
PyObject *module = NULL;
|
|
if (strcmp(modname, "__main__") == 0) {
|
|
module = _Py_GetMainModule(tstate);
|
|
if (_Py_CheckMainModule(module) < 0) {
|
|
assert(_PyErr_Occurred(tstate));
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
module = PyImport_ImportModule(modname);
|
|
if (module == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
return module;
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
runpy_run_path(const char *filename, const char *modname)
|
|
{
|
|
PyObject *run_path = PyImport_ImportModuleAttrString("runpy", "run_path");
|
|
if (run_path == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *args = Py_BuildValue("(sOs)", filename, Py_None, modname);
|
|
if (args == NULL) {
|
|
Py_DECREF(run_path);
|
|
return NULL;
|
|
}
|
|
PyObject *ns = PyObject_Call(run_path, args, NULL);
|
|
Py_DECREF(run_path);
|
|
Py_DECREF(args);
|
|
return ns;
|
|
}
|
|
|
|
|
|
static void
|
|
set_exc_with_cause(PyObject *exctype, const char *msg)
|
|
{
|
|
PyObject *cause = PyErr_GetRaisedException();
|
|
PyErr_SetString(exctype, msg);
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
PyException_SetCause(exc, cause);
|
|
PyErr_SetRaisedException(exc);
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
pyerr_get_message(PyObject *exc)
|
|
{
|
|
assert(!PyErr_Occurred());
|
|
PyObject *args = PyException_GetArgs(exc);
|
|
if (args == NULL || args == Py_None || PyObject_Size(args) < 1) {
|
|
return NULL;
|
|
}
|
|
if (PyUnicode_Check(args)) {
|
|
return args;
|
|
}
|
|
PyObject *msg = PySequence_GetItem(args, 0);
|
|
Py_DECREF(args);
|
|
if (msg == NULL) {
|
|
PyErr_Clear();
|
|
return NULL;
|
|
}
|
|
if (!PyUnicode_Check(msg)) {
|
|
Py_DECREF(msg);
|
|
return NULL;
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
#define MAX_MODNAME (255)
|
|
#define MAX_ATTRNAME (255)
|
|
|
|
struct attributeerror_info {
|
|
char modname[MAX_MODNAME+1];
|
|
char attrname[MAX_ATTRNAME+1];
|
|
};
|
|
|
|
static int
|
|
_parse_attributeerror(PyObject *exc, struct attributeerror_info *info)
|
|
{
|
|
assert(exc != NULL);
|
|
assert(PyErr_GivenExceptionMatches(exc, PyExc_AttributeError));
|
|
int res = -1;
|
|
|
|
PyObject *msgobj = pyerr_get_message(exc);
|
|
if (msgobj == NULL) {
|
|
return -1;
|
|
}
|
|
const char *err = PyUnicode_AsUTF8(msgobj);
|
|
|
|
if (strncmp(err, "module '", 8) != 0) {
|
|
goto finally;
|
|
}
|
|
err += 8;
|
|
|
|
const char *matched = strchr(err, '\'');
|
|
if (matched == NULL) {
|
|
goto finally;
|
|
}
|
|
Py_ssize_t len = matched - err;
|
|
if (len > MAX_MODNAME) {
|
|
goto finally;
|
|
}
|
|
(void)strncpy(info->modname, err, len);
|
|
info->modname[len] = '\0';
|
|
err = matched;
|
|
|
|
if (strncmp(err, "' has no attribute '", 20) != 0) {
|
|
goto finally;
|
|
}
|
|
err += 20;
|
|
|
|
matched = strchr(err, '\'');
|
|
if (matched == NULL) {
|
|
goto finally;
|
|
}
|
|
len = matched - err;
|
|
if (len > MAX_ATTRNAME) {
|
|
goto finally;
|
|
}
|
|
(void)strncpy(info->attrname, err, len);
|
|
info->attrname[len] = '\0';
|
|
err = matched + 1;
|
|
|
|
if (strlen(err) > 0) {
|
|
goto finally;
|
|
}
|
|
res = 0;
|
|
|
|
finally:
|
|
Py_DECREF(msgobj);
|
|
return res;
|
|
}
|
|
|
|
#undef MAX_MODNAME
|
|
#undef MAX_ATTRNAME
|
|
|
|
|
|
/**************/
|
|
/* exceptions */
|
|
/**************/
|
|
|
|
typedef struct xi_exceptions exceptions_t;
|
|
static int init_static_exctypes(exceptions_t *, PyInterpreterState *);
|
|
static void fini_static_exctypes(exceptions_t *, PyInterpreterState *);
|
|
static int init_heap_exctypes(exceptions_t *);
|
|
static void fini_heap_exctypes(exceptions_t *);
|
|
#include "crossinterp_exceptions.h"
|
|
|
|
|
|
/***************************/
|
|
/* cross-interpreter calls */
|
|
/***************************/
|
|
|
|
int
|
|
_Py_CallInInterpreter(PyInterpreterState *interp,
|
|
_Py_simple_func func, void *arg)
|
|
{
|
|
if (interp == PyInterpreterState_Get()) {
|
|
return func(arg);
|
|
}
|
|
// XXX Emit a warning if this fails?
|
|
_PyEval_AddPendingCall(interp, (_Py_pending_call_func)func, arg, 0);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
_Py_CallInInterpreterAndRawFree(PyInterpreterState *interp,
|
|
_Py_simple_func func, void *arg)
|
|
{
|
|
if (interp == PyInterpreterState_Get()) {
|
|
int res = func(arg);
|
|
PyMem_RawFree(arg);
|
|
return res;
|
|
}
|
|
// XXX Emit a warning if this fails?
|
|
_PyEval_AddPendingCall(interp, func, arg, _Py_PENDING_RAWFREE);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**************************/
|
|
/* cross-interpreter data */
|
|
/**************************/
|
|
|
|
/* registry of {type -> _PyXIData_getdata_t} */
|
|
|
|
/* For now we use a global registry of shareable classes.
|
|
An alternative would be to add a tp_* slot for a class's
|
|
_PyXIData_getdata_t. It would be simpler and more efficient. */
|
|
|
|
static void xid_lookup_init(_PyXIData_lookup_t *);
|
|
static void xid_lookup_fini(_PyXIData_lookup_t *);
|
|
struct _dlcontext;
|
|
static _PyXIData_getdata_t lookup_getdata(struct _dlcontext *, PyObject *);
|
|
#include "crossinterp_data_lookup.h"
|
|
|
|
|
|
/* lifecycle */
|
|
|
|
_PyXIData_t *
|
|
_PyXIData_New(void)
|
|
{
|
|
_PyXIData_t *xid = PyMem_RawCalloc(1, sizeof(_PyXIData_t));
|
|
if (xid == NULL) {
|
|
PyErr_NoMemory();
|
|
}
|
|
return xid;
|
|
}
|
|
|
|
void
|
|
_PyXIData_Free(_PyXIData_t *xid)
|
|
{
|
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
|
_PyXIData_Clear(interp, xid);
|
|
PyMem_RawFree(xid);
|
|
}
|
|
|
|
|
|
/* defining cross-interpreter data */
|
|
|
|
static inline void
|
|
_xidata_init(_PyXIData_t *xidata)
|
|
{
|
|
// If the value is being reused
|
|
// then _xidata_clear() should have been called already.
|
|
assert(xidata->data == NULL);
|
|
assert(xidata->obj == NULL);
|
|
*xidata = (_PyXIData_t){0};
|
|
_PyXIData_INTERPID(xidata) = -1;
|
|
}
|
|
|
|
static inline void
|
|
_xidata_clear(_PyXIData_t *xidata)
|
|
{
|
|
// _PyXIData_t only has two members that need to be
|
|
// cleaned up, if set: "xidata" must be freed and "obj" must be decref'ed.
|
|
// In both cases the original (owning) interpreter must be used,
|
|
// which is the caller's responsibility to ensure.
|
|
if (xidata->data != NULL) {
|
|
if (xidata->free != NULL) {
|
|
xidata->free(xidata->data);
|
|
}
|
|
xidata->data = NULL;
|
|
}
|
|
Py_CLEAR(xidata->obj);
|
|
}
|
|
|
|
void
|
|
_PyXIData_Init(_PyXIData_t *xidata,
|
|
PyInterpreterState *interp,
|
|
void *shared, PyObject *obj,
|
|
xid_newobjfunc new_object)
|
|
{
|
|
assert(xidata != NULL);
|
|
assert(new_object != NULL);
|
|
_xidata_init(xidata);
|
|
xidata->data = shared;
|
|
if (obj != NULL) {
|
|
assert(interp != NULL);
|
|
// released in _PyXIData_Clear()
|
|
xidata->obj = Py_NewRef(obj);
|
|
}
|
|
// Ideally every object would know its owning interpreter.
|
|
// Until then, we have to rely on the caller to identify it
|
|
// (but we don't need it in all cases).
|
|
_PyXIData_INTERPID(xidata) = (interp != NULL)
|
|
? PyInterpreterState_GetID(interp)
|
|
: -1;
|
|
xidata->new_object = new_object;
|
|
}
|
|
|
|
int
|
|
_PyXIData_InitWithSize(_PyXIData_t *xidata,
|
|
PyInterpreterState *interp,
|
|
const size_t size, PyObject *obj,
|
|
xid_newobjfunc new_object)
|
|
{
|
|
assert(size > 0);
|
|
// For now we always free the shared data in the same interpreter
|
|
// where it was allocated, so the interpreter is required.
|
|
assert(interp != NULL);
|
|
_PyXIData_Init(xidata, interp, NULL, obj, new_object);
|
|
xidata->data = PyMem_RawCalloc(1, size);
|
|
if (xidata->data == NULL) {
|
|
return -1;
|
|
}
|
|
xidata->free = PyMem_RawFree;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
_PyXIData_Clear(PyInterpreterState *interp, _PyXIData_t *xidata)
|
|
{
|
|
assert(xidata != NULL);
|
|
// This must be called in the owning interpreter.
|
|
assert(interp == NULL
|
|
|| _PyXIData_INTERPID(xidata) == -1
|
|
|| _PyXIData_INTERPID(xidata) == PyInterpreterState_GetID(interp));
|
|
_xidata_clear(xidata);
|
|
}
|
|
|
|
|
|
/* getting cross-interpreter data */
|
|
|
|
static inline void
|
|
_set_xid_lookup_failure(PyThreadState *tstate, PyObject *obj, const char *msg,
|
|
PyObject *cause)
|
|
{
|
|
if (msg != NULL) {
|
|
assert(obj == NULL);
|
|
set_notshareableerror(tstate, cause, 0, msg);
|
|
}
|
|
else if (obj == NULL) {
|
|
msg = "object does not support cross-interpreter data";
|
|
set_notshareableerror(tstate, cause, 0, msg);
|
|
}
|
|
else {
|
|
msg = "%R does not support cross-interpreter data";
|
|
format_notshareableerror(tstate, cause, 0, msg, obj);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
_PyObject_CheckXIData(PyThreadState *tstate, PyObject *obj)
|
|
{
|
|
dlcontext_t ctx;
|
|
if (get_lookup_context(tstate, &ctx) < 0) {
|
|
return -1;
|
|
}
|
|
_PyXIData_getdata_t getdata = lookup_getdata(&ctx, obj);
|
|
if (getdata.basic == NULL && getdata.fallback == NULL) {
|
|
if (!_PyErr_Occurred(tstate)) {
|
|
_set_xid_lookup_failure(tstate, obj, NULL, NULL);
|
|
}
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_check_xidata(PyThreadState *tstate, _PyXIData_t *xidata)
|
|
{
|
|
// xidata->data can be anything, including NULL, so we don't check it.
|
|
|
|
// xidata->obj may be NULL, so we don't check it.
|
|
|
|
if (_PyXIData_INTERPID(xidata) < 0) {
|
|
PyErr_SetString(PyExc_SystemError, "missing interp");
|
|
return -1;
|
|
}
|
|
|
|
if (xidata->new_object == NULL) {
|
|
PyErr_SetString(PyExc_SystemError, "missing new_object func");
|
|
return -1;
|
|
}
|
|
|
|
// xidata->free may be NULL, so we don't check it.
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_get_xidata(PyThreadState *tstate,
|
|
PyObject *obj, xidata_fallback_t fallback, _PyXIData_t *xidata)
|
|
{
|
|
PyInterpreterState *interp = tstate->interp;
|
|
|
|
assert(xidata->data == NULL);
|
|
assert(xidata->obj == NULL);
|
|
if (xidata->data != NULL || xidata->obj != NULL) {
|
|
_PyErr_SetString(tstate, PyExc_ValueError, "xidata not cleared");
|
|
return -1;
|
|
}
|
|
|
|
// Call the "getdata" func for the object.
|
|
dlcontext_t ctx;
|
|
if (get_lookup_context(tstate, &ctx) < 0) {
|
|
return -1;
|
|
}
|
|
Py_INCREF(obj);
|
|
_PyXIData_getdata_t getdata = lookup_getdata(&ctx, obj);
|
|
if (getdata.basic == NULL && getdata.fallback == NULL) {
|
|
if (PyErr_Occurred()) {
|
|
Py_DECREF(obj);
|
|
return -1;
|
|
}
|
|
// Fall back to obj
|
|
Py_DECREF(obj);
|
|
if (!_PyErr_Occurred(tstate)) {
|
|
_set_xid_lookup_failure(tstate, obj, NULL, NULL);
|
|
}
|
|
return -1;
|
|
}
|
|
int res = getdata.basic != NULL
|
|
? getdata.basic(tstate, obj, xidata)
|
|
: getdata.fallback(tstate, obj, fallback, xidata);
|
|
Py_DECREF(obj);
|
|
if (res != 0) {
|
|
PyObject *cause = _PyErr_GetRaisedException(tstate);
|
|
assert(cause != NULL);
|
|
_set_xid_lookup_failure(tstate, obj, NULL, cause);
|
|
Py_XDECREF(cause);
|
|
return -1;
|
|
}
|
|
|
|
// Fill in the blanks and validate the result.
|
|
_PyXIData_INTERPID(xidata) = PyInterpreterState_GetID(interp);
|
|
if (_check_xidata(tstate, xidata) != 0) {
|
|
(void)_PyXIData_Release(xidata);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
_PyObject_GetXIDataNoFallback(PyThreadState *tstate,
|
|
PyObject *obj, _PyXIData_t *xidata)
|
|
{
|
|
return _get_xidata(tstate, obj, _PyXIDATA_XIDATA_ONLY, xidata);
|
|
}
|
|
|
|
int
|
|
_PyObject_GetXIData(PyThreadState *tstate,
|
|
PyObject *obj, xidata_fallback_t fallback,
|
|
_PyXIData_t *xidata)
|
|
{
|
|
switch (fallback) {
|
|
case _PyXIDATA_XIDATA_ONLY:
|
|
return _get_xidata(tstate, obj, fallback, xidata);
|
|
case _PyXIDATA_FULL_FALLBACK:
|
|
if (_get_xidata(tstate, obj, fallback, xidata) == 0) {
|
|
return 0;
|
|
}
|
|
PyObject *exc = _PyErr_GetRaisedException(tstate);
|
|
if (PyFunction_Check(obj)) {
|
|
if (_PyFunction_GetXIData(tstate, obj, xidata) == 0) {
|
|
Py_DECREF(exc);
|
|
return 0;
|
|
}
|
|
_PyErr_Clear(tstate);
|
|
}
|
|
// We could try _PyMarshal_GetXIData() but we won't for now.
|
|
if (_PyPickle_GetXIData(tstate, obj, xidata) == 0) {
|
|
Py_DECREF(exc);
|
|
return 0;
|
|
}
|
|
// Raise the original exception.
|
|
_PyErr_SetRaisedException(tstate, exc);
|
|
return -1;
|
|
default:
|
|
#ifdef Py_DEBUG
|
|
Py_FatalError("unsupported xidata fallback option");
|
|
#endif
|
|
_PyErr_SetString(tstate, PyExc_SystemError,
|
|
"unsupported xidata fallback option");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
/* pickle C-API */
|
|
|
|
struct _pickle_context {
|
|
PyThreadState *tstate;
|
|
};
|
|
|
|
static PyObject *
|
|
_PyPickle_Dumps(struct _pickle_context *ctx, PyObject *obj)
|
|
{
|
|
PyObject *dumps = PyImport_ImportModuleAttrString("pickle", "dumps");
|
|
if (dumps == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *bytes = PyObject_CallOneArg(dumps, obj);
|
|
Py_DECREF(dumps);
|
|
return bytes;
|
|
}
|
|
|
|
|
|
struct sync_module_result {
|
|
PyObject *module;
|
|
PyObject *loaded;
|
|
PyObject *failed;
|
|
};
|
|
|
|
struct sync_module {
|
|
const char *filename;
|
|
char _filename[MAXPATHLEN+1];
|
|
struct sync_module_result cached;
|
|
};
|
|
|
|
static void
|
|
sync_module_clear(struct sync_module *data)
|
|
{
|
|
data->filename = NULL;
|
|
Py_CLEAR(data->cached.module);
|
|
Py_CLEAR(data->cached.loaded);
|
|
Py_CLEAR(data->cached.failed);
|
|
}
|
|
|
|
|
|
struct _unpickle_context {
|
|
PyThreadState *tstate;
|
|
// We only special-case the __main__ module,
|
|
// since other modules behave consistently.
|
|
struct sync_module main;
|
|
};
|
|
|
|
static void
|
|
_unpickle_context_clear(struct _unpickle_context *ctx)
|
|
{
|
|
sync_module_clear(&ctx->main);
|
|
}
|
|
|
|
static struct sync_module_result
|
|
_unpickle_context_get_module(struct _unpickle_context *ctx,
|
|
const char *modname)
|
|
{
|
|
if (strcmp(modname, "__main__") == 0) {
|
|
return ctx->main.cached;
|
|
}
|
|
else {
|
|
return (struct sync_module_result){
|
|
.failed = PyExc_NotImplementedError,
|
|
};
|
|
}
|
|
}
|
|
|
|
static struct sync_module_result
|
|
_unpickle_context_set_module(struct _unpickle_context *ctx,
|
|
const char *modname)
|
|
{
|
|
struct sync_module_result res = {0};
|
|
struct sync_module_result *cached = NULL;
|
|
const char *filename = NULL;
|
|
const char *run_modname = modname;
|
|
if (strcmp(modname, "__main__") == 0) {
|
|
cached = &ctx->main.cached;
|
|
filename = ctx->main.filename;
|
|
// We don't want to trigger "if __name__ == '__main__':".
|
|
run_modname = "<fake __main__>";
|
|
}
|
|
else {
|
|
res.failed = PyExc_NotImplementedError;
|
|
goto finally;
|
|
}
|
|
|
|
res.module = import_get_module(ctx->tstate, modname);
|
|
if (res.module == NULL) {
|
|
res.failed = _PyErr_GetRaisedException(ctx->tstate);
|
|
assert(res.failed != NULL);
|
|
goto finally;
|
|
}
|
|
|
|
if (filename == NULL) {
|
|
Py_CLEAR(res.module);
|
|
res.failed = PyExc_NotImplementedError;
|
|
goto finally;
|
|
}
|
|
res.loaded = runpy_run_path(filename, run_modname);
|
|
if (res.loaded == NULL) {
|
|
Py_CLEAR(res.module);
|
|
res.failed = _PyErr_GetRaisedException(ctx->tstate);
|
|
assert(res.failed != NULL);
|
|
goto finally;
|
|
}
|
|
|
|
finally:
|
|
if (cached != NULL) {
|
|
assert(cached->module == NULL);
|
|
assert(cached->loaded == NULL);
|
|
assert(cached->failed == NULL);
|
|
*cached = res;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
static int
|
|
_handle_unpickle_missing_attr(struct _unpickle_context *ctx, PyObject *exc)
|
|
{
|
|
// The caller must check if an exception is set or not when -1 is returned.
|
|
assert(!_PyErr_Occurred(ctx->tstate));
|
|
assert(PyErr_GivenExceptionMatches(exc, PyExc_AttributeError));
|
|
struct attributeerror_info info;
|
|
if (_parse_attributeerror(exc, &info) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Get the module.
|
|
struct sync_module_result mod = _unpickle_context_get_module(ctx, info.modname);
|
|
if (mod.failed != NULL) {
|
|
// It must have failed previously.
|
|
return -1;
|
|
}
|
|
if (mod.module == NULL) {
|
|
mod = _unpickle_context_set_module(ctx, info.modname);
|
|
if (mod.failed != NULL) {
|
|
return -1;
|
|
}
|
|
assert(mod.module != NULL);
|
|
}
|
|
|
|
// Bail out if it is unexpectedly set already.
|
|
if (PyObject_HasAttrString(mod.module, info.attrname)) {
|
|
return -1;
|
|
}
|
|
|
|
// Try setting the attribute.
|
|
PyObject *value = NULL;
|
|
if (PyDict_GetItemStringRef(mod.loaded, info.attrname, &value) <= 0) {
|
|
return -1;
|
|
}
|
|
assert(value != NULL);
|
|
int res = PyObject_SetAttrString(mod.module, info.attrname, value);
|
|
Py_DECREF(value);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static PyObject *
|
|
_PyPickle_Loads(struct _unpickle_context *ctx, PyObject *pickled)
|
|
{
|
|
PyObject *loads = PyImport_ImportModuleAttrString("pickle", "loads");
|
|
if (loads == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *obj = PyObject_CallOneArg(loads, pickled);
|
|
if (ctx != NULL) {
|
|
while (obj == NULL) {
|
|
assert(_PyErr_Occurred(ctx->tstate));
|
|
if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
|
// We leave other failures unhandled.
|
|
break;
|
|
}
|
|
// Try setting the attr if not set.
|
|
PyObject *exc = _PyErr_GetRaisedException(ctx->tstate);
|
|
if (_handle_unpickle_missing_attr(ctx, exc) < 0) {
|
|
// Any resulting exceptions are ignored
|
|
// in favor of the original.
|
|
_PyErr_SetRaisedException(ctx->tstate, exc);
|
|
break;
|
|
}
|
|
Py_CLEAR(exc);
|
|
// Retry with the attribute set.
|
|
obj = PyObject_CallOneArg(loads, pickled);
|
|
}
|
|
}
|
|
Py_DECREF(loads);
|
|
return obj;
|
|
}
|
|
|
|
|
|
/* pickle wrapper */
|
|
|
|
struct _pickle_xid_context {
|
|
// __main__.__file__
|
|
struct {
|
|
const char *utf8;
|
|
size_t len;
|
|
char _utf8[MAXPATHLEN+1];
|
|
} mainfile;
|
|
};
|
|
|
|
static int
|
|
_set_pickle_xid_context(PyThreadState *tstate, struct _pickle_xid_context *ctx)
|
|
{
|
|
// Set mainfile if possible.
|
|
Py_ssize_t len = _Py_GetMainfile(ctx->mainfile._utf8, MAXPATHLEN);
|
|
if (len < 0) {
|
|
// For now we ignore any exceptions.
|
|
PyErr_Clear();
|
|
}
|
|
else if (len > 0) {
|
|
ctx->mainfile.utf8 = ctx->mainfile._utf8;
|
|
ctx->mainfile.len = (size_t)len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct _shared_pickle_data {
|
|
_PyBytes_data_t pickled; // Must be first if we use _PyBytes_FromXIData().
|
|
struct _pickle_xid_context ctx;
|
|
};
|
|
|
|
PyObject *
|
|
_PyPickle_LoadFromXIData(_PyXIData_t *xidata)
|
|
{
|
|
PyThreadState *tstate = _PyThreadState_GET();
|
|
struct _shared_pickle_data *shared =
|
|
(struct _shared_pickle_data *)xidata->data;
|
|
// We avoid copying the pickled data by wrapping it in a memoryview.
|
|
// The alternative is to get a bytes object using _PyBytes_FromXIData().
|
|
PyObject *pickled = PyMemoryView_FromMemory(
|
|
(char *)shared->pickled.bytes, shared->pickled.len, PyBUF_READ);
|
|
if (pickled == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// Unpickle the object.
|
|
struct _unpickle_context ctx = {
|
|
.tstate = tstate,
|
|
.main = {
|
|
.filename = shared->ctx.mainfile.utf8,
|
|
},
|
|
};
|
|
PyObject *obj = _PyPickle_Loads(&ctx, pickled);
|
|
Py_DECREF(pickled);
|
|
_unpickle_context_clear(&ctx);
|
|
if (obj == NULL) {
|
|
PyObject *cause = _PyErr_GetRaisedException(tstate);
|
|
assert(cause != NULL);
|
|
_set_xid_lookup_failure(
|
|
tstate, NULL, "object could not be unpickled", cause);
|
|
Py_DECREF(cause);
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
|
|
int
|
|
_PyPickle_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
|
|
{
|
|
// Pickle the object.
|
|
struct _pickle_context ctx = {
|
|
.tstate = tstate,
|
|
};
|
|
PyObject *bytes = _PyPickle_Dumps(&ctx, obj);
|
|
if (bytes == NULL) {
|
|
PyObject *cause = _PyErr_GetRaisedException(tstate);
|
|
assert(cause != NULL);
|
|
_set_xid_lookup_failure(
|
|
tstate, NULL, "object could not be pickled", cause);
|
|
Py_DECREF(cause);
|
|
return -1;
|
|
}
|
|
|
|
// If we had an "unwrapper" mechnanism, we could call
|
|
// _PyObject_GetXIData() on the bytes object directly and add
|
|
// a simple unwrapper to call pickle.loads() on the bytes.
|
|
size_t size = sizeof(struct _shared_pickle_data);
|
|
struct _shared_pickle_data *shared =
|
|
(struct _shared_pickle_data *)_PyBytes_GetXIDataWrapped(
|
|
tstate, bytes, size, _PyPickle_LoadFromXIData, xidata);
|
|
Py_DECREF(bytes);
|
|
if (shared == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// If it mattered, we could skip getting __main__.__file__
|
|
// when "__main__" doesn't show up in the pickle bytes.
|
|
if (_set_pickle_xid_context(tstate, &shared->ctx) < 0) {
|
|
_xidata_clear(xidata);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* marshal wrapper */
|
|
|
|
PyObject *
|
|
_PyMarshal_ReadObjectFromXIData(_PyXIData_t *xidata)
|
|
{
|
|
PyThreadState *tstate = _PyThreadState_GET();
|
|
_PyBytes_data_t *shared = (_PyBytes_data_t *)xidata->data;
|
|
PyObject *obj = PyMarshal_ReadObjectFromString(shared->bytes, shared->len);
|
|
if (obj == NULL) {
|
|
PyObject *cause = _PyErr_GetRaisedException(tstate);
|
|
assert(cause != NULL);
|
|
_set_xid_lookup_failure(
|
|
tstate, NULL, "object could not be unmarshalled", cause);
|
|
Py_DECREF(cause);
|
|
return NULL;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
int
|
|
_PyMarshal_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
|
|
{
|
|
PyObject *bytes = PyMarshal_WriteObjectToString(obj, Py_MARSHAL_VERSION);
|
|
if (bytes == NULL) {
|
|
PyObject *cause = _PyErr_GetRaisedException(tstate);
|
|
assert(cause != NULL);
|
|
_set_xid_lookup_failure(
|
|
tstate, NULL, "object could not be marshalled", cause);
|
|
Py_DECREF(cause);
|
|
return -1;
|
|
}
|
|
size_t size = sizeof(_PyBytes_data_t);
|
|
_PyBytes_data_t *shared = _PyBytes_GetXIDataWrapped(
|
|
tstate, bytes, size, _PyMarshal_ReadObjectFromXIData, xidata);
|
|
Py_DECREF(bytes);
|
|
if (shared == NULL) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|
|
#ifdef Py_GIL_DISABLED
|
|
// Don't immortalize code constants to avoid memory leaks.
|
|
((_PyThreadStateImpl *)tstate)->suppress_co_const_immortalization++;
|
|
#endif
|
|
code = Py_CompileStringExFlags(
|
|
script, filename, Py_file_input, &cf, optimize);
|
|
#ifdef Py_GIL_DISABLED
|
|
((_PyThreadStateImpl *)tstate)->suppress_co_const_immortalization--;
|
|
#endif
|
|
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 *
|
|
_PyXIData_NewObject(_PyXIData_t *xidata)
|
|
{
|
|
return xidata->new_object(xidata);
|
|
}
|
|
|
|
static int
|
|
_call_clear_xidata(void *data)
|
|
{
|
|
_xidata_clear((_PyXIData_t *)data);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_xidata_release(_PyXIData_t *xidata, int rawfree)
|
|
{
|
|
if ((xidata->data == NULL || xidata->free == NULL) && xidata->obj == NULL) {
|
|
// Nothing to release!
|
|
if (rawfree) {
|
|
PyMem_RawFree(xidata);
|
|
}
|
|
else {
|
|
xidata->data = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Switch to the original interpreter.
|
|
PyInterpreterState *interp = _PyInterpreterState_LookUpID(
|
|
_PyXIData_INTERPID(xidata));
|
|
if (interp == NULL) {
|
|
// The interpreter was already destroyed.
|
|
// This function shouldn't have been called.
|
|
// XXX Someone leaked some memory...
|
|
assert(PyErr_Occurred());
|
|
if (rawfree) {
|
|
PyMem_RawFree(xidata);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// "Release" the data and/or the object.
|
|
if (rawfree) {
|
|
return _Py_CallInInterpreterAndRawFree(interp, _call_clear_xidata, xidata);
|
|
}
|
|
else {
|
|
return _Py_CallInInterpreter(interp, _call_clear_xidata, xidata);
|
|
}
|
|
}
|
|
|
|
int
|
|
_PyXIData_Release(_PyXIData_t *xidata)
|
|
{
|
|
return _xidata_release(xidata, 0);
|
|
}
|
|
|
|
int
|
|
_PyXIData_ReleaseAndRawFree(_PyXIData_t *xidata)
|
|
{
|
|
return _xidata_release(xidata, 1);
|
|
}
|
|
|
|
|
|
/*************************/
|
|
/* convenience utilities */
|
|
/*************************/
|
|
|
|
static const char *
|
|
_copy_string_obj_raw(PyObject *strobj, Py_ssize_t *p_size)
|
|
{
|
|
Py_ssize_t size = -1;
|
|
const char *str = PyUnicode_AsUTF8AndSize(strobj, &size);
|
|
if (str == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (size != (Py_ssize_t)strlen(str)) {
|
|
PyErr_SetString(PyExc_ValueError, "found embedded NULL character");
|
|
return NULL;
|
|
}
|
|
|
|
char *copied = PyMem_RawMalloc(size+1);
|
|
if (copied == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
strcpy(copied, str);
|
|
if (p_size != NULL) {
|
|
*p_size = size;
|
|
}
|
|
return copied;
|
|
}
|
|
|
|
|
|
static int
|
|
_convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc)
|
|
{
|
|
PyObject *args = NULL;
|
|
PyObject *kwargs = NULL;
|
|
PyObject *create = NULL;
|
|
|
|
// This is inspired by _PyErr_Display().
|
|
PyObject *tbexc_type = PyImport_ImportModuleAttrString(
|
|
"traceback",
|
|
"TracebackException");
|
|
if (tbexc_type == NULL) {
|
|
return -1;
|
|
}
|
|
create = PyObject_GetAttrString(tbexc_type, "from_exception");
|
|
Py_DECREF(tbexc_type);
|
|
if (create == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
args = PyTuple_Pack(1, exc);
|
|
if (args == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
kwargs = PyDict_New();
|
|
if (kwargs == NULL) {
|
|
goto error;
|
|
}
|
|
if (PyDict_SetItemString(kwargs, "save_exc_type", Py_False) < 0) {
|
|
goto error;
|
|
}
|
|
if (PyDict_SetItemString(kwargs, "lookup_lines", Py_False) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
PyObject *tbexc = PyObject_Call(create, args, kwargs);
|
|
Py_DECREF(args);
|
|
Py_DECREF(kwargs);
|
|
Py_DECREF(create);
|
|
if (tbexc == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
*p_tbexc = tbexc;
|
|
return 0;
|
|
|
|
error:
|
|
Py_XDECREF(args);
|
|
Py_XDECREF(kwargs);
|
|
Py_XDECREF(create);
|
|
return -1;
|
|
}
|
|
|
|
// We accommodate backports here.
|
|
#ifndef _Py_EMPTY_STR
|
|
# define _Py_EMPTY_STR &_Py_STR(empty)
|
|
#endif
|
|
|
|
static const char *
|
|
_format_TracebackException(PyObject *tbexc)
|
|
{
|
|
PyObject *lines = PyObject_CallMethod(tbexc, "format", NULL);
|
|
if (lines == NULL) {
|
|
return NULL;
|
|
}
|
|
assert(_Py_EMPTY_STR != NULL);
|
|
PyObject *formatted_obj = PyUnicode_Join(_Py_EMPTY_STR, lines);
|
|
Py_DECREF(lines);
|
|
if (formatted_obj == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Py_ssize_t size = -1;
|
|
const char *formatted = _copy_string_obj_raw(formatted_obj, &size);
|
|
Py_DECREF(formatted_obj);
|
|
// We remove trailing the newline added by TracebackException.format().
|
|
assert(formatted[size-1] == '\n');
|
|
((char *)formatted)[size-1] = '\0';
|
|
return formatted;
|
|
}
|
|
|
|
|
|
static int
|
|
_release_xid_data(_PyXIData_t *xidata, int rawfree)
|
|
{
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
int res = rawfree
|
|
? _PyXIData_Release(xidata)
|
|
: _PyXIData_ReleaseAndRawFree(xidata);
|
|
if (res < 0) {
|
|
/* The owning interpreter is already destroyed. */
|
|
_PyXIData_Clear(NULL, xidata);
|
|
// XXX Emit a warning?
|
|
PyErr_Clear();
|
|
}
|
|
PyErr_SetRaisedException(exc);
|
|
return res;
|
|
}
|
|
|
|
|
|
/***********************/
|
|
/* exception snapshots */
|
|
/***********************/
|
|
|
|
static int
|
|
_excinfo_init_type_from_exception(struct _excinfo_type *info, PyObject *exc)
|
|
{
|
|
/* Note that this copies directly rather than into an intermediate
|
|
struct and does not clear on error. If we need that then we
|
|
should have a separate function to wrap this one
|
|
and do all that there. */
|
|
PyObject *strobj = NULL;
|
|
|
|
PyTypeObject *type = Py_TYPE(exc);
|
|
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
|
assert(_Py_IsImmortal((PyObject *)type));
|
|
info->builtin = type;
|
|
}
|
|
else {
|
|
// Only builtin types are preserved.
|
|
info->builtin = NULL;
|
|
}
|
|
|
|
// __name__
|
|
strobj = PyType_GetName(type);
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->name = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->name == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// __qualname__
|
|
strobj = PyType_GetQualName(type);
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->qualname = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->qualname == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// __module__
|
|
strobj = PyType_GetModuleName(type);
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->module = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->module == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_excinfo_init_type_from_object(struct _excinfo_type *info, PyObject *exctype)
|
|
{
|
|
PyObject *strobj = NULL;
|
|
|
|
// __name__
|
|
strobj = PyObject_GetAttrString(exctype, "__name__");
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->name = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->name == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// __qualname__
|
|
strobj = PyObject_GetAttrString(exctype, "__qualname__");
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->qualname = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->qualname == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// __module__
|
|
strobj = PyObject_GetAttrString(exctype, "__module__");
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->module = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->module == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_excinfo_clear_type(struct _excinfo_type *info)
|
|
{
|
|
if (info->builtin != NULL) {
|
|
assert(info->builtin->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN);
|
|
assert(_Py_IsImmortal((PyObject *)info->builtin));
|
|
}
|
|
if (info->name != NULL) {
|
|
PyMem_RawFree((void *)info->name);
|
|
}
|
|
if (info->qualname != NULL) {
|
|
PyMem_RawFree((void *)info->qualname);
|
|
}
|
|
if (info->module != NULL) {
|
|
PyMem_RawFree((void *)info->module);
|
|
}
|
|
*info = (struct _excinfo_type){NULL};
|
|
}
|
|
|
|
static void
|
|
_excinfo_normalize_type(struct _excinfo_type *info,
|
|
const char **p_module, const char **p_qualname)
|
|
{
|
|
if (info->name == NULL) {
|
|
assert(info->builtin == NULL);
|
|
assert(info->qualname == NULL);
|
|
assert(info->module == NULL);
|
|
// This is inspired by TracebackException.format_exception_only().
|
|
*p_module = NULL;
|
|
*p_qualname = NULL;
|
|
return;
|
|
}
|
|
|
|
const char *module = info->module;
|
|
const char *qualname = info->qualname;
|
|
if (qualname == NULL) {
|
|
qualname = info->name;
|
|
}
|
|
assert(module != NULL);
|
|
if (strcmp(module, "builtins") == 0) {
|
|
module = NULL;
|
|
}
|
|
else if (strcmp(module, "__main__") == 0) {
|
|
module = NULL;
|
|
}
|
|
*p_qualname = qualname;
|
|
*p_module = module;
|
|
}
|
|
|
|
static void
|
|
_PyXI_excinfo_clear(_PyXI_excinfo *info)
|
|
{
|
|
_excinfo_clear_type(&info->type);
|
|
if (info->msg != NULL) {
|
|
PyMem_RawFree((void *)info->msg);
|
|
}
|
|
if (info->errdisplay != NULL) {
|
|
PyMem_RawFree((void *)info->errdisplay);
|
|
}
|
|
*info = (_PyXI_excinfo){{NULL}};
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_excinfo_format(_PyXI_excinfo *info)
|
|
{
|
|
const char *module, *qualname;
|
|
_excinfo_normalize_type(&info->type, &module, &qualname);
|
|
if (qualname != NULL) {
|
|
if (module != NULL) {
|
|
if (info->msg != NULL) {
|
|
return PyUnicode_FromFormat("%s.%s: %s",
|
|
module, qualname, info->msg);
|
|
}
|
|
else {
|
|
return PyUnicode_FromFormat("%s.%s", module, qualname);
|
|
}
|
|
}
|
|
else {
|
|
if (info->msg != NULL) {
|
|
return PyUnicode_FromFormat("%s: %s", qualname, info->msg);
|
|
}
|
|
else {
|
|
return PyUnicode_FromString(qualname);
|
|
}
|
|
}
|
|
}
|
|
else if (info->msg != NULL) {
|
|
return PyUnicode_FromString(info->msg);
|
|
}
|
|
else {
|
|
Py_RETURN_NONE;
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
_PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
|
|
{
|
|
assert(exc != NULL);
|
|
|
|
if (PyErr_GivenExceptionMatches(exc, PyExc_MemoryError)) {
|
|
_PyXI_excinfo_clear(info);
|
|
return NULL;
|
|
}
|
|
const char *failure = NULL;
|
|
|
|
if (_excinfo_init_type_from_exception(&info->type, exc) < 0) {
|
|
failure = "error while initializing exception type snapshot";
|
|
goto error;
|
|
}
|
|
|
|
// Extract the exception message.
|
|
PyObject *msgobj = PyObject_Str(exc);
|
|
if (msgobj == NULL) {
|
|
failure = "error while formatting exception";
|
|
goto error;
|
|
}
|
|
info->msg = _copy_string_obj_raw(msgobj, NULL);
|
|
Py_DECREF(msgobj);
|
|
if (info->msg == NULL) {
|
|
failure = "error while copying exception message";
|
|
goto error;
|
|
}
|
|
|
|
// Pickle a traceback.TracebackException.
|
|
PyObject *tbexc = NULL;
|
|
if (_convert_exc_to_TracebackException(exc, &tbexc) < 0) {
|
|
#ifdef Py_DEBUG
|
|
PyErr_FormatUnraisable("Exception ignored while creating TracebackException");
|
|
#endif
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
info->errdisplay = _format_TracebackException(tbexc);
|
|
Py_DECREF(tbexc);
|
|
if (info->errdisplay == NULL) {
|
|
#ifdef Py_DEBUG
|
|
PyErr_FormatUnraisable("Exception ignored while formatting TracebackException");
|
|
#endif
|
|
PyErr_Clear();
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
|
|
error:
|
|
assert(failure != NULL);
|
|
_PyXI_excinfo_clear(info);
|
|
return failure;
|
|
}
|
|
|
|
static const char *
|
|
_PyXI_excinfo_InitFromObject(_PyXI_excinfo *info, PyObject *obj)
|
|
{
|
|
const char *failure = NULL;
|
|
|
|
PyObject *exctype = PyObject_GetAttrString(obj, "type");
|
|
if (exctype == NULL) {
|
|
failure = "exception snapshot missing 'type' attribute";
|
|
goto error;
|
|
}
|
|
int res = _excinfo_init_type_from_object(&info->type, exctype);
|
|
Py_DECREF(exctype);
|
|
if (res < 0) {
|
|
failure = "error while initializing exception type snapshot";
|
|
goto error;
|
|
}
|
|
|
|
// Extract the exception message.
|
|
PyObject *msgobj = PyObject_GetAttrString(obj, "msg");
|
|
if (msgobj == NULL) {
|
|
failure = "exception snapshot missing 'msg' attribute";
|
|
goto error;
|
|
}
|
|
info->msg = _copy_string_obj_raw(msgobj, NULL);
|
|
Py_DECREF(msgobj);
|
|
if (info->msg == NULL) {
|
|
failure = "error while copying exception message";
|
|
goto error;
|
|
}
|
|
|
|
// Pickle a traceback.TracebackException.
|
|
PyObject *errdisplay = PyObject_GetAttrString(obj, "errdisplay");
|
|
if (errdisplay == NULL) {
|
|
failure = "exception snapshot missing 'errdisplay' attribute";
|
|
goto error;
|
|
}
|
|
info->errdisplay = _copy_string_obj_raw(errdisplay, NULL);
|
|
Py_DECREF(errdisplay);
|
|
if (info->errdisplay == NULL) {
|
|
failure = "error while copying exception error display";
|
|
goto error;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
error:
|
|
assert(failure != NULL);
|
|
_PyXI_excinfo_clear(info);
|
|
return failure;
|
|
}
|
|
|
|
static void
|
|
_PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
|
|
{
|
|
PyObject *tbexc = NULL;
|
|
if (info->errdisplay != NULL) {
|
|
tbexc = PyUnicode_FromString(info->errdisplay);
|
|
if (tbexc == NULL) {
|
|
PyErr_Clear();
|
|
}
|
|
}
|
|
|
|
PyObject *formatted = _PyXI_excinfo_format(info);
|
|
PyErr_SetObject(exctype, formatted);
|
|
Py_DECREF(formatted);
|
|
|
|
if (tbexc != NULL) {
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
if (PyObject_SetAttrString(exc, "_errdisplay", tbexc) < 0) {
|
|
#ifdef Py_DEBUG
|
|
PyErr_FormatUnraisable("Exception ignored while "
|
|
"setting _errdisplay");
|
|
#endif
|
|
PyErr_Clear();
|
|
}
|
|
Py_DECREF(tbexc);
|
|
PyErr_SetRaisedException(exc);
|
|
}
|
|
}
|
|
|
|
static PyObject *
|
|
_PyXI_excinfo_TypeAsObject(_PyXI_excinfo *info)
|
|
{
|
|
PyObject *ns = _PyNamespace_New(NULL);
|
|
if (ns == NULL) {
|
|
return NULL;
|
|
}
|
|
int empty = 1;
|
|
|
|
if (info->type.name != NULL) {
|
|
PyObject *name = PyUnicode_FromString(info->type.name);
|
|
if (name == NULL) {
|
|
goto error;
|
|
}
|
|
int res = PyObject_SetAttrString(ns, "__name__", name);
|
|
Py_DECREF(name);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
empty = 0;
|
|
}
|
|
|
|
if (info->type.qualname != NULL) {
|
|
PyObject *qualname = PyUnicode_FromString(info->type.qualname);
|
|
if (qualname == NULL) {
|
|
goto error;
|
|
}
|
|
int res = PyObject_SetAttrString(ns, "__qualname__", qualname);
|
|
Py_DECREF(qualname);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
empty = 0;
|
|
}
|
|
|
|
if (info->type.module != NULL) {
|
|
PyObject *module = PyUnicode_FromString(info->type.module);
|
|
if (module == NULL) {
|
|
goto error;
|
|
}
|
|
int res = PyObject_SetAttrString(ns, "__module__", module);
|
|
Py_DECREF(module);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
empty = 0;
|
|
}
|
|
|
|
if (empty) {
|
|
Py_CLEAR(ns);
|
|
}
|
|
|
|
return ns;
|
|
|
|
error:
|
|
Py_DECREF(ns);
|
|
return NULL;
|
|
}
|
|
|
|
static PyObject *
|
|
_PyXI_excinfo_AsObject(_PyXI_excinfo *info)
|
|
{
|
|
PyObject *ns = _PyNamespace_New(NULL);
|
|
if (ns == NULL) {
|
|
return NULL;
|
|
}
|
|
int res;
|
|
|
|
PyObject *type = _PyXI_excinfo_TypeAsObject(info);
|
|
if (type == NULL) {
|
|
if (PyErr_Occurred()) {
|
|
goto error;
|
|
}
|
|
type = Py_NewRef(Py_None);
|
|
}
|
|
res = PyObject_SetAttrString(ns, "type", type);
|
|
Py_DECREF(type);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
|
|
PyObject *msg = info->msg != NULL
|
|
? PyUnicode_FromString(info->msg)
|
|
: Py_NewRef(Py_None);
|
|
if (msg == NULL) {
|
|
goto error;
|
|
}
|
|
res = PyObject_SetAttrString(ns, "msg", msg);
|
|
Py_DECREF(msg);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
|
|
PyObject *formatted = _PyXI_excinfo_format(info);
|
|
if (formatted == NULL) {
|
|
goto error;
|
|
}
|
|
res = PyObject_SetAttrString(ns, "formatted", formatted);
|
|
Py_DECREF(formatted);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (info->errdisplay != NULL) {
|
|
PyObject *tbexc = PyUnicode_FromString(info->errdisplay);
|
|
if (tbexc == NULL) {
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
res = PyObject_SetAttrString(ns, "errdisplay", tbexc);
|
|
Py_DECREF(tbexc);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ns;
|
|
|
|
error:
|
|
Py_DECREF(ns);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int
|
|
_PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc)
|
|
{
|
|
assert(!PyErr_Occurred());
|
|
if (exc == NULL || exc == Py_None) {
|
|
PyErr_SetString(PyExc_ValueError, "missing exc");
|
|
return -1;
|
|
}
|
|
const char *failure;
|
|
if (PyExceptionInstance_Check(exc) || PyExceptionClass_Check(exc)) {
|
|
failure = _PyXI_excinfo_InitFromException(info, exc);
|
|
}
|
|
else {
|
|
failure = _PyXI_excinfo_InitFromObject(info, exc);
|
|
}
|
|
if (failure != NULL) {
|
|
PyErr_SetString(PyExc_Exception, failure);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_FormatExcInfo(_PyXI_excinfo *info)
|
|
{
|
|
return _PyXI_excinfo_format(info);
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_ExcInfoAsObject(_PyXI_excinfo *info)
|
|
{
|
|
return _PyXI_excinfo_AsObject(info);
|
|
}
|
|
|
|
void
|
|
_PyXI_ClearExcInfo(_PyXI_excinfo *info)
|
|
{
|
|
_PyXI_excinfo_clear(info);
|
|
}
|
|
|
|
|
|
/***************************/
|
|
/* short-term data sharing */
|
|
/***************************/
|
|
|
|
/* error codes */
|
|
|
|
static int
|
|
_PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
|
|
{
|
|
PyThreadState *tstate = _PyThreadState_GET();
|
|
|
|
assert(!PyErr_Occurred());
|
|
assert(code != _PyXI_ERR_NO_ERROR);
|
|
assert(code != _PyXI_ERR_UNCAUGHT_EXCEPTION);
|
|
switch (code) {
|
|
case _PyXI_ERR_OTHER:
|
|
// XXX msg?
|
|
PyErr_SetNone(PyExc_InterpreterError);
|
|
break;
|
|
case _PyXI_ERR_NO_MEMORY:
|
|
PyErr_NoMemory();
|
|
break;
|
|
case _PyXI_ERR_ALREADY_RUNNING:
|
|
assert(interp != NULL);
|
|
_PyErr_SetInterpreterAlreadyRunning();
|
|
break;
|
|
case _PyXI_ERR_MAIN_NS_FAILURE:
|
|
PyErr_SetString(PyExc_InterpreterError,
|
|
"failed to get __main__ namespace");
|
|
break;
|
|
case _PyXI_ERR_APPLY_NS_FAILURE:
|
|
PyErr_SetString(PyExc_InterpreterError,
|
|
"failed to apply namespace to __main__");
|
|
break;
|
|
case _PyXI_ERR_PRESERVE_FAILURE:
|
|
PyErr_SetString(PyExc_InterpreterError,
|
|
"failed to preserve objects across session");
|
|
break;
|
|
case _PyXI_ERR_EXC_PROPAGATION_FAILURE:
|
|
PyErr_SetString(PyExc_InterpreterError,
|
|
"failed to transfer exception between interpreters");
|
|
break;
|
|
case _PyXI_ERR_NOT_SHAREABLE:
|
|
_set_xid_lookup_failure(tstate, NULL, NULL, NULL);
|
|
break;
|
|
default:
|
|
#ifdef Py_DEBUG
|
|
Py_FatalError("unsupported error code");
|
|
#else
|
|
PyErr_Format(PyExc_RuntimeError, "unsupported error code %d", code);
|
|
#endif
|
|
}
|
|
assert(PyErr_Occurred());
|
|
return -1;
|
|
}
|
|
|
|
/* shared exceptions */
|
|
|
|
static const char *
|
|
_PyXI_InitError(_PyXI_error *error, PyObject *excobj, _PyXI_errcode code)
|
|
{
|
|
if (error->interp == NULL) {
|
|
error->interp = PyInterpreterState_Get();
|
|
}
|
|
|
|
const char *failure = NULL;
|
|
if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
|
|
// There is an unhandled exception we need to propagate.
|
|
failure = _PyXI_excinfo_InitFromException(&error->uncaught, excobj);
|
|
if (failure != NULL) {
|
|
// We failed to initialize error->uncaught.
|
|
// XXX Print the excobj/traceback? Emit a warning?
|
|
// XXX Print the current exception/traceback?
|
|
if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
|
|
error->code = _PyXI_ERR_NO_MEMORY;
|
|
}
|
|
else {
|
|
error->code = _PyXI_ERR_OTHER;
|
|
}
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
error->code = code;
|
|
}
|
|
assert(error->code != _PyXI_ERR_NO_ERROR);
|
|
}
|
|
else {
|
|
// There is an error code we need to propagate.
|
|
assert(excobj == NULL);
|
|
assert(code != _PyXI_ERR_NO_ERROR);
|
|
error->code = code;
|
|
_PyXI_excinfo_clear(&error->uncaught);
|
|
}
|
|
return failure;
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_ApplyError(_PyXI_error *error)
|
|
{
|
|
PyThreadState *tstate = PyThreadState_Get();
|
|
if (error->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
|
|
// We will raise an exception that proxies the propagated exception.
|
|
return _PyXI_excinfo_AsObject(&error->uncaught);
|
|
}
|
|
else if (error->code == _PyXI_ERR_NOT_SHAREABLE) {
|
|
// Propagate the exception directly.
|
|
assert(!_PyErr_Occurred(tstate));
|
|
_set_xid_lookup_failure(tstate, NULL, error->uncaught.msg, NULL);
|
|
}
|
|
else {
|
|
// Raise an exception corresponding to the code.
|
|
assert(error->code != _PyXI_ERR_NO_ERROR);
|
|
(void)_PyXI_ApplyErrorCode(error->code, error->interp);
|
|
if (error->uncaught.type.name != NULL || error->uncaught.msg != NULL) {
|
|
// __context__ will be set to a proxy of the propagated exception.
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
_PyXI_excinfo_Apply(&error->uncaught, PyExc_InterpreterError);
|
|
PyObject *exc2 = PyErr_GetRaisedException();
|
|
PyException_SetContext(exc, exc2);
|
|
PyErr_SetRaisedException(exc);
|
|
}
|
|
}
|
|
assert(PyErr_Occurred());
|
|
return NULL;
|
|
}
|
|
|
|
/* shared namespaces */
|
|
|
|
/* Shared namespaces are expected to have relatively short lifetimes.
|
|
This means dealloc of a shared namespace will normally happen "soon".
|
|
Namespace items hold cross-interpreter data, which must get released.
|
|
If the namespace/items are cleared in a different interpreter than
|
|
where the items' cross-interpreter data was set then that will cause
|
|
pending calls to be used to release the cross-interpreter data.
|
|
The tricky bit is that the pending calls can happen sufficiently
|
|
later that the namespace/items might already be deallocated. This is
|
|
a problem if the cross-interpreter data is allocated as part of a
|
|
namespace item. If that's the case then we must ensure the shared
|
|
namespace is only cleared/freed *after* that data has been released. */
|
|
|
|
typedef struct _sharednsitem {
|
|
const char *name;
|
|
_PyXIData_t *xidata;
|
|
// We could have a "PyXIData _data" field, so it would
|
|
// be allocated as part of the item and avoid an extra allocation.
|
|
// However, doing so adds a bunch of complexity because we must
|
|
// ensure the item isn't freed before a pending call might happen
|
|
// in a different interpreter to release the XI data.
|
|
} _PyXI_namespace_item;
|
|
|
|
#ifndef NDEBUG
|
|
static int
|
|
_sharednsitem_is_initialized(_PyXI_namespace_item *item)
|
|
{
|
|
if (item->name != NULL) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
_sharednsitem_init(_PyXI_namespace_item *item, PyObject *key)
|
|
{
|
|
item->name = _copy_string_obj_raw(key, NULL);
|
|
if (item->name == NULL) {
|
|
assert(!_sharednsitem_is_initialized(item));
|
|
return -1;
|
|
}
|
|
item->xidata = NULL;
|
|
assert(_sharednsitem_is_initialized(item));
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid)
|
|
{
|
|
if (item->xidata == NULL) {
|
|
return 0;
|
|
}
|
|
if (p_interpid != NULL) {
|
|
*p_interpid = _PyXIData_INTERPID(item->xidata);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value,
|
|
xidata_fallback_t fallback)
|
|
{
|
|
assert(_sharednsitem_is_initialized(item));
|
|
assert(item->xidata == NULL);
|
|
item->xidata = _PyXIData_New();
|
|
if (item->xidata == NULL) {
|
|
return -1;
|
|
}
|
|
PyThreadState *tstate = PyThreadState_Get();
|
|
if (_PyObject_GetXIData(tstate, value, fallback, item->xidata) < 0) {
|
|
PyMem_RawFree(item->xidata);
|
|
item->xidata = NULL;
|
|
// The caller may want to propagate PyExc_NotShareableError
|
|
// if currently switched between interpreters.
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_sharednsitem_clear_value(_PyXI_namespace_item *item)
|
|
{
|
|
_PyXIData_t *xidata = item->xidata;
|
|
if (xidata != NULL) {
|
|
item->xidata = NULL;
|
|
int rawfree = 1;
|
|
(void)_release_xid_data(xidata, rawfree);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_sharednsitem_clear(_PyXI_namespace_item *item)
|
|
{
|
|
if (item->name != NULL) {
|
|
PyMem_RawFree((void *)item->name);
|
|
item->name = NULL;
|
|
}
|
|
_sharednsitem_clear_value(item);
|
|
}
|
|
|
|
static int
|
|
_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns,
|
|
xidata_fallback_t fallback)
|
|
{
|
|
assert(item->name != NULL);
|
|
assert(item->xidata == NULL);
|
|
PyObject *value = PyDict_GetItemString(ns, item->name); // borrowed
|
|
if (value == NULL) {
|
|
if (PyErr_Occurred()) {
|
|
return -1;
|
|
}
|
|
// When applied, this item will be set to the default (or fail).
|
|
return 0;
|
|
}
|
|
if (_sharednsitem_set_value(item, value, fallback) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
|
|
{
|
|
PyObject *name = PyUnicode_FromString(item->name);
|
|
if (name == NULL) {
|
|
return -1;
|
|
}
|
|
PyObject *value;
|
|
if (item->xidata != NULL) {
|
|
value = _PyXIData_NewObject(item->xidata);
|
|
if (value == NULL) {
|
|
Py_DECREF(name);
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
value = Py_NewRef(dflt);
|
|
}
|
|
int res = PyDict_SetItem(ns, name, value);
|
|
Py_DECREF(name);
|
|
Py_DECREF(value);
|
|
return res;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
Py_ssize_t maxitems;
|
|
Py_ssize_t numnames;
|
|
Py_ssize_t numvalues;
|
|
_PyXI_namespace_item items[1];
|
|
} _PyXI_namespace;
|
|
|
|
#ifndef NDEBUG
|
|
static int
|
|
_sharedns_check_counts(_PyXI_namespace *ns)
|
|
{
|
|
if (ns->maxitems <= 0) {
|
|
return 0;
|
|
}
|
|
if (ns->numnames < 0) {
|
|
return 0;
|
|
}
|
|
if (ns->numnames > ns->maxitems) {
|
|
return 0;
|
|
}
|
|
if (ns->numvalues < 0) {
|
|
return 0;
|
|
}
|
|
if (ns->numvalues > ns->numnames) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
_sharedns_check_consistency(_PyXI_namespace *ns)
|
|
{
|
|
if (!_sharedns_check_counts(ns)) {
|
|
return 0;
|
|
}
|
|
|
|
Py_ssize_t i = 0;
|
|
_PyXI_namespace_item *item;
|
|
if (ns->numvalues > 0) {
|
|
item = &ns->items[0];
|
|
if (!_sharednsitem_is_initialized(item)) {
|
|
return 0;
|
|
}
|
|
int64_t interpid0 = -1;
|
|
if (!_sharednsitem_has_value(item, &interpid0)) {
|
|
return 0;
|
|
}
|
|
i += 1;
|
|
for (; i < ns->numvalues; i++) {
|
|
item = &ns->items[i];
|
|
if (!_sharednsitem_is_initialized(item)) {
|
|
return 0;
|
|
}
|
|
int64_t interpid = -1;
|
|
if (!_sharednsitem_has_value(item, &interpid)) {
|
|
return 0;
|
|
}
|
|
if (interpid != interpid0) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
for (; i < ns->numnames; i++) {
|
|
item = &ns->items[i];
|
|
if (!_sharednsitem_is_initialized(item)) {
|
|
return 0;
|
|
}
|
|
if (_sharednsitem_has_value(item, NULL)) {
|
|
return 0;
|
|
}
|
|
}
|
|
for (; i < ns->maxitems; i++) {
|
|
item = &ns->items[i];
|
|
if (_sharednsitem_is_initialized(item)) {
|
|
return 0;
|
|
}
|
|
if (_sharednsitem_has_value(item, NULL)) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
static _PyXI_namespace *
|
|
_sharedns_alloc(Py_ssize_t maxitems)
|
|
{
|
|
if (maxitems < 0) {
|
|
if (!PyErr_Occurred()) {
|
|
PyErr_BadInternalCall();
|
|
}
|
|
return NULL;
|
|
}
|
|
else if (maxitems == 0) {
|
|
PyErr_SetString(PyExc_ValueError, "empty namespaces not allowed");
|
|
return NULL;
|
|
}
|
|
|
|
// Check for overflow.
|
|
size_t fixedsize = sizeof(_PyXI_namespace) - sizeof(_PyXI_namespace_item);
|
|
if ((size_t)maxitems >
|
|
((size_t)PY_SSIZE_T_MAX - fixedsize) / sizeof(_PyXI_namespace_item))
|
|
{
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
|
|
// Allocate the value, including items.
|
|
size_t size = fixedsize + sizeof(_PyXI_namespace_item) * maxitems;
|
|
|
|
_PyXI_namespace *ns = PyMem_RawCalloc(size, 1);
|
|
if (ns == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
ns->maxitems = maxitems;
|
|
assert(_sharedns_check_consistency(ns));
|
|
return ns;
|
|
}
|
|
|
|
static void
|
|
_sharedns_free(_PyXI_namespace *ns)
|
|
{
|
|
// If we weren't always dynamically allocating the cross-interpreter
|
|
// data in each item then we would need to use a pending call
|
|
// to call _sharedns_free(), to avoid the race between freeing
|
|
// the shared namespace and releasing the XI data.
|
|
assert(_sharedns_check_counts(ns));
|
|
Py_ssize_t i = 0;
|
|
_PyXI_namespace_item *item;
|
|
if (ns->numvalues > 0) {
|
|
// One or more items may have interpreter-specific data.
|
|
#ifndef NDEBUG
|
|
int64_t interpid = PyInterpreterState_GetID(PyInterpreterState_Get());
|
|
int64_t interpid_i;
|
|
#endif
|
|
for (; i < ns->numvalues; i++) {
|
|
item = &ns->items[i];
|
|
assert(_sharednsitem_is_initialized(item));
|
|
// While we do want to ensure consistency across items,
|
|
// technically they don't need to match the current
|
|
// interpreter. However, we keep the constraint for
|
|
// simplicity, by giving _PyXI_FreeNamespace() the exclusive
|
|
// responsibility of dealing with the owning interpreter.
|
|
assert(_sharednsitem_has_value(item, &interpid_i));
|
|
assert(interpid_i == interpid);
|
|
_sharednsitem_clear(item);
|
|
}
|
|
}
|
|
for (; i < ns->numnames; i++) {
|
|
item = &ns->items[i];
|
|
assert(_sharednsitem_is_initialized(item));
|
|
assert(!_sharednsitem_has_value(item, NULL));
|
|
_sharednsitem_clear(item);
|
|
}
|
|
#ifndef NDEBUG
|
|
for (; i < ns->maxitems; i++) {
|
|
item = &ns->items[i];
|
|
assert(!_sharednsitem_is_initialized(item));
|
|
assert(!_sharednsitem_has_value(item, NULL));
|
|
}
|
|
#endif
|
|
|
|
PyMem_RawFree(ns);
|
|
}
|
|
|
|
static _PyXI_namespace *
|
|
_create_sharedns(PyObject *names)
|
|
{
|
|
assert(names != NULL);
|
|
Py_ssize_t numnames = PyDict_CheckExact(names)
|
|
? PyDict_Size(names)
|
|
: PySequence_Size(names);
|
|
|
|
_PyXI_namespace *ns = _sharedns_alloc(numnames);
|
|
if (ns == NULL) {
|
|
return NULL;
|
|
}
|
|
_PyXI_namespace_item *items = ns->items;
|
|
|
|
// Fill in the names.
|
|
if (PyDict_CheckExact(names)) {
|
|
Py_ssize_t i = 0;
|
|
Py_ssize_t pos = 0;
|
|
PyObject *name;
|
|
while(PyDict_Next(names, &pos, &name, NULL)) {
|
|
if (_sharednsitem_init(&items[i], name) < 0) {
|
|
goto error;
|
|
}
|
|
ns->numnames += 1;
|
|
i += 1;
|
|
}
|
|
}
|
|
else if (PySequence_Check(names)) {
|
|
for (Py_ssize_t i = 0; i < numnames; i++) {
|
|
PyObject *name = PySequence_GetItem(names, i);
|
|
if (name == NULL) {
|
|
goto error;
|
|
}
|
|
int res = _sharednsitem_init(&items[i], name);
|
|
Py_DECREF(name);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
ns->numnames += 1;
|
|
}
|
|
}
|
|
else {
|
|
PyErr_SetString(PyExc_NotImplementedError,
|
|
"non-sequence namespace not supported");
|
|
goto error;
|
|
}
|
|
assert(ns->numnames == ns->maxitems);
|
|
return ns;
|
|
|
|
error:
|
|
_sharedns_free(ns);
|
|
return NULL;
|
|
}
|
|
|
|
static void _propagate_not_shareable_error(_PyXI_errcode *);
|
|
|
|
static int
|
|
_fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj,
|
|
xidata_fallback_t fallback, _PyXI_errcode *p_errcode)
|
|
{
|
|
// All items are expected to be shareable.
|
|
assert(_sharedns_check_counts(ns));
|
|
assert(ns->numnames == ns->maxitems);
|
|
assert(ns->numvalues == 0);
|
|
for (Py_ssize_t i=0; i < ns->maxitems; i++) {
|
|
if (_sharednsitem_copy_from_ns(&ns->items[i], nsobj, fallback) < 0) {
|
|
if (p_errcode != NULL) {
|
|
_propagate_not_shareable_error(p_errcode);
|
|
}
|
|
// Clear out the ones we set so far.
|
|
for (Py_ssize_t j=0; j < i; j++) {
|
|
_sharednsitem_clear_value(&ns->items[j]);
|
|
ns->numvalues -= 1;
|
|
}
|
|
return -1;
|
|
}
|
|
ns->numvalues += 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_sharedns_free_pending(void *data)
|
|
{
|
|
_sharedns_free((_PyXI_namespace *)data);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_destroy_sharedns(_PyXI_namespace *ns)
|
|
{
|
|
assert(_sharedns_check_counts(ns));
|
|
assert(ns->numnames == ns->maxitems);
|
|
if (ns->numvalues == 0) {
|
|
_sharedns_free(ns);
|
|
return;
|
|
}
|
|
|
|
int64_t interpid0;
|
|
if (!_sharednsitem_has_value(&ns->items[0], &interpid0)) {
|
|
// This shouldn't have been possible.
|
|
// We can deal with it in _sharedns_free().
|
|
_sharedns_free(ns);
|
|
return;
|
|
}
|
|
PyInterpreterState *interp = _PyInterpreterState_LookUpID(interpid0);
|
|
if (interp == PyInterpreterState_Get()) {
|
|
_sharedns_free(ns);
|
|
return;
|
|
}
|
|
|
|
// One or more items may have interpreter-specific data.
|
|
// Currently the xidata for each value is dynamically allocated,
|
|
// so technically we don't need to worry about that.
|
|
// However, explicitly adding a pending call here is simpler.
|
|
(void)_Py_CallInInterpreter(interp, _sharedns_free_pending, ns);
|
|
}
|
|
|
|
static int
|
|
_apply_sharedns(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
|
|
{
|
|
for (Py_ssize_t i=0; i < ns->maxitems; i++) {
|
|
if (_sharednsitem_apply(&ns->items[i], nsobj, dflt) != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*********************************/
|
|
/* switched-interpreter sessions */
|
|
/*********************************/
|
|
|
|
struct xi_session_error {
|
|
// This is set if the interpreter is entered and raised an exception
|
|
// that needs to be handled in some special way during exit.
|
|
_PyXI_errcode *override;
|
|
// This is set if exit captured an exception to propagate.
|
|
_PyXI_error *info;
|
|
|
|
// -- pre-allocated memory --
|
|
_PyXI_error _info;
|
|
_PyXI_errcode _override;
|
|
};
|
|
|
|
struct xi_session {
|
|
#define SESSION_UNUSED 0
|
|
#define SESSION_ACTIVE 1
|
|
int status;
|
|
int switched;
|
|
|
|
// Once a session has been entered, this is the tstate that was
|
|
// current before the session. If it is different from cur_tstate
|
|
// then we must have switched interpreters. Either way, this will
|
|
// be the current tstate once we exit the session.
|
|
PyThreadState *prev_tstate;
|
|
// Once a session has been entered, this is the current tstate.
|
|
// It must be current when the session exits.
|
|
PyThreadState *init_tstate;
|
|
// This is true if init_tstate needs cleanup during exit.
|
|
int own_init_tstate;
|
|
|
|
// This is true if, while entering the session, init_thread took
|
|
// "ownership" of the interpreter's __main__ module. This means
|
|
// it is the only thread that is allowed to run code there.
|
|
// (Caveat: for now, users may still run exec() against the
|
|
// __main__ module's dict, though that isn't advisable.)
|
|
int running;
|
|
// This is a cached reference to the __dict__ of the entered
|
|
// interpreter's __main__ module. It is looked up when at the
|
|
// beginning of the session as a convenience.
|
|
PyObject *main_ns;
|
|
|
|
// This is a dict of objects that will be available (via sharing)
|
|
// once the session exits. Do not access this directly; use
|
|
// _PyXI_Preserve() and _PyXI_GetPreserved() instead;
|
|
PyObject *_preserved;
|
|
|
|
struct xi_session_error error;
|
|
};
|
|
|
|
_PyXI_session *
|
|
_PyXI_NewSession(void)
|
|
{
|
|
_PyXI_session *session = PyMem_RawCalloc(1, sizeof(_PyXI_session));
|
|
if (session == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
return session;
|
|
}
|
|
|
|
void
|
|
_PyXI_FreeSession(_PyXI_session *session)
|
|
{
|
|
assert(session->status == SESSION_UNUSED);
|
|
PyMem_RawFree(session);
|
|
}
|
|
|
|
|
|
static inline int
|
|
_session_is_active(_PyXI_session *session)
|
|
{
|
|
return session->status == SESSION_ACTIVE;
|
|
}
|
|
|
|
static int
|
|
_session_pop_error(_PyXI_session *session, struct xi_session_error *err)
|
|
{
|
|
if (session->error.info == NULL) {
|
|
assert(session->error.override == NULL);
|
|
*err = (struct xi_session_error){0};
|
|
return 0;
|
|
}
|
|
*err = session->error;
|
|
err->info = &err->_info;
|
|
if (err->override != NULL) {
|
|
err->override = &err->_override;
|
|
}
|
|
session->error = (struct xi_session_error){0};
|
|
return 1;
|
|
}
|
|
|
|
static int _ensure_main_ns(_PyXI_session *, _PyXI_errcode *);
|
|
static inline void _session_set_error(_PyXI_session *, _PyXI_errcode);
|
|
|
|
|
|
/* enter/exit a cross-interpreter session */
|
|
|
|
static void
|
|
_enter_session(_PyXI_session *session, PyInterpreterState *interp)
|
|
{
|
|
// Set here and cleared in _exit_session().
|
|
assert(session->status == SESSION_UNUSED);
|
|
assert(!session->own_init_tstate);
|
|
assert(session->init_tstate == NULL);
|
|
assert(session->prev_tstate == NULL);
|
|
// Set elsewhere and cleared in _exit_session().
|
|
assert(!session->running);
|
|
assert(session->main_ns == NULL);
|
|
// Set elsewhere and cleared in _capture_current_exception().
|
|
assert(session->error.override == NULL);
|
|
// Set elsewhere and cleared in _PyXI_Exit().
|
|
assert(session->error.info == NULL);
|
|
|
|
// Switch to interpreter.
|
|
PyThreadState *tstate = PyThreadState_Get();
|
|
PyThreadState *prev = tstate;
|
|
int same_interp = (interp == tstate->interp);
|
|
if (!same_interp) {
|
|
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_EXEC);
|
|
// XXX Possible GILState issues?
|
|
PyThreadState *swapped = PyThreadState_Swap(tstate);
|
|
assert(swapped == prev);
|
|
(void)swapped;
|
|
}
|
|
|
|
*session = (_PyXI_session){
|
|
.status = SESSION_ACTIVE,
|
|
.switched = !same_interp,
|
|
.init_tstate = tstate,
|
|
.prev_tstate = prev,
|
|
.own_init_tstate = !same_interp,
|
|
};
|
|
}
|
|
|
|
static void
|
|
_exit_session(_PyXI_session *session)
|
|
{
|
|
PyThreadState *tstate = session->init_tstate;
|
|
assert(tstate != NULL);
|
|
assert(PyThreadState_Get() == tstate);
|
|
assert(!_PyErr_Occurred(tstate));
|
|
|
|
// Release any of the entered interpreters resources.
|
|
Py_CLEAR(session->main_ns);
|
|
Py_CLEAR(session->_preserved);
|
|
|
|
// Ensure this thread no longer owns __main__.
|
|
if (session->running) {
|
|
_PyInterpreterState_SetNotRunningMain(tstate->interp);
|
|
assert(!_PyErr_Occurred(tstate));
|
|
session->running = 0;
|
|
}
|
|
|
|
// Switch back.
|
|
assert(session->prev_tstate != NULL);
|
|
if (session->prev_tstate != session->init_tstate) {
|
|
assert(session->own_init_tstate);
|
|
session->own_init_tstate = 0;
|
|
PyThreadState_Clear(tstate);
|
|
PyThreadState_Swap(session->prev_tstate);
|
|
PyThreadState_Delete(tstate);
|
|
}
|
|
else {
|
|
assert(!session->own_init_tstate);
|
|
}
|
|
|
|
assert(session->error.info == NULL);
|
|
assert(session->error.override == _PyXI_ERR_NO_ERROR);
|
|
|
|
*session = (_PyXI_session){0};
|
|
}
|
|
|
|
static void
|
|
_propagate_not_shareable_error(_PyXI_errcode *p_errcode)
|
|
{
|
|
assert(p_errcode != NULL);
|
|
PyThreadState *tstate = PyThreadState_Get();
|
|
PyObject *exctype = get_notshareableerror_type(tstate);
|
|
if (exctype == NULL) {
|
|
PyErr_FormatUnraisable(
|
|
"Exception ignored while propagating not shareable error");
|
|
return;
|
|
}
|
|
if (PyErr_ExceptionMatches(exctype)) {
|
|
// We want to propagate the exception directly.
|
|
*p_errcode = _PyXI_ERR_NOT_SHAREABLE;
|
|
}
|
|
}
|
|
|
|
int
|
|
_PyXI_Enter(_PyXI_session *session,
|
|
PyInterpreterState *interp, PyObject *nsupdates,
|
|
_PyXI_session_result *result)
|
|
{
|
|
// Convert the attrs for cross-interpreter use.
|
|
_PyXI_namespace *sharedns = NULL;
|
|
if (nsupdates != NULL) {
|
|
Py_ssize_t len = PyDict_Size(nsupdates);
|
|
if (len < 0) {
|
|
if (result != NULL) {
|
|
result->errcode = _PyXI_ERR_APPLY_NS_FAILURE;
|
|
}
|
|
return -1;
|
|
}
|
|
if (len > 0) {
|
|
sharedns = _create_sharedns(nsupdates);
|
|
if (sharedns == NULL) {
|
|
if (result != NULL) {
|
|
result->errcode = _PyXI_ERR_APPLY_NS_FAILURE;
|
|
}
|
|
return -1;
|
|
}
|
|
// For now we limit it to shareable objects.
|
|
xidata_fallback_t fallback = _PyXIDATA_XIDATA_ONLY;
|
|
_PyXI_errcode errcode = _PyXI_ERR_NO_ERROR;
|
|
if (_fill_sharedns(sharedns, nsupdates, fallback, &errcode) < 0) {
|
|
assert(PyErr_Occurred());
|
|
assert(session->error.info == NULL);
|
|
if (errcode == _PyXI_ERR_NO_ERROR) {
|
|
errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
|
|
}
|
|
_destroy_sharedns(sharedns);
|
|
if (result != NULL) {
|
|
result->errcode = errcode;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Switch to the requested interpreter (if necessary).
|
|
_enter_session(session, interp);
|
|
_PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
|
|
|
|
// Ensure this thread owns __main__.
|
|
if (_PyInterpreterState_SetRunningMain(interp) < 0) {
|
|
// In the case where we didn't switch interpreters, it would
|
|
// be more efficient to leave the exception in place and return
|
|
// immediately. However, life is simpler if we don't.
|
|
errcode = _PyXI_ERR_ALREADY_RUNNING;
|
|
goto error;
|
|
}
|
|
session->running = 1;
|
|
|
|
// Apply the cross-interpreter data.
|
|
if (sharedns != NULL) {
|
|
if (_ensure_main_ns(session, &errcode) < 0) {
|
|
goto error;
|
|
}
|
|
if (_apply_sharedns(sharedns, session->main_ns, NULL) < 0) {
|
|
errcode = _PyXI_ERR_APPLY_NS_FAILURE;
|
|
goto error;
|
|
}
|
|
_destroy_sharedns(sharedns);
|
|
}
|
|
|
|
errcode = _PyXI_ERR_NO_ERROR;
|
|
assert(!PyErr_Occurred());
|
|
return 0;
|
|
|
|
error:
|
|
// We want to propagate all exceptions here directly (best effort).
|
|
assert(errcode != _PyXI_ERR_NO_ERROR);
|
|
_session_set_error(session, errcode);
|
|
assert(!PyErr_Occurred());
|
|
|
|
// Exit the session.
|
|
struct xi_session_error err;
|
|
(void)_session_pop_error(session, &err);
|
|
_exit_session(session);
|
|
|
|
if (sharedns != NULL) {
|
|
_destroy_sharedns(sharedns);
|
|
}
|
|
|
|
// Apply the error from the other interpreter.
|
|
PyObject *excinfo = _PyXI_ApplyError(err.info);
|
|
_PyXI_excinfo_clear(&err.info->uncaught);
|
|
if (excinfo != NULL) {
|
|
if (result != NULL) {
|
|
result->excinfo = excinfo;
|
|
}
|
|
else {
|
|
#ifdef Py_DEBUG
|
|
fprintf(stderr, "_PyXI_Enter(): uncaught exception discarded");
|
|
#endif
|
|
}
|
|
}
|
|
assert(PyErr_Occurred());
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int _pop_preserved(_PyXI_session *, _PyXI_namespace **, PyObject **,
|
|
_PyXI_errcode *);
|
|
static int _finish_preserved(_PyXI_namespace *, PyObject **);
|
|
|
|
int
|
|
_PyXI_Exit(_PyXI_session *session, _PyXI_errcode errcode,
|
|
_PyXI_session_result *result)
|
|
{
|
|
int res = 0;
|
|
|
|
// Capture the raised exception, if any.
|
|
assert(session->error.info == NULL);
|
|
if (PyErr_Occurred()) {
|
|
_session_set_error(session, errcode);
|
|
assert(!PyErr_Occurred());
|
|
}
|
|
else {
|
|
assert(errcode == _PyXI_ERR_NO_ERROR);
|
|
assert(session->error.override == NULL);
|
|
}
|
|
|
|
// Capture the preserved namespace.
|
|
_PyXI_namespace *preserved = NULL;
|
|
PyObject *preservedobj = NULL;
|
|
if (result != NULL) {
|
|
errcode = _PyXI_ERR_NO_ERROR;
|
|
if (_pop_preserved(session, &preserved, &preservedobj, &errcode) < 0) {
|
|
if (session->error.info != NULL) {
|
|
// XXX Chain the exception (i.e. set __context__)?
|
|
PyErr_FormatUnraisable(
|
|
"Exception ignored while capturing preserved objects");
|
|
}
|
|
else {
|
|
_session_set_error(session, errcode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exit the session.
|
|
struct xi_session_error err;
|
|
(void)_session_pop_error(session, &err);
|
|
_exit_session(session);
|
|
|
|
// Restore the preserved namespace.
|
|
assert(preserved == NULL || preservedobj == NULL);
|
|
if (_finish_preserved(preserved, &preservedobj) < 0) {
|
|
assert(preservedobj == NULL);
|
|
if (err.info != NULL) {
|
|
// XXX Chain the exception (i.e. set __context__)?
|
|
PyErr_FormatUnraisable(
|
|
"Exception ignored while capturing preserved objects");
|
|
}
|
|
else {
|
|
errcode = _PyXI_ERR_PRESERVE_FAILURE;
|
|
_propagate_not_shareable_error(&errcode);
|
|
}
|
|
}
|
|
if (result != NULL) {
|
|
result->preserved = preservedobj;
|
|
result->errcode = errcode;
|
|
}
|
|
|
|
// Apply the error from the other interpreter, if any.
|
|
if (err.info != NULL) {
|
|
res = -1;
|
|
assert(!PyErr_Occurred());
|
|
PyObject *excinfo = _PyXI_ApplyError(err.info);
|
|
_PyXI_excinfo_clear(&err.info->uncaught);
|
|
if (excinfo == NULL) {
|
|
assert(PyErr_Occurred());
|
|
if (result != NULL) {
|
|
_PyXI_ClearResult(result);
|
|
*result = (_PyXI_session_result){
|
|
.errcode = _PyXI_ERR_EXC_PROPAGATION_FAILURE,
|
|
};
|
|
}
|
|
}
|
|
else if (result != NULL) {
|
|
result->excinfo = excinfo;
|
|
}
|
|
else {
|
|
#ifdef Py_DEBUG
|
|
fprintf(stderr, "_PyXI_Exit(): uncaught exception discarded");
|
|
#endif
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/* in an active cross-interpreter session */
|
|
|
|
static void
|
|
_capture_current_exception(_PyXI_session *session)
|
|
{
|
|
assert(session->error.info == NULL);
|
|
if (!PyErr_Occurred()) {
|
|
assert(session->error.override == NULL);
|
|
return;
|
|
}
|
|
|
|
// Handle the exception override.
|
|
_PyXI_errcode *override = session->error.override;
|
|
session->error.override = NULL;
|
|
_PyXI_errcode errcode = override != NULL
|
|
? *override
|
|
: _PyXI_ERR_UNCAUGHT_EXCEPTION;
|
|
|
|
// Pop the exception object.
|
|
PyObject *excval = NULL;
|
|
if (errcode == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
|
|
// We want to actually capture the current exception.
|
|
excval = PyErr_GetRaisedException();
|
|
}
|
|
else if (errcode == _PyXI_ERR_ALREADY_RUNNING) {
|
|
// We don't need the exception info.
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
// We could do a variety of things here, depending on errcode.
|
|
// However, for now we simply capture the exception and save
|
|
// the errcode.
|
|
excval = PyErr_GetRaisedException();
|
|
}
|
|
|
|
// Capture the exception.
|
|
_PyXI_error *err = &session->error._info;
|
|
*err = (_PyXI_error){
|
|
.interp = session->init_tstate->interp,
|
|
};
|
|
const char *failure;
|
|
if (excval == NULL) {
|
|
failure = _PyXI_InitError(err, NULL, errcode);
|
|
}
|
|
else {
|
|
failure = _PyXI_InitError(err, excval, _PyXI_ERR_UNCAUGHT_EXCEPTION);
|
|
Py_DECREF(excval);
|
|
if (failure == NULL && override != NULL) {
|
|
err->code = errcode;
|
|
}
|
|
}
|
|
|
|
// Handle capture failure.
|
|
if (failure != NULL) {
|
|
// XXX Make this error message more generic.
|
|
fprintf(stderr,
|
|
"RunFailedError: script raised an uncaught exception (%s)",
|
|
failure);
|
|
err = NULL;
|
|
}
|
|
|
|
// Finished!
|
|
assert(!PyErr_Occurred());
|
|
session->error.info = err;
|
|
}
|
|
|
|
static inline void
|
|
_session_set_error(_PyXI_session *session, _PyXI_errcode errcode)
|
|
{
|
|
assert(_session_is_active(session));
|
|
assert(PyErr_Occurred());
|
|
if (errcode == _PyXI_ERR_NO_ERROR) {
|
|
// We're a bit forgiving here.
|
|
errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
|
|
}
|
|
if (errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION) {
|
|
session->error._override = errcode;
|
|
session->error.override = &session->error._override;
|
|
}
|
|
_capture_current_exception(session);
|
|
}
|
|
|
|
static int
|
|
_ensure_main_ns(_PyXI_session *session, _PyXI_errcode *p_errcode)
|
|
{
|
|
assert(_session_is_active(session));
|
|
if (session->main_ns != NULL) {
|
|
return 0;
|
|
}
|
|
// Cache __main__.__dict__.
|
|
PyObject *main_mod = _Py_GetMainModule(session->init_tstate);
|
|
if (_Py_CheckMainModule(main_mod) < 0) {
|
|
if (p_errcode != NULL) {
|
|
*p_errcode = _PyXI_ERR_MAIN_NS_FAILURE;
|
|
}
|
|
return -1;
|
|
}
|
|
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
|
|
Py_DECREF(main_mod);
|
|
if (ns == NULL) {
|
|
if (p_errcode != NULL) {
|
|
*p_errcode = _PyXI_ERR_MAIN_NS_FAILURE;
|
|
}
|
|
return -1;
|
|
}
|
|
session->main_ns = Py_NewRef(ns);
|
|
return 0;
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_GetMainNamespace(_PyXI_session *session, _PyXI_errcode *p_errcode)
|
|
{
|
|
if (!_session_is_active(session)) {
|
|
PyErr_SetString(PyExc_RuntimeError, "session not active");
|
|
return NULL;
|
|
}
|
|
if (_ensure_main_ns(session, p_errcode) < 0) {
|
|
return NULL;
|
|
}
|
|
return session->main_ns;
|
|
}
|
|
|
|
|
|
static int
|
|
_pop_preserved(_PyXI_session *session,
|
|
_PyXI_namespace **p_xidata, PyObject **p_obj,
|
|
_PyXI_errcode *p_errcode)
|
|
{
|
|
assert(_PyThreadState_GET() == session->init_tstate); // active session
|
|
if (session->_preserved == NULL) {
|
|
*p_xidata = NULL;
|
|
*p_obj = NULL;
|
|
return 0;
|
|
}
|
|
if (session->init_tstate == session->prev_tstate) {
|
|
// We did not switch interpreters.
|
|
*p_xidata = NULL;
|
|
*p_obj = session->_preserved;
|
|
session->_preserved = NULL;
|
|
return 0;
|
|
}
|
|
*p_obj = NULL;
|
|
|
|
// We did switch interpreters.
|
|
Py_ssize_t len = PyDict_Size(session->_preserved);
|
|
if (len < 0) {
|
|
if (p_errcode != NULL) {
|
|
*p_errcode = _PyXI_ERR_PRESERVE_FAILURE;
|
|
}
|
|
return -1;
|
|
}
|
|
else if (len == 0) {
|
|
*p_xidata = NULL;
|
|
}
|
|
else {
|
|
_PyXI_namespace *xidata = _create_sharedns(session->_preserved);
|
|
if (xidata == NULL) {
|
|
if (p_errcode != NULL) {
|
|
*p_errcode = _PyXI_ERR_PRESERVE_FAILURE;
|
|
}
|
|
return -1;
|
|
}
|
|
_PyXI_errcode errcode = _PyXI_ERR_NO_ERROR;
|
|
if (_fill_sharedns(xidata, session->_preserved,
|
|
_PyXIDATA_FULL_FALLBACK, &errcode) < 0)
|
|
{
|
|
assert(session->error.info == NULL);
|
|
if (errcode != _PyXI_ERR_NOT_SHAREABLE) {
|
|
errcode = _PyXI_ERR_PRESERVE_FAILURE;
|
|
}
|
|
if (p_errcode != NULL) {
|
|
*p_errcode = errcode;
|
|
}
|
|
_destroy_sharedns(xidata);
|
|
return -1;
|
|
}
|
|
*p_xidata = xidata;
|
|
}
|
|
Py_CLEAR(session->_preserved);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_finish_preserved(_PyXI_namespace *xidata, PyObject **p_preserved)
|
|
{
|
|
if (xidata == NULL) {
|
|
return 0;
|
|
}
|
|
int res = -1;
|
|
if (p_preserved != NULL) {
|
|
PyObject *ns = PyDict_New();
|
|
if (ns == NULL) {
|
|
goto finally;
|
|
}
|
|
if (_apply_sharedns(xidata, ns, NULL) < 0) {
|
|
Py_CLEAR(ns);
|
|
goto finally;
|
|
}
|
|
*p_preserved = ns;
|
|
}
|
|
res = 0;
|
|
|
|
finally:
|
|
_destroy_sharedns(xidata);
|
|
return res;
|
|
}
|
|
|
|
int
|
|
_PyXI_Preserve(_PyXI_session *session, const char *name, PyObject *value,
|
|
_PyXI_errcode *p_errcode)
|
|
{
|
|
if (!_session_is_active(session)) {
|
|
PyErr_SetString(PyExc_RuntimeError, "session not active");
|
|
return -1;
|
|
}
|
|
if (session->_preserved == NULL) {
|
|
session->_preserved = PyDict_New();
|
|
if (session->_preserved == NULL) {
|
|
set_exc_with_cause(PyExc_RuntimeError,
|
|
"failed to initialize preserved objects");
|
|
if (p_errcode != NULL) {
|
|
*p_errcode = _PyXI_ERR_PRESERVE_FAILURE;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
if (PyDict_SetItemString(session->_preserved, name, value) < 0) {
|
|
set_exc_with_cause(PyExc_RuntimeError, "failed to preserve object");
|
|
if (p_errcode != NULL) {
|
|
*p_errcode = _PyXI_ERR_PRESERVE_FAILURE;
|
|
}
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_GetPreserved(_PyXI_session_result *result, const char *name)
|
|
{
|
|
PyObject *value = NULL;
|
|
if (result->preserved != NULL) {
|
|
(void)PyDict_GetItemStringRef(result->preserved, name, &value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void
|
|
_PyXI_ClearResult(_PyXI_session_result *result)
|
|
{
|
|
Py_CLEAR(result->preserved);
|
|
Py_CLEAR(result->excinfo);
|
|
}
|
|
|
|
|
|
/*********************/
|
|
/* runtime lifecycle */
|
|
/*********************/
|
|
|
|
int
|
|
_Py_xi_global_state_init(_PyXI_global_state_t *state)
|
|
{
|
|
assert(state != NULL);
|
|
xid_lookup_init(&state->data_lookup);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
_Py_xi_global_state_fini(_PyXI_global_state_t *state)
|
|
{
|
|
assert(state != NULL);
|
|
xid_lookup_fini(&state->data_lookup);
|
|
}
|
|
|
|
int
|
|
_Py_xi_state_init(_PyXI_state_t *state, PyInterpreterState *interp)
|
|
{
|
|
assert(state != NULL);
|
|
assert(interp == NULL || state == _PyXI_GET_STATE(interp));
|
|
|
|
xid_lookup_init(&state->data_lookup);
|
|
|
|
// Initialize exceptions.
|
|
if (interp != NULL) {
|
|
if (init_static_exctypes(&state->exceptions, interp) < 0) {
|
|
fini_heap_exctypes(&state->exceptions);
|
|
return -1;
|
|
}
|
|
}
|
|
if (init_heap_exctypes(&state->exceptions) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
_Py_xi_state_fini(_PyXI_state_t *state, PyInterpreterState *interp)
|
|
{
|
|
assert(state != NULL);
|
|
assert(interp == NULL || state == _PyXI_GET_STATE(interp));
|
|
|
|
fini_heap_exctypes(&state->exceptions);
|
|
if (interp != NULL) {
|
|
fini_static_exctypes(&state->exceptions, interp);
|
|
}
|
|
|
|
xid_lookup_fini(&state->data_lookup);
|
|
}
|
|
|
|
|
|
PyStatus
|
|
_PyXI_Init(PyInterpreterState *interp)
|
|
{
|
|
if (_Py_IsMainInterpreter(interp)) {
|
|
_PyXI_global_state_t *global_state = _PyXI_GET_GLOBAL_STATE(interp);
|
|
if (global_state == NULL) {
|
|
PyErr_PrintEx(0);
|
|
return _PyStatus_ERR(
|
|
"failed to get global cross-interpreter state");
|
|
}
|
|
if (_Py_xi_global_state_init(global_state) < 0) {
|
|
PyErr_PrintEx(0);
|
|
return _PyStatus_ERR(
|
|
"failed to initialize global cross-interpreter state");
|
|
}
|
|
}
|
|
|
|
_PyXI_state_t *state = _PyXI_GET_STATE(interp);
|
|
if (state == NULL) {
|
|
PyErr_PrintEx(0);
|
|
return _PyStatus_ERR(
|
|
"failed to get interpreter's cross-interpreter state");
|
|
}
|
|
// The static types were already initialized in _PyXI_InitTypes(),
|
|
// so we pass in NULL here to avoid initializing them again.
|
|
if (_Py_xi_state_init(state, NULL) < 0) {
|
|
PyErr_PrintEx(0);
|
|
return _PyStatus_ERR(
|
|
"failed to initialize interpreter's cross-interpreter state");
|
|
}
|
|
|
|
return _PyStatus_OK();
|
|
}
|
|
|
|
// _PyXI_Fini() must be called before the interpreter is cleared,
|
|
// since we must clear some heap objects.
|
|
|
|
void
|
|
_PyXI_Fini(PyInterpreterState *interp)
|
|
{
|
|
_PyXI_state_t *state = _PyXI_GET_STATE(interp);
|
|
#ifndef NDEBUG
|
|
if (state == NULL) {
|
|
PyErr_PrintEx(0);
|
|
return;
|
|
}
|
|
#endif
|
|
// The static types will be finalized soon in _PyXI_FiniTypes(),
|
|
// so we pass in NULL here to avoid finalizing them right now.
|
|
_Py_xi_state_fini(state, NULL);
|
|
|
|
if (_Py_IsMainInterpreter(interp)) {
|
|
_PyXI_global_state_t *global_state = _PyXI_GET_GLOBAL_STATE(interp);
|
|
_Py_xi_global_state_fini(global_state);
|
|
}
|
|
}
|
|
|
|
PyStatus
|
|
_PyXI_InitTypes(PyInterpreterState *interp)
|
|
{
|
|
if (init_static_exctypes(&_PyXI_GET_STATE(interp)->exceptions, interp) < 0) {
|
|
PyErr_PrintEx(0);
|
|
return _PyStatus_ERR(
|
|
"failed to initialize the cross-interpreter exception types");
|
|
}
|
|
// We would initialize heap types here too but that leads to ref leaks.
|
|
// Instead, we intialize them in _PyXI_Init().
|
|
return _PyStatus_OK();
|
|
}
|
|
|
|
void
|
|
_PyXI_FiniTypes(PyInterpreterState *interp)
|
|
{
|
|
// We would finalize heap types here too but that leads to ref leaks.
|
|
// Instead, we finalize them in _PyXI_Fini().
|
|
fini_static_exctypes(&_PyXI_GET_STATE(interp)->exceptions, interp);
|
|
}
|
|
|
|
|
|
/*************/
|
|
/* other API */
|
|
/*************/
|
|
|
|
PyInterpreterState *
|
|
_PyXI_NewInterpreter(PyInterpreterConfig *config, long *maybe_whence,
|
|
PyThreadState **p_tstate, PyThreadState **p_save_tstate)
|
|
{
|
|
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
|
|
assert(save_tstate != NULL);
|
|
|
|
PyThreadState *tstate;
|
|
PyStatus status = Py_NewInterpreterFromConfig(&tstate, config);
|
|
if (PyStatus_Exception(status)) {
|
|
// Since no new thread state was created, there is no exception
|
|
// to propagate; raise a fresh one after swapping back in the
|
|
// old thread state.
|
|
PyThreadState_Swap(save_tstate);
|
|
_PyErr_SetFromPyStatus(status);
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
PyErr_SetString(PyExc_InterpreterError,
|
|
"sub-interpreter creation failed");
|
|
_PyErr_ChainExceptions1(exc);
|
|
return NULL;
|
|
}
|
|
assert(tstate != NULL);
|
|
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
|
|
|
|
long whence = _PyInterpreterState_WHENCE_XI;
|
|
if (maybe_whence != NULL) {
|
|
whence = *maybe_whence;
|
|
}
|
|
_PyInterpreterState_SetWhence(interp, whence);
|
|
|
|
if (p_tstate != NULL) {
|
|
// We leave the new thread state as the current one.
|
|
*p_tstate = tstate;
|
|
}
|
|
else {
|
|
// Throw away the initial tstate.
|
|
PyThreadState_Clear(tstate);
|
|
PyThreadState_Swap(save_tstate);
|
|
PyThreadState_Delete(tstate);
|
|
save_tstate = NULL;
|
|
}
|
|
if (p_save_tstate != NULL) {
|
|
*p_save_tstate = save_tstate;
|
|
}
|
|
return interp;
|
|
}
|
|
|
|
void
|
|
_PyXI_EndInterpreter(PyInterpreterState *interp,
|
|
PyThreadState *tstate, PyThreadState **p_save_tstate)
|
|
{
|
|
#ifndef NDEBUG
|
|
long whence = _PyInterpreterState_GetWhence(interp);
|
|
#endif
|
|
assert(whence != _PyInterpreterState_WHENCE_RUNTIME);
|
|
|
|
if (!_PyInterpreterState_IsReady(interp)) {
|
|
assert(whence == _PyInterpreterState_WHENCE_UNKNOWN);
|
|
// PyInterpreterState_Clear() requires the GIL,
|
|
// which a not-ready does not have, so we don't clear it.
|
|
// That means there may be leaks here until clearing the
|
|
// interpreter is fixed.
|
|
PyInterpreterState_Delete(interp);
|
|
return;
|
|
}
|
|
assert(whence != _PyInterpreterState_WHENCE_UNKNOWN);
|
|
|
|
PyThreadState *save_tstate = NULL;
|
|
PyThreadState *cur_tstate = PyThreadState_GET();
|
|
if (tstate == NULL) {
|
|
if (PyThreadState_GetInterpreter(cur_tstate) == interp) {
|
|
tstate = cur_tstate;
|
|
}
|
|
else {
|
|
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI);
|
|
assert(tstate != NULL);
|
|
save_tstate = PyThreadState_Swap(tstate);
|
|
}
|
|
}
|
|
else {
|
|
assert(PyThreadState_GetInterpreter(tstate) == interp);
|
|
if (tstate != cur_tstate) {
|
|
assert(PyThreadState_GetInterpreter(cur_tstate) != interp);
|
|
save_tstate = PyThreadState_Swap(tstate);
|
|
}
|
|
}
|
|
|
|
Py_EndInterpreter(tstate);
|
|
|
|
if (p_save_tstate != NULL) {
|
|
save_tstate = *p_save_tstate;
|
|
}
|
|
PyThreadState_Swap(save_tstate);
|
|
}
|