diff options
Diffstat (limited to 'gdb/testsuite/gdb.python')
-rw-r--r-- | gdb/testsuite/gdb.python/gdb_leak_detector.py | 120 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-breakpoint.exp | 2 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-color-leak.exp | 28 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-color-leak.py | 31 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-color.exp | 3 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-inferior-leak.exp | 14 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-inferior-leak.py | 108 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-read-memory-leak.exp | 14 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-read-memory-leak.py | 84 |
9 files changed, 217 insertions, 187 deletions
diff --git a/gdb/testsuite/gdb.python/gdb_leak_detector.py b/gdb/testsuite/gdb.python/gdb_leak_detector.py new file mode 100644 index 0000000..b0f6d47 --- /dev/null +++ b/gdb/testsuite/gdb.python/gdb_leak_detector.py @@ -0,0 +1,120 @@ +# Copyright (C) 2021-2025 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Defines a base class, which can be sub-classed, in order to run +# memory leak tests on some aspects of GDB's Python API. See the +# comments on the gdb_leak_detector class for more details. + +import os +import tracemalloc +import gdb + + +# This class must be sub-classed to create a memory leak test. The +# sub-classes __init__ method should call the parent classes __init__ +# method, and the sub-class should override allocate() and +# deallocate(). See the comments on the various methods below for +# more details of required arguments and expected usage. +class gdb_leak_detector: + + # Class initialisation. FILENAME is the file in which the + # sub-class is defined, usually passed as just '__file__'. This + # is used when looking for memory allocations; only allocations in + # FILENAME are considered. + def __init__(self, filename): + self.filters = [tracemalloc.Filter(True, "*" + os.path.basename(filename))] + + # Internal helper function to actually run the test. Calls the + # allocate() method to allocate an object from GDB's Python API. + # When CLEAR is True the object will then be deallocated by + # calling deallocate(), otherwise, deallocate() is not called. + # + # Finally, this function checks for any memory allocatios + # originating from 'self.filename' that have not been freed, and + # returns the total (in bytes) of the memory that has been + # allocated, but not freed. + def _do_test(self, clear): + # Start tracing, and take a snapshot of the current allocations. + tracemalloc.start() + snapshot1 = tracemalloc.take_snapshot() + + # Generate the GDB Python API object by calling the allocate + # method. + self.allocate() + + # Possibly clear the reference to the allocated object. + if clear: + self.deallocate() + + # Now grab a second snapshot of memory allocations, and stop + # tracing memory allocations. + snapshot2 = tracemalloc.take_snapshot() + tracemalloc.stop() + + # Filter the snapshots; we only care about allocations originating + # from this file. + snapshot1 = snapshot1.filter_traces(self.filters) + snapshot2 = snapshot2.filter_traces(self.filters) + + # Compare the snapshots, this leaves only things that were + # allocated, but not deallocated since the first snapshot. + stats = snapshot2.compare_to(snapshot1, "traceback") + + # Total up all the allocated things. + total = 0 + for stat in stats: + total += stat.size_diff + return total + + # Run the memory leak test. Prints 'PASS' if successful, + # otherwise, raises an exception (of type GdbError). + def run(self): + # The first time we run this some global state will be allocated which + # shows up as memory that is allocated, but not released. So, run the + # test once and discard the result. + self._do_test(True) + + # Now run the test twice, the first time we clear our global reference + # to the allocated object, which should allow Python to deallocate the + # object. The second time we hold onto the global reference, preventing + # Python from performing the deallocation. + bytes_with_clear = self._do_test(True) + bytes_without_clear = self._do_test(False) + + # If there are any allocations left over when we cleared the reference + # (and expected deallocation) then this indicates a leak. + if bytes_with_clear > 0: + raise gdb.GdbError("memory leak when object reference was released") + + # If there are no allocations showing when we hold onto a reference, + # then this likely indicates that the testing infrastructure is broken, + # and we're no longer spotting the allocations at all. + if bytes_without_clear == 0: + raise gdb.GdbError("object is unexpectedly not showing as allocated") + + # Print a PASS message that the TCL script can see. + print("PASS") + + # Sub-classes must override this method. Allocate an object (or + # multiple objects) from GDB's Python API. Store references to + # these objects within SELF. + def allocate(self): + raise NotImplementedError("allocate() not implemented") + + # Sub-classes must override this method. Deallocate the object(s) + # allocated by the allocate() method. All that is required is for + # the references created in allocate() to be set to None. + def deallocate(self): + raise NotImplementedError("allocate() not implemented") diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp index 1b9c05f..9a901a3 100644 --- a/gdb/testsuite/gdb.python/py-breakpoint.exp +++ b/gdb/testsuite/gdb.python/py-breakpoint.exp @@ -707,7 +707,7 @@ proc_with_prefix test_bkpt_explicit_loc {} { delete_breakpoints gdb_test "python bp1 = gdb.Breakpoint (line=bp1)" \ - "RuntimeError.*: Line keyword should be an integer or a string.*" \ + "RuntimeError.*: Line keyword should be an integer or a string\\.\r\n.*" \ "set explicit breakpoint by invalid line type" delete_breakpoints diff --git a/gdb/testsuite/gdb.python/py-color-leak.exp b/gdb/testsuite/gdb.python/py-color-leak.exp new file mode 100644 index 0000000..6d7fa7c --- /dev/null +++ b/gdb/testsuite/gdb.python/py-color-leak.exp @@ -0,0 +1,28 @@ +# Copyright (C) 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This file is part of the GDB testsuite. It checks for memory leaks +# associated with allocating gdb.Color objects. + +load_lib gdb-python.exp + +require allow_python_tests + +standard_testfile + +clean_restart + +gdb_py_run_memory_leak_test ${srcdir}/${subdir}/${testfile}.py \ + "gdb.Color object deallocates correctly" diff --git a/gdb/testsuite/gdb.python/py-color-leak.py b/gdb/testsuite/gdb.python/py-color-leak.py new file mode 100644 index 0000000..50dc315 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-color-leak.py @@ -0,0 +1,31 @@ +# Copyright (C) 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import gdb_leak_detector + + +class color_leak_detector(gdb_leak_detector.gdb_leak_detector): + def __init__(self): + super().__init__(__file__) + self.color = None + + def allocate(self): + self.color = gdb.Color("red") + + def deallocate(self): + self.color = None + + +color_leak_detector().run() diff --git a/gdb/testsuite/gdb.python/py-color.exp b/gdb/testsuite/gdb.python/py-color.exp index c6f1041..1b8e0c5 100644 --- a/gdb/testsuite/gdb.python/py-color.exp +++ b/gdb/testsuite/gdb.python/py-color.exp @@ -13,8 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# This file is part of the GDB testsuite. -# It tests gdb.parameter and gdb.Parameter. +# This file is part of the GDB testsuite. It tests gdb.Color. load_lib gdb-python.exp diff --git a/gdb/testsuite/gdb.python/py-inferior-leak.exp b/gdb/testsuite/gdb.python/py-inferior-leak.exp index 6710f59..15216ee 100644 --- a/gdb/testsuite/gdb.python/py-inferior-leak.exp +++ b/gdb/testsuite/gdb.python/py-inferior-leak.exp @@ -24,15 +24,5 @@ standard_testfile clean_restart -# Skip this test if the tracemalloc module is not available. -if { ![gdb_py_module_available "tracemalloc"] } { - unsupported "tracemalloc module not available" - return -} - -set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] - -# Source the Python script, this runs the test (which is written -# completely in Python), and either prints PASS, or throws an -# exception. -gdb_test "source ${pyfile}" "PASS" "source python script" +gdb_py_run_memory_leak_test ${srcdir}/${subdir}/${testfile}.py \ + "gdb.Inferior object deallocates correctly" diff --git a/gdb/testsuite/gdb.python/py-inferior-leak.py b/gdb/testsuite/gdb.python/py-inferior-leak.py index 97837dc..38f33c3 100644 --- a/gdb/testsuite/gdb.python/py-inferior-leak.py +++ b/gdb/testsuite/gdb.python/py-inferior-leak.py @@ -14,99 +14,31 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import re -import tracemalloc +import gdb_leak_detector -import gdb -# A global variable in which we store a reference to the gdb.Inferior -# object sent to us in the new_inferior event. -inf = None +class inferior_leak_detector(gdb_leak_detector.gdb_leak_detector): + def __init__(self): + super().__init__(__file__) + self.inferior = None + self.__handler = lambda event: setattr(self, "inferior", event.inferior) + gdb.events.new_inferior.connect(self.__handler) + def __del__(self): + gdb.events.new_inferior.disconnect(self.__handler) -# Register the new_inferior event handler. -def new_inferior_handler(event): - global inf - inf = event.inferior + def allocate(self): + output = gdb.execute("add-inferior", False, True) + m = re.search(r"Added inferior (\d+)", output) + if m: + num = int(m.group(1)) + else: + raise RuntimeError("no match") + gdb.execute("remove-inferiors %s" % num) -gdb.events.new_inferior.connect(new_inferior_handler) + def deallocate(self): + self.inferior = None -# A global filters list, we only care about memory allocations -# originating from this script. -filters = [tracemalloc.Filter(True, "*py-inferior-leak.py")] - -# Add a new inferior, and return the number of the new inferior. -def add_inferior(): - output = gdb.execute("add-inferior", False, True) - m = re.search(r"Added inferior (\d+)", output) - if m: - num = int(m.group(1)) - else: - raise RuntimeError("no match") - return num - - -# Run the test. When CLEAR is True we clear the global INF variable -# before comparing the before and after memory allocation traces. -# When CLEAR is False we leave INF set to reference the gdb.Inferior -# object, thus preventing the gdb.Inferior from being deallocated. -def test(clear): - global filters, inf - - # Start tracing, and take a snapshot of the current allocations. - tracemalloc.start() - snapshot1 = tracemalloc.take_snapshot() - - # Create an inferior, this triggers the new_inferior event, which - # in turn holds a reference to the new gdb.Inferior object in the - # global INF variable. - num = add_inferior() - gdb.execute("remove-inferiors %s" % num) - - # Possibly clear the global INF variable. - if clear: - inf = None - - # Now grab a second snapshot of memory allocations, and stop - # tracing memory allocations. - snapshot2 = tracemalloc.take_snapshot() - tracemalloc.stop() - - # Filter the snapshots; we only care about allocations originating - # from this file. - snapshot1 = snapshot1.filter_traces(filters) - snapshot2 = snapshot2.filter_traces(filters) - - # Compare the snapshots, this leaves only things that were - # allocated, but not deallocated since the first snapshot. - stats = snapshot2.compare_to(snapshot1, "traceback") - - # Total up all the deallocated things. - total = 0 - for stat in stats: - total += stat.size_diff - return total - - -# The first time we run this some global state will be allocated which -# shows up as memory that is allocated, but not released. So, run the -# test once and discard the result. -test(True) - -# Now run the test twice, the first time we clear our global reference -# to the gdb.Inferior object, which should allow Python to deallocate -# the object. The second time we hold onto the global reference, -# preventing Python from performing the deallocation. -bytes_with_clear = test(True) -bytes_without_clear = test(False) - -# The bug that used to exist in GDB was that even when we released the -# global reference the gdb.Inferior object would not be deallocated. -if bytes_with_clear > 0: - raise gdb.GdbError("memory leak when gdb.Inferior should be released") -if bytes_without_clear == 0: - raise gdb.GdbError("gdb.Inferior object is no longer allocated") - -# Print a PASS message that the test script can see. -print("PASS") +inferior_leak_detector().run() diff --git a/gdb/testsuite/gdb.python/py-read-memory-leak.exp b/gdb/testsuite/gdb.python/py-read-memory-leak.exp index 0015a57..9ae5eb8 100644 --- a/gdb/testsuite/gdb.python/py-read-memory-leak.exp +++ b/gdb/testsuite/gdb.python/py-read-memory-leak.exp @@ -30,15 +30,5 @@ if ![runto_main] { return -1 } -# Skip this test if the tracemalloc module is not available. -if { ![gdb_py_module_available "tracemalloc"] } { - unsupported "tracemalloc module not available" - return -} - -set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] - -# Source the Python script, this runs the test (which is written -# completely in Python), and either prints PASS, or throws an -# exception. -gdb_test "source ${pyfile}" "PASS" "source python script" +gdb_py_run_memory_leak_test ${srcdir}/${subdir}/${testfile}.py \ + "buffers returned by read_memory() deallocates correctly" diff --git a/gdb/testsuite/gdb.python/py-read-memory-leak.py b/gdb/testsuite/gdb.python/py-read-memory-leak.py index 348403d..71edf47 100644 --- a/gdb/testsuite/gdb.python/py-read-memory-leak.py +++ b/gdb/testsuite/gdb.python/py-read-memory-leak.py @@ -13,81 +13,21 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os -import tracemalloc +import gdb_leak_detector -import gdb -# A global variable in which we store a reference to the memory buffer -# returned from gdb.Inferior.read_memory(). -mem_buf = None +class read_leak_detector(gdb_leak_detector.gdb_leak_detector): + def __init__(self): + super().__init__(__file__) + self.mem_buf = None + self.addr = gdb.parse_and_eval("px") + self.inf = gdb.inferiors()[0] + def allocate(self): + self.mem_buf = self.inf.read_memory(self.addr, 4096) -# A global filters list, we only care about memory allocations -# originating from this script. -filters = [tracemalloc.Filter(True, "*" + os.path.basename(__file__))] + def deallocate(self): + self.mem_buf = None -# Run the test. When CLEAR is True we clear the global INF variable -# before comparing the before and after memory allocation traces. -# When CLEAR is False we leave INF set to reference the gdb.Inferior -# object, thus preventing the gdb.Inferior from being deallocated. -def test(clear): - global filters, mem_buf - - addr = gdb.parse_and_eval("px") - inf = gdb.inferiors()[0] - - # Start tracing, and take a snapshot of the current allocations. - tracemalloc.start() - snapshot1 = tracemalloc.take_snapshot() - - # Read from the inferior, this allocate a memory buffer object. - mem_buf = inf.read_memory(addr, 4096) - - # Possibly clear the global INF variable. - if clear: - mem_buf = None - - # Now grab a second snapshot of memory allocations, and stop - # tracing memory allocations. - snapshot2 = tracemalloc.take_snapshot() - tracemalloc.stop() - - # Filter the snapshots; we only care about allocations originating - # from this file. - snapshot1 = snapshot1.filter_traces(filters) - snapshot2 = snapshot2.filter_traces(filters) - - # Compare the snapshots, this leaves only things that were - # allocated, but not deallocated since the first snapshot. - stats = snapshot2.compare_to(snapshot1, "traceback") - - # Total up all the allocated things. - total = 0 - for stat in stats: - total += stat.size_diff - return total - - -# The first time we run this some global state will be allocated which -# shows up as memory that is allocated, but not released. So, run the -# test once and discard the result. -test(True) - -# Now run the test twice, the first time we clear our global reference -# to the memory buffer object, which should allow Python to deallocate -# the object. The second time we hold onto the global reference, -# preventing Python from performing the deallocation. -bytes_with_clear = test(True) -bytes_without_clear = test(False) - -# The bug that used to exist in GDB was that even when we released the -# global reference the gdb.Inferior object would not be deallocated. -if bytes_with_clear > 0: - raise gdb.GdbError("memory leak when memory buffer should be released") -if bytes_without_clear == 0: - raise gdb.GdbError("memory buffer object is no longer allocated") - -# Print a PASS message that the test script can see. -print("PASS") +read_leak_detector().run() |