Skip to content

gh-154751: Keep the screen alive in curses.initscr() after newterm() - #154752

Merged
serhiy-storchaka merged 5 commits into
python:mainfrom
fedonman:fix-curses-initscr-screen-ref
Jul 27, 2026
Merged

gh-154751: Keep the screen alive in curses.initscr() after newterm()#154752
serhiy-storchaka merged 5 commits into
python:mainfrom
fedonman:fix-curses-initscr-screen-ref

Conversation

@fedonman

@fedonman fedonman commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

curses.initscr() called while a screen from curses.newterm() was current returned a
window built with no screen. The window's screen field is the reference that keeps a
screen alive, so this window kept nothing alive. Dropping the screen freed its WINDOW,
and the next method call on the window read freed memory:

import curses, gc, os
s1 = os.openpty()[1]; s2 = os.openpty()[1]
a = curses.newterm('xterm', s1, s1)
b = curses.newterm('xterm', s2, s2)
curses.set_term(a)
w = curses.initscr()
curses.set_term(b)
del a; gc.collect()
w.addstr(0, 0, 'boom')     # Segmentation fault, rc=139

When the current screen came from newterm(), initscr() now returns that screen's own
standard window, so there is only ever one wrapper over that WINDOW and it holds the
screen. Otherwise it passes state->topscreen, matching newwin(), newpad() and
getwin(); that value is NULL for the screen created by initscr(), which has no screen
object, so plain initscr() use is unchanged.

This restores what the screen objects documentation promises: "Each window keeps its screen
alive, so a screen remains valid as long as any of its windows does."

newterm() and set_term() are new in 3.16 (gh-90092, GH-151748), so no released version
is affected and there is no NEWS entry.

The reproducer above now prints no crash and exits 0, and
curses.initscr() is screen.stdscr is True where it was False. Tests:

$ ./python -m test -u all -v test_curses -m 'test_initscr_after_newterm_keeps_screen_alive'
test_initscr_after_newterm_keeps_screen_alive (test.test_curses.ScreenTests.test_initscr_after_newterm_keeps_screen_alive) ... ok
OK

$ ./python -m test -u all test_curses
Total tests: run=164 skipped=3
Result: SUCCESS

…erm()

When initscr() was called while a screen from newterm() was current, it
returned a window created with no screen. The window therefore did not hold a
reference to the screen it belonged to, so the screen could be collected while
the window was still in use, and the next method call on the window read freed
memory.

It now passes the current screen, like newwin(), newpad() and getwin() already
do. That is NULL for the screen created by initscr(), which has no screen
object, so that case is unchanged.
@serhiy-storchaka
serhiy-storchaka self-requested a review July 27, 2026 12:06
Comment thread Modules/_cursesmodule.c
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@serhiy-storchaka

Copy link
Copy Markdown
Member

Returning the screen's existing window instead would fix that too

And this is right.

Please add a test for this case.

Comment thread Misc/NEWS.d/next/Library/2026-07-26-21-11-58.gh-issue-154751.q-YeEM.rst Outdated
fedonman added 2 commits July 27, 2026 20:00
The test now checks the identity the review asked for. The NEWS entry goes
away because set_term(), newterm() and this code path are all new in 3.16 and
unreleased, so no user could observe the crash.

@serhiy-storchaka serhiy-storchaka left a comment

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.

LGTM. 👍

@serhiy-storchaka
serhiy-storchaka merged commit 92efaff into python:main Jul 27, 2026
54 checks passed
@fedonman
fedonman deleted the fix-curses-initscr-screen-ref branch July 27, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants