mirror of
https://github.com/python/cpython.git
synced 2025-07-09 20:35:26 +00:00
bpo-36842: Implement PEP 578 (GH-12613)
Adds sys.audit, sys.addaudithook, io.open_code, and associated C APIs.
This commit is contained in:
parent
e788057a91
commit
b82e17e626
70 changed files with 3565 additions and 1816 deletions
|
@ -22,7 +22,9 @@ Data members:
|
|||
#include "pycore_pymem.h"
|
||||
#include "pycore_pathconfig.h"
|
||||
#include "pycore_pystate.h"
|
||||
#include "pycore_tupleobject.h"
|
||||
#include "pythread.h"
|
||||
#include "pydtrace.h"
|
||||
|
||||
#include "osdefs.h"
|
||||
#include <locale.h>
|
||||
|
@ -111,6 +113,308 @@ PySys_SetObject(const char *name, PyObject *v)
|
|||
}
|
||||
}
|
||||
|
||||
static int
|
||||
should_audit(void)
|
||||
{
|
||||
PyThreadState *ts = _PyThreadState_GET();
|
||||
if (!ts) {
|
||||
return 0;
|
||||
}
|
||||
PyInterpreterState *is = ts ? ts->interp : NULL;
|
||||
return _PyRuntime.audit_hook_head
|
||||
|| (is && is->audit_hooks)
|
||||
|| PyDTrace_AUDIT_ENABLED();
|
||||
}
|
||||
|
||||
int
|
||||
PySys_Audit(const char *event, const char *argFormat, ...)
|
||||
{
|
||||
PyObject *eventName = NULL;
|
||||
PyObject *eventArgs = NULL;
|
||||
PyObject *hooks = NULL;
|
||||
PyObject *hook = NULL;
|
||||
int res = -1;
|
||||
|
||||
/* N format is inappropriate, because you do not know
|
||||
whether the reference is consumed by the call.
|
||||
Assert rather than exception for perf reasons */
|
||||
assert(!argFormat || !strchr(argFormat, 'N'));
|
||||
|
||||
/* Early exit when no hooks are registered */
|
||||
if (!should_audit()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_Py_AuditHookEntry *e = _PyRuntime.audit_hook_head;
|
||||
PyThreadState *ts = _PyThreadState_GET();
|
||||
PyInterpreterState *is = ts ? ts->interp : NULL;
|
||||
int dtrace = PyDTrace_AUDIT_ENABLED();
|
||||
|
||||
PyObject *exc_type, *exc_value, *exc_tb;
|
||||
if (ts) {
|
||||
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
|
||||
}
|
||||
|
||||
/* Initialize event args now */
|
||||
if (argFormat && argFormat[0]) {
|
||||
va_list args;
|
||||
va_start(args, argFormat);
|
||||
eventArgs = Py_VaBuildValue(argFormat, args);
|
||||
if (eventArgs && !PyTuple_Check(eventArgs)) {
|
||||
PyObject *argTuple = PyTuple_Pack(1, eventArgs);
|
||||
Py_DECREF(eventArgs);
|
||||
eventArgs = argTuple;
|
||||
}
|
||||
} else {
|
||||
eventArgs = PyTuple_New(0);
|
||||
}
|
||||
if (!eventArgs) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Call global hooks */
|
||||
for (; e; e = e->next) {
|
||||
if (e->hookCFunction(event, eventArgs, e->userData) < 0) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dtrace USDT point */
|
||||
if (dtrace) {
|
||||
PyDTrace_AUDIT(event, (void *)eventArgs);
|
||||
}
|
||||
|
||||
/* Call interpreter hooks */
|
||||
if (is && is->audit_hooks) {
|
||||
eventName = PyUnicode_FromString(event);
|
||||
if (!eventName) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
hooks = PyObject_GetIter(is->audit_hooks);
|
||||
if (!hooks) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Disallow tracing in hooks unless explicitly enabled */
|
||||
ts->tracing++;
|
||||
ts->use_tracing = 0;
|
||||
while ((hook = PyIter_Next(hooks)) != NULL) {
|
||||
PyObject *o;
|
||||
int canTrace = -1;
|
||||
o = PyObject_GetAttrString(hook, "__cantrace__");
|
||||
if (o) {
|
||||
canTrace = PyObject_IsTrue(o);
|
||||
Py_DECREF(o);
|
||||
} else if (PyErr_Occurred() &&
|
||||
PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
||||
PyErr_Clear();
|
||||
canTrace = 0;
|
||||
}
|
||||
if (canTrace < 0) {
|
||||
break;
|
||||
}
|
||||
if (canTrace) {
|
||||
ts->use_tracing = (ts->c_tracefunc || ts->c_profilefunc);
|
||||
ts->tracing--;
|
||||
}
|
||||
o = PyObject_CallFunctionObjArgs(hook, eventName,
|
||||
eventArgs, NULL);
|
||||
if (canTrace) {
|
||||
ts->tracing++;
|
||||
ts->use_tracing = 0;
|
||||
}
|
||||
if (!o) {
|
||||
break;
|
||||
}
|
||||
Py_DECREF(o);
|
||||
Py_CLEAR(hook);
|
||||
}
|
||||
ts->use_tracing = (ts->c_tracefunc || ts->c_profilefunc);
|
||||
ts->tracing--;
|
||||
if (PyErr_Occurred()) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
res = 0;
|
||||
|
||||
exit:
|
||||
Py_XDECREF(hook);
|
||||
Py_XDECREF(hooks);
|
||||
Py_XDECREF(eventName);
|
||||
Py_XDECREF(eventArgs);
|
||||
|
||||
if (ts) {
|
||||
if (!res) {
|
||||
PyErr_Restore(exc_type, exc_value, exc_tb);
|
||||
} else {
|
||||
assert(PyErr_Occurred());
|
||||
Py_XDECREF(exc_type);
|
||||
Py_XDECREF(exc_value);
|
||||
Py_XDECREF(exc_tb);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* We expose this function primarily for our own cleanup during
|
||||
* finalization. In general, it should not need to be called,
|
||||
* and as such it is not defined in any header files.
|
||||
*/
|
||||
void _PySys_ClearAuditHooks(void) {
|
||||
/* Must be finalizing to clear hooks */
|
||||
_PyRuntimeState *runtime = &_PyRuntime;
|
||||
PyThreadState *ts = _PyRuntimeState_GetThreadState(runtime);
|
||||
assert(!ts || _Py_CURRENTLY_FINALIZING(runtime, ts));
|
||||
if (!ts || !_Py_CURRENTLY_FINALIZING(runtime, ts))
|
||||
return;
|
||||
|
||||
if (Py_VerboseFlag) {
|
||||
PySys_WriteStderr("# clear sys.audit hooks\n");
|
||||
}
|
||||
|
||||
/* Hooks can abort later hooks for this event, but cannot
|
||||
abort the clear operation itself. */
|
||||
PySys_Audit("cpython._PySys_ClearAuditHooks", NULL);
|
||||
PyErr_Clear();
|
||||
|
||||
_Py_AuditHookEntry *e = _PyRuntime.audit_hook_head, *n;
|
||||
_PyRuntime.audit_hook_head = NULL;
|
||||
while (e) {
|
||||
n = e->next;
|
||||
PyMem_RawFree(e);
|
||||
e = n;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData)
|
||||
{
|
||||
/* Invoke existing audit hooks to allow them an opportunity to abort. */
|
||||
/* Cannot invoke hooks until we are initialized */
|
||||
if (Py_IsInitialized()) {
|
||||
if (PySys_Audit("sys.addaudithook", NULL) < 0) {
|
||||
if (PyErr_ExceptionMatches(PyExc_Exception)) {
|
||||
/* We do not report errors derived from Exception */
|
||||
PyErr_Clear();
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
_Py_AuditHookEntry *e = _PyRuntime.audit_hook_head;
|
||||
if (!e) {
|
||||
e = (_Py_AuditHookEntry*)PyMem_RawMalloc(sizeof(_Py_AuditHookEntry));
|
||||
_PyRuntime.audit_hook_head = e;
|
||||
} else {
|
||||
while (e->next)
|
||||
e = e->next;
|
||||
e = e->next = (_Py_AuditHookEntry*)PyMem_RawMalloc(
|
||||
sizeof(_Py_AuditHookEntry));
|
||||
}
|
||||
|
||||
if (!e) {
|
||||
if (Py_IsInitialized())
|
||||
PyErr_NoMemory();
|
||||
return -1;
|
||||
}
|
||||
|
||||
e->next = NULL;
|
||||
e->hookCFunction = (Py_AuditHookFunction)hook;
|
||||
e->userData = userData;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
sys.addaudithook
|
||||
|
||||
hook: object
|
||||
|
||||
Adds a new audit hook callback.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
sys_addaudithook_impl(PyObject *module, PyObject *hook)
|
||||
/*[clinic end generated code: output=4f9c17aaeb02f44e input=0f3e191217a45e34]*/
|
||||
{
|
||||
/* Invoke existing audit hooks to allow them an opportunity to abort. */
|
||||
if (PySys_Audit("sys.addaudithook", NULL) < 0) {
|
||||
if (PyErr_ExceptionMatches(PyExc_Exception)) {
|
||||
/* We do not report errors derived from Exception */
|
||||
PyErr_Clear();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyInterpreterState *is = _PyInterpreterState_Get();
|
||||
|
||||
if (is->audit_hooks == NULL) {
|
||||
is->audit_hooks = PyList_New(0);
|
||||
if (is->audit_hooks == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (PyList_Append(is->audit_hooks, hook) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(audit_doc,
|
||||
"audit(event, *args)\n\
|
||||
\n\
|
||||
Passes the event to any audit hooks that are attached.");
|
||||
|
||||
static PyObject *
|
||||
sys_audit(PyObject *self, PyObject *const *args, Py_ssize_t argc)
|
||||
{
|
||||
if (argc == 0) {
|
||||
PyErr_SetString(PyExc_TypeError, "audit() missing 1 required positional argument: 'event'");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!should_audit()) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject *auditEvent = args[0];
|
||||
if (!auditEvent) {
|
||||
PyErr_SetString(PyExc_TypeError, "expected str for argument 'event'");
|
||||
return NULL;
|
||||
}
|
||||
if (!PyUnicode_Check(auditEvent)) {
|
||||
PyErr_Format(PyExc_TypeError, "expected str for argument 'event', not %.200s",
|
||||
Py_TYPE(auditEvent)->tp_name);
|
||||
return NULL;
|
||||
}
|
||||
const char *event = PyUnicode_AsUTF8(auditEvent);
|
||||
if (!event) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *auditArgs = _PyTuple_FromArray(args + 1, argc - 1);
|
||||
if (!auditArgs) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int res = PySys_Audit(event, "O", auditArgs);
|
||||
Py_DECREF(auditArgs);
|
||||
|
||||
if (res < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
sys_breakpointhook(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *keywords)
|
||||
{
|
||||
|
@ -1469,6 +1773,10 @@ sys__getframe_impl(PyObject *module, int depth)
|
|||
{
|
||||
PyFrameObject *f = _PyThreadState_GET()->frame;
|
||||
|
||||
if (PySys_Audit("sys._getframe", "O", f) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (depth > 0 && f != NULL) {
|
||||
f = f->f_back;
|
||||
--depth;
|
||||
|
@ -1642,8 +1950,11 @@ sys_getandroidapilevel_impl(PyObject *module)
|
|||
#endif /* ANDROID_API_LEVEL */
|
||||
|
||||
|
||||
|
||||
static PyMethodDef sys_methods[] = {
|
||||
/* Might as well keep this in alphabetic order */
|
||||
SYS_ADDAUDITHOOK_METHODDEF
|
||||
{"audit", (PyCFunction)(void(*)(void))sys_audit, METH_FASTCALL, audit_doc },
|
||||
{"breakpointhook", (PyCFunction)(void(*)(void))sys_breakpointhook,
|
||||
METH_FASTCALL | METH_KEYWORDS, breakpointhook_doc},
|
||||
SYS_CALLSTATS_METHODDEF
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue