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 9F56A3858028 for ; Fri, 26 Mar 2021 08:29:47 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 9F56A3858028 Received: from submission (posteo.de [89.146.220.130]) by mout02.posteo.de (Postfix) with ESMTPS id DC8DE2400FC for ; Fri, 26 Mar 2021 09:29:45 +0100 (CET) Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4F6FWs1kjmz6tmf; Fri, 26 Mar 2021 09:29:44 +0100 (CET) From: Michael Weghorn To: gdb-patches@sourceware.org Cc: Michael Weghorn Subject: [PATCH v3] gdb: Do autoload before notifying Python side in new_objfile event Date: Fri, 26 Mar 2021 09:29:33 +0100 Message-Id: <20210326082934.2522904-1-m.weghorn@posteo.de> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210210154053.82927-1-m.weghorn@posteo.de> References: <20210210154053.82927-1-m.weghorn@posteo.de> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-13.3 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, 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: Fri, 26 Mar 2021 08:29:51 -0000 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 &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 */ -- 2.30.2