From 58d7a1886886c46b3aa5eaa7ea4faa34a9831f2e Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 17 Jan 2025 10:33:04 -0500 Subject: [PATCH 1/4] [3.12] gh-58956: Fix a frame refleak in bdb (GH-128190) (cherry picked from commit 767c89ba7c5a70626df6e75eb56b546bf911b997) Co-authored-by: Tian Gao --- Lib/bdb.py | 12 ++ Lib/test/test_pdb.py | 116 ++++++++++++++++++ ...4-12-23-02-09-44.gh-issue-58956.4OdMdT.rst | 1 + 3 files changed, 129 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst diff --git a/Lib/bdb.py b/Lib/bdb.py index 196e6b178cb9fd..691eeff9fbe792 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -339,7 +339,12 @@ def set_trace(self, frame=None): frame.f_trace = self.trace_dispatch self.botframe = frame frame = frame.f_back +<<<<<<< HEAD self.set_step() +======= + self.set_stepinstr() + self.enterframe = None +>>>>>>> 767c89ba7c5... gh-58956: Fix a frame refleak in bdb (#128190) sys.settrace(self.trace_dispatch) def set_continue(self): @@ -356,6 +361,13 @@ def set_continue(self): while frame and frame is not self.botframe: del frame.f_trace frame = frame.f_back +<<<<<<< HEAD +======= + for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items(): + frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes + self.frame_trace_lines_opcodes = {} + self.enterframe = None +>>>>>>> 767c89ba7c5... gh-58956: Fix a frame refleak in bdb (#128190) def set_quit(self): """Set quitting attribute to True. diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index bd61de0ad3494c..0d6b827956dc8d 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -1981,6 +1981,122 @@ def test_pdb_ambiguous_statements(): (Pdb) continue """ +def test_pdb_f_trace_lines(): + """GH-80675 + + pdb should work even if f_trace_lines is set to False on some frames. + + >>> reset_Breakpoint() + + >>> def test_function(): + ... import sys + ... frame = sys._getframe() + ... frame.f_trace_lines = False + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... if frame.f_trace_lines != False: + ... print("f_trace_lines is not reset after continue!") + + >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE + ... 'continue' + ... ]): + ... test_function() + > (5)test_function() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) continue + """ + +def test_pdb_frame_refleak(): + """ + pdb should not leak reference to frames + + >>> def frame_leaker(container): + ... import sys + ... container.append(sys._getframe()) + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... pass + + >>> def test_function(): + ... import gc + ... container = [] + ... frame_leaker(container) # c + ... print(len(gc.get_referrers(container[0]))) + ... container = [] + ... frame_leaker(container) # n c + ... print(len(gc.get_referrers(container[0]))) + ... container = [] + ... frame_leaker(container) # r c + ... print(len(gc.get_referrers(container[0]))) + + >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE + ... 'continue', + ... 'next', + ... 'continue', + ... 'return', + ... 'continue', + ... ]): + ... test_function() + > (4)frame_leaker() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) continue + 1 + > (4)frame_leaker() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) next + > (5)frame_leaker() + -> pass + (Pdb) continue + 1 + > (4)frame_leaker() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) return + --Return-- + > (5)frame_leaker()->None + -> pass + (Pdb) continue + 1 + """ + +def test_pdb_function_break(): + """Testing the line number of break on function + + >>> def foo(): pass + + >>> def bar(): + ... + ... pass + + >>> def boo(): + ... # comments + ... global x + ... x = 1 + + >>> def gen(): + ... yield 42 + + >>> def test_function(): + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + + >>> with PdbTestInput([ # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + ... 'break foo', + ... 'break bar', + ... 'break boo', + ... 'break gen', + ... 'continue' + ... ]): + ... test_function() + > (2)test_function() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) break foo + Breakpoint ... at :1 + (Pdb) break bar + Breakpoint ... at :3 + (Pdb) break boo + Breakpoint ... at :4 + (Pdb) break gen + Breakpoint ... at :2 + (Pdb) continue + """ + def test_pdb_issue_gh_65052(): """See GH-65052 diff --git a/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst b/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst new file mode 100644 index 00000000000000..b78bc5aaf44217 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-12-23-02-09-44.gh-issue-58956.4OdMdT.rst @@ -0,0 +1 @@ +Fixed a frame reference leak in :mod:`bdb`. From 056361ffa6cff689c4273458586b231ea9c16733 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 17 Jan 2025 12:56:25 -0500 Subject: [PATCH 2/4] Fix conflicts and remove extra tests --- Lib/bdb.py | 10 ------- Lib/test/test_pdb.py | 65 -------------------------------------------- 2 files changed, 75 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 691eeff9fbe792..3486deacd86a7c 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -339,12 +339,8 @@ def set_trace(self, frame=None): frame.f_trace = self.trace_dispatch self.botframe = frame frame = frame.f_back -<<<<<<< HEAD self.set_step() -======= - self.set_stepinstr() self.enterframe = None ->>>>>>> 767c89ba7c5... gh-58956: Fix a frame refleak in bdb (#128190) sys.settrace(self.trace_dispatch) def set_continue(self): @@ -361,13 +357,7 @@ def set_continue(self): while frame and frame is not self.botframe: del frame.f_trace frame = frame.f_back -<<<<<<< HEAD -======= - for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items(): - frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes - self.frame_trace_lines_opcodes = {} self.enterframe = None ->>>>>>> 767c89ba7c5... gh-58956: Fix a frame refleak in bdb (#128190) def set_quit(self): """Set quitting attribute to True. diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 0d6b827956dc8d..eb5f5ee927a852 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -1981,30 +1981,6 @@ def test_pdb_ambiguous_statements(): (Pdb) continue """ -def test_pdb_f_trace_lines(): - """GH-80675 - - pdb should work even if f_trace_lines is set to False on some frames. - - >>> reset_Breakpoint() - - >>> def test_function(): - ... import sys - ... frame = sys._getframe() - ... frame.f_trace_lines = False - ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() - ... if frame.f_trace_lines != False: - ... print("f_trace_lines is not reset after continue!") - - >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE - ... 'continue' - ... ]): - ... test_function() - > (5)test_function() - -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() - (Pdb) continue - """ - def test_pdb_frame_refleak(): """ pdb should not leak reference to frames @@ -2056,47 +2032,6 @@ def test_pdb_frame_refleak(): 1 """ -def test_pdb_function_break(): - """Testing the line number of break on function - - >>> def foo(): pass - - >>> def bar(): - ... - ... pass - - >>> def boo(): - ... # comments - ... global x - ... x = 1 - - >>> def gen(): - ... yield 42 - - >>> def test_function(): - ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() - - >>> with PdbTestInput([ # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - ... 'break foo', - ... 'break bar', - ... 'break boo', - ... 'break gen', - ... 'continue' - ... ]): - ... test_function() - > (2)test_function() - -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() - (Pdb) break foo - Breakpoint ... at :1 - (Pdb) break bar - Breakpoint ... at :3 - (Pdb) break boo - Breakpoint ... at :4 - (Pdb) break gen - Breakpoint ... at :2 - (Pdb) continue - """ - def test_pdb_issue_gh_65052(): """See GH-65052 From e7e50375e2f9ce46f8d1aab504e4d6bfe5482f2f Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 17 Jan 2025 12:58:47 -0500 Subject: [PATCH 3/4] Clear curframe_locals --- Lib/pdb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/pdb.py b/Lib/pdb.py index 1e1b5ea4f0a184..2a6e994dac1073 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -281,6 +281,7 @@ def forget(self): if hasattr(self, 'curframe') and self.curframe: self.curframe.f_globals.pop('__pdb_convenience_variables', None) self.curframe = None + self.curframe_locals = {} self.tb_lineno.clear() def setup(self, f, tb): From 7121fb40d5511f0dac76e3538e5207c6ed9acbb4 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 17 Jan 2025 13:07:30 -0500 Subject: [PATCH 4/4] 3.12 has a different breakpoint --- Lib/test/test_pdb.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index eb5f5ee927a852..778aa03a63ab63 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2011,19 +2011,20 @@ def test_pdb_frame_refleak(): ... 'continue', ... ]): ... test_function() - > (4)frame_leaker() - -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + > (5)frame_leaker() + -> pass (Pdb) continue 1 - > (4)frame_leaker() - -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() - (Pdb) next > (5)frame_leaker() -> pass + (Pdb) next + --Return-- + > (5)frame_leaker()->None + -> pass (Pdb) continue 1 - > (4)frame_leaker() - -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + > (5)frame_leaker() + -> pass (Pdb) return --Return-- > (5)frame_leaker()->None