Skip to content

Align debugger hook dispatch and callback lifetimes - #9722

Draft
youknowdot wants to merge 7 commits into
RustPython:mainfrom
youknowdot:fix/debugger-hook-controls-20261104
Draft

youknowdot wants to merge 7 commits into
RustPython:mainfrom
youknowdot:fix/debugger-hook-controls-20261104

Conversation

@youknowdot

Copy link
Copy Markdown
Contributor

Summary

Align breakpoint and sys.breakpointhook callback controls in 122 changed lines across two production Rust files.

  • Select sys.breakpointhook directly from the sys dictionary and audit the selected callable before invoking it. Preserve the selection if an audit hook replaces it, and distinguish missing from a present None.
  • Retain raw positional and keyword arguments until after the selected/resolved hook is released, including call failures. Use FuncArgs directly rather than reconstructing it through a second argument converter.
  • Respect ignore_environment before reading PYTHONBREAKPOINT, preserve disabled/default handling, and reject leading-dot names without attempting import.
  • Reuse import_from with an empty list, discard the importer result and retrieve the exact registered module rather than using a top-level package or arbitrary importer return. Retain the from-list through module lookup.
  • Warn only for ImportError during import and AttributeError during hook lookup. Propagate other exceptions and warning failures. Release the imported module before calling the resolved hook.

Reference: CPython d220409a13b77de7a6c30abe16ea90db01528edc, builtin breakpoint and sys.breakpointhook.

Validation

Independent exact-patch source review passed. The original bounded reflection/debugger CPython oracle passed 64 cases and 36 assertions; independent additional checks passed 13 cases and 25 assertions covering error subclasses, warning-as-error, importer result disposal, exact module lookup and retained from-list cleanup. Stub hooks were used throughout; no debugger was launched. These establish reference behavior and source reasoning, not RustPython runtime results.

Final threaded host-env/VM/stdlib cargo check passed in 1m07s and all-targets Clippy in 4m09s. All 12 baseline diagnostics match unchanged, without suppression. Normal commit hooks passed. Compiler-reported configuration, dictionary-key and dynamic-name API mismatches were corrected and independently re-reviewed before these final checks.

Limits and integration

Native RustPython execution was not performed because the required execution guard has not been verified. Full workspace/C-API suites and separate platform/feature graphs remain unrun; hosted CI is pending. No tests, Lib files, assertions, expected-failure markers or workflows were changed.

This is not full PyImport_Import parity: current VM import helpers still differ in globals/locals and builtins selection, and interpreter-internal versus reassigned sys.modules behavior remains separate. Environment encoding and universal caller-tail argument destruction order are not claimed. Raw owners are retained through internal hook cleanup.

Relevant #9720 print/input, #9690 shutdown and #9612 tracing patches were inspected and do not overlap these modified hunks. This branch is based directly on main dc84268 and does not include those unrelated changes.

AI-assisted contribution by OpenAI Codex; assistance is disclosed in commit trailers.

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Comment thread crates/vm/src/stdlib/builtins.rs Outdated
Comment on lines +800 to +802
// Audit callbacks can replace sys.breakpointhook. Invoke the selected
// hook, retaining the original arguments until that hook is released.
let result = hook.call(args.clone(), vm);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why should we do that manually?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The lifetime requirement is to keep the original argument references alive while releasing the selected hook. PyObject::call takes FuncArgs by value, so hook.call(args, vm) can release those references before hook is dropped. If an audit callback or the hook itself replaces sys.breakpointhook, the selected hook may then be finalized, and its __del__ can observe whether the arguments are still alive. CPython borrows the caller's argument vector and decrefs the selected hook before returning. I checked both replacement cases on CPython 3.15.0: the hook's finalizer runs while the argument is still alive.

The explicit drops are not necessary for that ordering here, though. Rust drops the local hook before the parameter args at scope exit, so this block can simply end with:

hook.call(args.clone(), vm)

The clone retains PyObjectRef owners; it does not copy the Python objects. Keeping those owners is the relevant part with the current consuming call API. The explicit drop(hook); drop(args); sequence is redundant in this block.

youknowdot and others added 7 commits November 6, 2026 22:47
Audit the selected breakpoint hook, honor isolated environment policy, preserve exact module selection and selective errors, and retain callback owners through cleanup.

Assisted-by: OpenAI Codex:model version not exposed
Keep debugger hook behavior unchanged while correcting configuration access and borrowing the string payload for module lookup.

Assisted-by: OpenAI Codex:model version not exposed
Use the existing PyStr attribute conversion for dynamic environment-derived names.

Assisted-by: OpenAI Codex:model version not exposed
Reuse one PyImport_Import-style helper for debugger and C API callers, preserving fresh globals lookup, generic builtins access and explicit cleanup ordering without changing ordinary import helpers.

Assisted-by: OpenAI Codex:model version not exposed
Keep the original argument owners with args.clone() and let Rust drop the local hook before the function arguments at scope exit.

Assisted-by: OpenAI Codex:model version not exposed
Bind retained argument owners before the selected hook so scope cleanup releases the hook first. Keep the clone at the call site and consume the original arguments, preserving reference counts without explicit drops or redundant_clone diagnostics.

Assisted-by: OpenAI Codex:model version not exposed
Keep retained owners outside lock scopes and preserve callback-sensitive release ordering.

Assisted-by: OpenAI Codex:model version not exposed
@youknowdot
youknowdot force-pushed the fix/debugger-hook-controls-20261104 branch from 7eccf92 to 04721cc Compare November 6, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants