GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-158711: Ensure legacy tracing ignores PyMonitoring_Fire* by godlygeek · Pull Request #158894 · python/cpython

Conversation

@godlygeek

@godlygeek godlygeek commented Oct 6, 2026 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

sys.setprofile and sys.settrace callbacks are no longer called for sys.monitoring events that extension modules fire for their own code objects through the monitoring C API.

Until now, the sys.setprofile or sys.settrace function would get run, but it would be passed the top frame on the Python stack rather than anything related to the code object passed to the monitoring C API, and this would manifest to the legacy tracing function as nonsensical duplicate events rather than anything that could be attributed to the code object passed to the monitoring API.

  • Issue: PyMonitoring_FirePyStartEvent results in incorrect duplicate calls to sys.setprofile profile functions #158711

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@godlygeek
Ensure legacy tracing ignores PyMonitoring_Fire*
d6e5e4a
`sys.setprofile` and `sys.settrace` callbacks are no longer called for
`sys.monitoring` events that extension modules fire for their own code
objects through the monitoring C API.

Until now, the `sys.setprofile` or `sys.settrace` function would get
run, but it would be passed the top frame on the Python stack rather
than anything related to the code object passed to the monitoring C API,
and this would manifest to the legacy tracing function as nonsensical
duplicate events rather than anything that could be attributed to the
code object passed to the monitoring API.
@bedevere-app bedevere-app Bot added the awaiting review label Oct 6, 2026
@bedevere-app bedevere-app Bot mentioned this pull request Oct 6, 2026
PyMonitoring_FirePyStartEvent results in incorrect duplicate calls to sys.setprofile profile functions #158711
Open
godlygeek
godlygeek commented Oct 6, 2026
View reviewed changes
Comment thread Lib/test/test_monitoring.py
( 1, E.PY_RESUME, capi.fire_event_py_resume),
( 1, E.PY_YIELD, capi.fire_event_py_yield, 10),
( 1, E.PY_RETURN, capi.fire_event_py_return, 20),
( 2, E.CALL, capi.fire_event_call, callable, 40),

@godlygeek godlygeek Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: The removed 1st tuple element (never reflected in the above comment!) was the expected number of calls that would be seen. It was 1 for everything except E.CALL, where it was 2 instead. It was 2 for E.CALL because we'd see both a call to the monitoring API and a call to the Python function that triggered the call to the monitoring API. Instead of having this count of expected calls, the test now uses a Counter class that looks only for calls for a particular CodeLike and ignores all others, which makes it so that the expected count is 1 for everything instead of for everything but CALL.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_monitoring.py
sys.monitoring.register_callback(TEST_TOOL, event, counter)
if event == E.C_RETURN or event == E.C_RAISE:
sys.monitoring.set_events(TEST_TOOL, E.CALL)
event_value = int(math.log2(E.CALL))

@godlygeek godlygeek Oct 6, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewer: previously this would call

self.Scope(self.codelike, math.log2(E.C_RETURN))

which results in an out-of-bounds read inside PyMonitoring_EnterScope. It accesses m->tools[event], and m->tools is an array of 16 elements, and PY_MONITORING_EVENT_C_RETURN is 16 and so one past the end of that array.

This was wrong; you get the C_RETURN event by subscribing to C_CALL events. That's what this fix is.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/legacy_tracing.c Outdated
Comment on lines +112 to +115
if (PyVectorcall_NARGS(nargsf) != 4) {
// PyMonitoring_FireCReturnEvent and PyMonitoring_FireCRaiseEvent
// don't provide the callable.
assert(PyVectorcall_NARGS(nargsf) == 3);

@godlygeek godlygeek Oct 6, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewer: this fixes an assertion error that would be hit on debug builds whenever you call PyMonitoring_FireCReturnEvent or PyMonitoring_FireCRaiseEvent with a sys.setprofile legacy profile function installed.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/legacy_tracing.c
assert(!PyErr_Occurred());
assert(kwnames == NULL);
assert(PyVectorcall_NARGS(nargsf) == 3);
assert(PyCode_Check(args[0]));

@godlygeek godlygeek Oct 6, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewer: this would trigger an assertion error in the tests, where we pass a CodeLike instead of a code object. It only didn't fail up until now because the path wasn't being exercised.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/legacy_tracing.c Outdated
Comment on lines +390 to +393
PyFrameObject *frame = get_top_frame_if_running_code(args[0]);
if (frame == NULL) {
Py_RETURN_NONE;
}

@godlygeek godlygeek Oct 6, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewer: this check is moved up so that we know that the code object we received is actually a PyCodeObject before we cast it to one.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@pablogsal
pablogsal self-requested a review October 11, 2026 17:59
pablogsal
pablogsal reviewed Oct 11, 2026
View reviewed changes
Comment thread Python/legacy_tracing.c Outdated
return NULL;
}
PyObject *res = call_profile_func(self, meth);
PyObject *res = call_profile_func(self, args[0], meth);

@pablogsal pablogsal Oct 11, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check the code mismatch before tp_descr_get? Reporting list.append with arg0=1 still raises TypeError before reaching this guard, preventing delivery to sys.monitoring.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/legacy_tracing.c Outdated
return NULL;
}
PyObject *res = call_profile_func(self, meth);
PyObject *res = call_profile_func(self, args[0], meth);

@pablogsal pablogsal Oct 11, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still does the tp_descr_get before the frame check, so a PyMonitoring_FireCallEvent with a method descriptor and an arg0 of another type fails with a TypeError under sys.setprofile instead of being ignored (and otherwise we create a bound method just to throw it away). I think we should do the frame check at the top of sys_profile_call_or_return. Can you add a test for this case as well?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/legacy_tracing.c Outdated

/* Return the top frame (borrowed) if it's running `code`, else NULL. */
static PyFrameObject *
get_top_frame_if_running_code(PyObject *code)

@pablogsal pablogsal Oct 11, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not mask the legacy tools out in PyMonitoring_EnterScope (or in capi_call_instrumentation) instead? Tool ids 6 and 7 are only used by sys.setprofile/sys.settrace, so something like

state_array[i].active = m->tools[event] & ~LEGACY_TOOLS;

would keep all these handlers untouched, and extensions would not even pay for the call (and the PyEval_GetFrame()) when only legacy tracing is active. Am I missing a case where we want these handlers to run for C API events?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@godlygeek godlygeek Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, that approach didn't occur to me, but I do think it's cleaner.

Am I missing a case where we want these handlers to run for C API events?

The only case I can think of is perhaps if the user has called _PyInterpreterState_SetEvalFrameFunc, and is calling the monitoring C API from their own frame evaluation function? In that case they would have a real frame on the PyEval_GetFrame() stack, and the existing behavior would be right for them.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@godlygeek godlygeek Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the implementation to that. I left the tests alone. Let me know if you'd like to pare down the tests as well, now that all of these events are being ignored in one common place, instead of in many separate places.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@godlygeek
Mask out the legacy monitoring tools instead
e44754a
Instead of making the callbacks associated with the `sys.setprofile`
and `sys.settrace` legacy trace functions detect when they were called
with something that's not a code object or not the code object held by
the top frame on the Python stack, sidestep the problem entirely by
making sure that the legacy profile functions are masked out of the set
of tools to fire events for when using the monitoring C API.
@godlygeek

godlygeek commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

The failed test from the latest run seems unrelated to my changes...

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@pablogsal pablogsal pablogsal left review comments

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting review

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@godlygeek @pablogsal
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.