From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mout01.posteo.de (mout01.posteo.de [185.67.36.65]) by sourceware.org (Postfix) with ESMTPS id 215223858C27 for ; Tue, 20 Apr 2021 11:38:27 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 215223858C27 Received: from submission (posteo.de [89.146.220.130]) by mout01.posteo.de (Postfix) with ESMTPS id 6EFDB24002E for ; Tue, 20 Apr 2021 13:38:25 +0200 (CEST) Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4FPhX04BBPz6tmM for ; Tue, 20 Apr 2021 13:38:24 +0200 (CEST) Subject: [PING 2] [PATCH v3] gdb: Do autoload before notifying Python side in new_objfile event To: gdb-patches@sourceware.org References: <20210210154053.82927-1-m.weghorn@posteo.de> <20210326082934.2522904-1-m.weghorn@posteo.de> From: Michael Weghorn Message-ID: <8ef2bf68-b2c8-57bd-750a-2bd3b831bc6b@posteo.de> Date: Tue, 20 Apr 2021 11:38:19 +0000 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit X-Spam-Status: No, score=-13.6 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 20 Apr 2021 11:38:38 -0000 Ping. On 12/04/2021 15.37, Michael Weghorn via Gdb-patches wrote: > Ping. > > On 26/03/2021 09.29, Michael Weghorn via Gdb-patches wrote: >> Previously, the observers attached to the 'gdb::observers::new_objfile' >> observable were always notified in the order in which they had been >> attached. >> >> The new_objfile observer callback to auto-load scripts is attached in >> '_initialize_auto_load'. >> The new_objfile observer callback that propagates the new_objfile event >> to the Python side is attached in 'gdbpy_initialize_inferior', which is >> called via '_initialize_python'. >> With '_initialize_python' happening before '_initialize_auto_load', >> the consequence was that the new_objfile event was emitted on the Python >> side before autoloaded scripts had been executed when a new objfile was >> loaded. >> As a result, trying to access the objfile's pretty printers (defined in >> the autoloaded script) from a handler for the Python-side >> 'new_objfile' event would fail. Those would only be initialized later on >> (when the 'auto_load_new_objfile' callback was called). >> >> To make sure that the objfile passed to the Python event handler >> is properly initialized (including its 'pretty_printers' member), >> make sure that the 'auto_load_new_objfile' observer is notified >> before the 'python_new_objfile' one that propagates the event >> to the Python side. >> >> To do this, extend the 'observable' class to allow explicitly >> specifying dependencies when attaching observers, by adding >> the possibility to specify a key for the observer, and the >> keys for other observers it depends on, and make use of that >> mechanism to specify a dependency from 'python_new_objfile' >> on 'auto_load_new_objfile'. >> >> To make sure dependencies are notified before observers >> depending on them, the vector holding the observers is sorted in >> a way that dependencies come before observers depending on them. >> The current implementation for sorting uses the depth-first search >> algorithm for topological sorting as described at [1]. >> >> Add a corresponding testcase that involves a test library with an autoloaded >> Python script and a handler for the Python 'new_objfile' event. >> >> (The real world use case where I came across this issue was in an attempt >> to extend handling for GDB pretty printers for dynamically loaded >> objfiles in the Qt Creator IDE, s. [2] and [3] for more background.) >> >> [1] https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search >> [2] https://bugreports.qt.io/browse/QTCREATORBUG-25339 >> [3] https://codereview.qt-project.org/c/qt-creator/qt-creator/+/333857/1 >> >> Tested on x86_64-linux (Debian testing). >> >> gdb/ChangeLog: >> >> * auto-load.c (_initialize_auto_load): Specify key when >> attaching the 'auto_load_new_objfile' observer, so >> other observers can specify it as a dependency. >> * auto-load.h (AUTO_LOAD_H): Declare >> 'auto_load_new_objfile_observer' as key to be used >> for the 'auto_load_new_objfile' observer. >> * python/py-inferior.c (gdbpy_initialize_inferior): Make >> 'python_new_objfile' observer depend on 'auto_load_new_objfile' >> observer, so it gets notified after the latter. >> >> gdb/testsuite/ChangeLog: >> >> * gdb.python/libpy-autoloaded-pretty-printers-in-newobjfile-event.so-gdb.py: New test. >> * gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.cc: New test. >> * gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.h: New test. >> * gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-main.cc: New test. >> * gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.exp: New test. >> * gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.py: New test. >> >> gdbsupport/ChangeLog: >> * observable.h (struct observer_key): New struct that can be >> used as a key when attaching observers, and specyfing >> dependencies to other observers >> (class observable): Extend to allow specifying dependencies >> between observers, keep vector holding observers sorted >> that dependencies are notified before observers depending >> on them. >> --- >> gdb/auto-load.c | 5 +- >> gdb/auto-load.h | 11 ++ >> gdb/python/py-inferior.c | 6 +- >> ...tty-printers-in-newobjfile-event.so-gdb.py | 44 ++++++ >> ...pretty-printers-in-newobjfile-event-lib.cc | 26 ++++ >> ...-pretty-printers-in-newobjfile-event-lib.h | 31 +++++ >> ...retty-printers-in-newobjfile-event-main.cc | 23 ++++ >> ...ed-pretty-printers-in-newobjfile-event.exp | 96 +++++++++++++ >> ...ded-pretty-printers-in-newobjfile-event.py | 34 +++++ >> gdbsupport/observable.h | 127 ++++++++++++++++-- >> 10 files changed, 389 insertions(+), 14 deletions(-) >> create mode 100644 gdb/testsuite/gdb.python/libpy-autoloaded-pretty-printers-in-newobjfile-event.so-gdb.py >> create mode 100644 gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.cc >> create mode 100644 gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.h >> create mode 100644 gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-main.cc >> create mode 100644 gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.exp >> create mode 100644 gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.py >> >> diff --git a/gdb/auto-load.c b/gdb/auto-load.c >> index 1dfcf21eeeb..4ecfdd28f35 100644 >> --- a/gdb/auto-load.c >> +++ b/gdb/auto-load.c >> @@ -1494,6 +1494,8 @@ found and/or loaded."), >> return &retval; >> } >> >> +gdb::observers::observer_key auto_load_new_objfile_observer; >> + >> void _initialize_auto_load (); >> void >> _initialize_auto_load () >> @@ -1503,7 +1505,8 @@ _initialize_auto_load () >> char *guile_name_help; >> const char *suffix; >> >> - gdb::observers::new_objfile.attach (auto_load_new_objfile); >> + gdb::observers::new_objfile.attach (auto_load_new_objfile, >> + &auto_load_new_objfile_observer); >> >> add_setshow_boolean_cmd ("gdb-scripts", class_support, >> &auto_load_gdb_scripts, _("\ >> diff --git a/gdb/auto-load.h b/gdb/auto-load.h >> index f726126c554..e599b862ff9 100644 >> --- a/gdb/auto-load.h >> +++ b/gdb/auto-load.h >> @@ -25,6 +25,13 @@ struct program_space; >> struct auto_load_pspace_info; >> struct extension_language_defn; >> >> +namespace gdb >> +{ >> +namespace observers { >> +struct observer_key; >> +} >> +} >> + >> /* Value of the 'set debug auto-load' configuration variable. */ >> >> extern bool debug_auto_load; >> @@ -40,6 +47,10 @@ extern bool auto_load_local_gdbinit; >> extern char *auto_load_local_gdbinit_pathname; >> extern bool auto_load_local_gdbinit_loaded; >> >> +/* Key used for the auto_load_new_objfile observer, so other observers can >> + * specify it as a dependency. */ >> +extern gdb::observers::observer_key auto_load_new_objfile_observer; >> + >> extern struct auto_load_pspace_info * >> get_auto_load_pspace_data_for_loading (struct program_space *pspace); >> extern void auto_load_objfile_script (struct objfile *objfile, >> diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c >> index a3d5952a10b..6ec3f1599ca 100644 >> --- a/gdb/python/py-inferior.c >> +++ b/gdb/python/py-inferior.c >> @@ -18,6 +18,7 @@ >> along with this program. If not, see . */ >> >> #include "defs.h" >> +#include "auto-load.h" >> #include "gdbcore.h" >> #include "gdbthread.h" >> #include "inferior.h" >> @@ -913,7 +914,10 @@ gdbpy_initialize_inferior (void) >> gdb::observers::memory_changed.attach (python_on_memory_change); >> gdb::observers::register_changed.attach (python_on_register_change); >> gdb::observers::inferior_exit.attach (python_inferior_exit); >> - gdb::observers::new_objfile.attach (python_new_objfile); >> + // needs to run after auto_load_new_objfile observer, so autoloaded >> + // pretty-printers are available >> + gdb::observers::new_objfile.attach (python_new_objfile, nullptr, >> + { &auto_load_new_objfile_observer }); >> gdb::observers::inferior_added.attach (python_new_inferior); >> gdb::observers::inferior_removed.attach (python_inferior_deleted); >> >> diff --git a/gdb/testsuite/gdb.python/libpy-autoloaded-pretty-printers-in-newobjfile-event.so-gdb.py b/gdb/testsuite/gdb.python/libpy-autoloaded-pretty-printers-in-newobjfile-event.so-gdb.py >> new file mode 100644 >> index 00000000000..2327e4a7384 >> --- /dev/null >> +++ b/gdb/testsuite/gdb.python/libpy-autoloaded-pretty-printers-in-newobjfile-event.so-gdb.py >> @@ -0,0 +1,44 @@ >> +# Copyright (C) 2021 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 . >> + >> +# This file is part of the GDB testsuite. It tests that python pretty >> +# printers defined in a python script that is autoloaded have been >> +# registered when a custom event handler for the new_objfile event >> +# is called. >> + >> +import gdb.printing >> + >> + >> +class MyClassTestLibPrinter(object): >> + "Print a MyClassTestLib" >> + >> + def __init__(self, val): >> + self.val = val >> + >> + def to_string(self): >> + return "MyClassTestLib object, id: {}".format(self.val['id']) >> + >> + def display_hint(self): >> + return "string" >> + >> + >> +def build_pretty_printer(): >> + pp = gdb.printing.RegexpCollectionPrettyPrinter( >> + "my_library") >> + pp.add_printer("MyClassTestLib", "^MyClassTestLib$", MyClassTestLibPrinter) >> + return pp >> + >> + >> +gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer()) >> diff --git a/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.cc b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.cc >> new file mode 100644 >> index 00000000000..7e06cf3903f >> --- /dev/null >> +++ b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.cc >> @@ -0,0 +1,26 @@ >> +/* This testcase is part of GDB, the GNU debugger. >> + >> + Copyright 2021 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 . */ >> + >> +#include "py-autoloaded-pretty-printers-in-newobjfile-event-lib.h" >> + >> +MyClassTestLib::MyClassTestLib(int theId) { >> + id = theId; >> +} >> + >> +int MyClassTestLib::getId() { >> + return id; >> +} >> diff --git a/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.h b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.h >> new file mode 100644 >> index 00000000000..fa2501bf6de >> --- /dev/null >> +++ b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-lib.h >> @@ -0,0 +1,31 @@ >> +/* This testcase is part of GDB, the GNU debugger. >> + >> + Copyright 2021 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 . */ >> + >> +#ifndef TESTLIBRARY_H >> +#define TESTLIBRARY_H >> + >> +class MyClassTestLib { >> + >> +public: >> + explicit MyClassTestLib(int theId); >> + int getId(); >> + >> +private: >> + int id; >> +}; >> + >> +#endif // TESTLIBRARY_H >> diff --git a/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-main.cc b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-main.cc >> new file mode 100644 >> index 00000000000..6e66bbe3d43 >> --- /dev/null >> +++ b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event-main.cc >> @@ -0,0 +1,23 @@ >> +/* This testcase is part of GDB, the GNU debugger. >> + >> + Copyright 2021 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 . */ >> + >> +#include "py-autoloaded-pretty-printers-in-newobjfile-event-lib.h" >> + >> +int main() { >> + MyClassTestLib test(1); >> + return 0; /* break to inspect */ >> +} >> diff --git a/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.exp b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.exp >> new file mode 100644 >> index 00000000000..ed1c3230835 >> --- /dev/null >> +++ b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.exp >> @@ -0,0 +1,96 @@ >> +# Copyright (C) 2021 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 . >> + >> +# This file is part of the GDB testsuite. It tests that python pretty >> +# printers defined in a python script that is autoloaded have been >> +# registered when a custom event handler for the new_objfile event >> +# is called. >> + >> +if [use_gdb_stub] { >> + return 0 >> +} >> + >> +load_lib gdb-python.exp >> + >> +set test "py-autoloaded-pretty-printers-in-newobjfile-event" >> +set srcfile_main "${test}-main.cc" >> +set executable_main ${test}-test >> +set binfile_main [standard_output_file ${executable_main}] >> +set srcfile_lib "${test}-lib.cc" >> +set libname "lib${test}" >> +set pyscriptfile_lib "${libname}.so-gdb.py" >> +set binfile_lib [standard_output_file ${libname}.so] >> + >> + >> +# Start with a fresh gdb. >> +gdb_exit >> +gdb_start >> + >> +# Skip all tests if Python scripting is not enabled. >> +if { [skip_python_tests] } { continue } >> + >> +if {[gdb_compile_shlib ${srcdir}/${subdir}/${srcfile_lib} ${binfile_lib} \ >> + [list debug c++ -Wl,-soname,${libname}.so]] != ""} { >> + return -1 >> +} >> + >> +if {[gdb_compile "${srcdir}/${subdir}/${srcfile_main}" "${binfile_main}.o" \ >> + object [list debug c++]] != ""} { >> + return -1 >> +} >> + >> +set testobjdir [standard_output_file {}] >> +if {[gdb_compile "${binfile_main}.o" "${binfile_main}" executable \ >> + [list debug "additional_flags=-L$testobjdir -l${test} \ >> + -Wl,-rpath=$testobjdir"]] != ""} { >> + return -1 >> +} >> + >> +# Start with a fresh gdb. >> +gdb_exit >> +gdb_start >> +gdb_reinitialize_dir $srcdir/$subdir >> + >> +# Make the -gdb.py script available to gdb, it is automatically loaded by >> +# gdb if it is put in the same directory as the library. >> +set remote_python_file_autoload [gdb_remote_download host \ >> + ${srcdir}/${subdir}/${pyscriptfile_lib}] >> + >> +gdb_test_no_output "set auto-load safe-path ${remote_python_file_autoload}" \ >> + "set auto-load safe-path" >> + >> +# load the python file that defines a handler for the new_objfile event, >> +# which will generate the output to check later >> +# (prints information on available pretty-printers for objfile) >> +set remote_python_file [gdb_remote_download host \ >> + ${srcdir}/${subdir}/${test}.py] >> +gdb_test_no_output "source ${remote_python_file}" "load python file" >> + >> +gdb_load ${binfile_main} >> + >> +gdb_test_no_output "set print pretty on" >> + >> +gdb_breakpoint [gdb_get_line_number "break to inspect" ${srcfile_main}] >> + >> +# check that the handler prints output when test library is loaded >> +# and that the pretty printer from autoloaded python file has been registered >> +gdb_test "run" "new_objfile event for test library.* >> +.*number of pretty printers: 1.* >> +.*name of the first pretty printer: my_library.*" >> + >> +# check that pretty printer actually works >> +gdb_test "print test" " .*MyClassTestLib object, id: 1.*" >> + >> +gdb_continue_to_end >> diff --git a/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.py b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.py >> new file mode 100644 >> index 00000000000..924f304fd33 >> --- /dev/null >> +++ b/gdb/testsuite/gdb.python/py-autoloaded-pretty-printers-in-newobjfile-event.py >> @@ -0,0 +1,34 @@ >> +# Copyright (C) 2021 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 . >> + >> +# This file is part of the GDB testsuite. It tests that python pretty >> +# printers defined in a python script that is autoloaded have been >> +# registered when a custom event handler for the new_objfile event >> +# is called. >> + >> +import gdb >> +import os >> + >> + >> +def new_objfile_handler(event): >> + assert(isinstance(event, gdb.NewObjFileEvent)) >> + # only observe the custom test library >> + if os.path.basename(event.new_objfile.filename).find("libpy-autoloaded-pretty-printers-in-newobjfile-event") == 0: >> + print("new_objfile event for test library") >> + assert(len(event.new_objfile.pretty_printers) == 1) >> + print("number of pretty printers: {}".format(len(event.new_objfile.pretty_printers))) >> + print("name of the first pretty printer: {}".format(event.new_objfile.pretty_printers[0].name)) >> + >> +gdb.events.new_objfile.connect(new_objfile_handler) >> diff --git a/gdbsupport/observable.h b/gdbsupport/observable.h >> index 1a9e767ba18..524f98e5a78 100644 >> --- a/gdbsupport/observable.h >> +++ b/gdbsupport/observable.h >> @@ -52,6 +52,13 @@ struct token >> DISABLE_COPY_AND_ASSIGN (token); >> }; >> >> +struct observer_key >> +{ >> + observer_key () = default; >> + >> + DISABLE_COPY_AND_ASSIGN (observer_key); >> +}; >> + >> template >> class observable >> { >> @@ -59,6 +66,22 @@ class observable >> >> typedef std::function func_type; >> >> +private: >> + struct observer_entry >> + { >> + const token *tok; >> + func_type func; >> + observer_key *key; >> + std::vector deps; >> + >> + observer_entry (const token *t, func_type f, observer_key *k, >> + std::vector d) >> + : tok (t), func (f), key (k), deps (d) >> + { >> + } >> + }; >> + >> +public: >> explicit observable (const char *name) >> : m_name (name) >> { >> @@ -67,17 +90,29 @@ class observable >> DISABLE_COPY_AND_ASSIGN (observable); >> >> /* Attach F as an observer to this observable. F cannot be >> - detached. */ >> - void attach (const func_type &f) >> + detached. >> + Optional: >> + * key that can be used to specify a dependency on >> + the attached observer >> + * keys for other observers this observer depends on >> + Dependencies are notified before observers depending on them. */ >> + void attach (const func_type &f, observer_key *key = nullptr, >> + std::vector dependencies = {}) >> { >> - m_observers.emplace_back (nullptr, f); >> + attach (f, nullptr, key, dependencies); >> } >> >> /* Attach F as an observer to this observable. T is a reference to >> - a token that can be used to later remove F. */ >> - void attach (const func_type &f, const token &t) >> + a token that can be used to later remove F. >> + Optional: >> + * key that can be used to specify a dependency on >> + the attached observer >> + * keys for other observers this observer depends on >> + Dependencies are notified before observers depending on them. */ >> + void attach (const func_type &f, const token &t, observer_key *key = nullptr, >> + std::vector dependencies = {}) >> { >> - m_observers.emplace_back (&t, f); >> + attach (f, &t, key, dependencies); >> } >> >> /* Remove observers associated with T from this observable. T is >> @@ -87,10 +122,9 @@ class observable >> { >> auto iter = std::remove_if (m_observers.begin (), >> m_observers.end (), >> - [&] (const std::pair> - func_type> &e) >> + [&] (const observer_entry &e) >> { >> - return e.first == &t; >> + return e.tok == &t; >> }); >> >> m_observers.erase (iter, m_observers.end ()); >> @@ -103,13 +137,82 @@ class observable >> fprintf_unfiltered (gdb_stdlog, "observable %s notify() called\n", >> m_name); >> for (auto &&e : m_observers) >> - e.second (args...); >> + e.func (args...); >> } >> >> private: >> - >> - std::vector> m_observers; >> + std::vector m_observers; >> const char *m_name; >> + >> + /* used for sorting algorithm */ >> + enum class mark >> + { >> + NONE, >> + PERMANENT, >> + TEMPORARY >> + }; >> + >> + /* Helper method for topological sort using depth-first search algorithm */ >> + void visit_for_sorting (std::vector &sorted_elems, >> + std::vector &marks, int index) >> + { >> + if (marks[index] == mark::PERMANENT) >> + return; >> + if (marks[index] == mark::TEMPORARY) >> + error (_("Cyclic dependencies in observers.")); >> + >> + marks[index] = mark::TEMPORARY; >> + >> + for (observer_key *dep : m_observers[index].deps) >> + { >> + auto it_dep >> + = std::find_if (m_observers.begin (), m_observers.end (), >> + [&] (observer_entry e) { return e.key == dep; }); >> + if (it_dep != m_observers.end ()) >> + { >> + int i = std::distance (m_observers.begin (), it_dep); >> + visit_for_sorting (sorted_elems, marks, i); >> + } >> + } >> + >> + marks[index] = mark::PERMANENT; >> + sorted_elems.push_back (m_observers[index]); >> + } >> + >> + /* Sorts the elements, so that dependencies come before observers >> + * depending on them. >> + * >> + * Currently uses depth-first search algorithm for topological sorting, >> + * s. https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search . */ >> + void sort_elements () >> + { >> + std::vector sorted_elems; >> + std::vector marks (m_observers.size (), mark::NONE); >> + >> + auto it = marks.begin(); >> + while (it != marks.end ()) >> + { >> + int index = std::distance (marks.begin (), it); >> + visit_for_sorting (sorted_elems, marks, index); >> + >> + it = std::find_if (marks.begin (), marks.end (), >> + [] (mark m) { return m == mark::NONE; }); >> + } >> + // assign sorted result >> + m_observers = sorted_elems; >> + } >> + >> + void attach (const func_type &f, const token *t, observer_key *key, >> + std::vector dependencies) >> + { >> + m_observers.emplace_back (t, f, key, dependencies); >> + >> + if (key != nullptr) >> + { >> + // other observers might depend on this one -> sort anew >> + sort_elements (); >> + } >> + }; >> }; >> >> } /* namespace observers */ >>