From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mout02.posteo.de (mout02.posteo.de [185.67.36.66]) by sourceware.org (Postfix) with ESMTPS id 69D3A3857001 for ; Mon, 12 Apr 2021 13:37:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 69D3A3857001 Received: from submission (posteo.de [89.146.220.130]) by mout02.posteo.de (Postfix) with ESMTPS id 752D32400FD for ; Mon, 12 Apr 2021 15:37:21 +0200 (CEST) Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4FJqXw4s5zz6tmc for ; Mon, 12 Apr 2021 15:37:20 +0200 (CEST) Subject: [PING] [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: Date: Mon, 12 Apr 2021 15:37:16 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.9.0 MIME-Version: 1.0 In-Reply-To: <20210326082934.2522904-1-m.weghorn@posteo.de> 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: Mon, 12 Apr 2021 13:37:28 -0000 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 */ >