summaryrefslogtreecommitdiffstats
path: root/Python/sysmodule.c
diff options
context:
space:
mode:
Diffstat (limited to 'Python/sysmodule.c')
-rw-r--r--Python/sysmodule.c311
1 files changed, 311 insertions, 0 deletions
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 1735b90..5ebeacf 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -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