gnucash master: Bug 798778 - GnuCashquits abruptly when attempting to edit optionsâ¦
John Ralls
jralls at code.gnucash.org
Fri Mar 17 13:42:31 EDT 2023
Updated via https://github.com/Gnucash/gnucash/commit/b4b84319 (commit)
from https://github.com/Gnucash/gnucash/commit/144b6ae0 (commit)
commit b4b843198421aef9332ad1cae348a4acacfa5586
Author: John Ralls <jralls at ceridwen.us>
Date: Thu Mar 16 17:50:06 2023 -0700
Bug 798778 - GnuCashquits abruptly when attempting to edit optionsâ¦
for certain reports.
Those reports being ones using complex options, apparently because
the callbacks weren't protected from Guile's garbage collector.
So replace the anyway ugly hack of a void* with a std::any wrapping
a class holding a std::unique_ptr with a custom deleter. The
constructor calls scm_gc_protect_object on the SCM containing the
callback and the custom deleter calls scm_gc_unprotect_object. The
copy constructor, required for std::any, makes a new std::unique_ptr
and calls scm_gc_protect_object again ensuring that the protect and
unprotect calls are symmetrical.
Meanwhile std::any hides the Guile dependency from all the classes
that don't need to know about it. The only ugliness is that there's
no good place to put a common implementation of SCNCallbackWrapper so it's
repeated in gnc-optiondb.i and dialog-options.cpp.
diff --git a/bindings/guile/gnc-optiondb.i b/bindings/guile/gnc-optiondb.i
index ce5c9433c8..2343daf49a 100644
--- a/bindings/guile/gnc-optiondb.i
+++ b/bindings/guile/gnc-optiondb.i
@@ -1614,7 +1614,27 @@ inline SCM return_scm_value(ValueType value)
};
+%ignore SCMDeleter;
+%ignore SCMCalbackWrapper;
+
%inline %{
+
+ struct SCMDeleter
+ {
+ void operator()(SCM cb) {
+ scm_gc_unprotect_object(cb);
+ }
+ };
+
+ class SCMCallbackWrapper
+ {
+ std::unique_ptr<scm_unused_struct, SCMDeleter> m_callback;
+ public:
+ SCMCallbackWrapper(SCM cb) : m_callback{scm_gc_protect_object(cb)} {}
+ SCMCallbackWrapper(const SCMCallbackWrapper& cbw) : m_callback{scm_gc_protect_object(cbw.get())} {}
+ SCM get() const { return m_callback.get(); }
+ };
+
/**
* Create a new complex boolean option and register it in the options database.
*
@@ -1633,7 +1653,7 @@ void gnc_register_complex_boolean_option(GncOptionDBPtr& db,
{
GncOption option{section, name, key, doc_string, value,
GncOptionUIType::BOOLEAN};
- option.set_widget_changed (widget_changed_cb);
+ option.set_widget_changed(std::make_any<SCMCallbackWrapper>(widget_changed_cb));
db->register_option(section, std::move(option));
}
@@ -1666,7 +1686,7 @@ gnc_register_multichoice_callback_option(GncOptionDBPtr& db,
std::get<0>(choices.at(0)));
GncOption option{GncOptionMultichoiceValue{section, name, key, doc_string,
defval.c_str(), std::move(choices)}};
- option.set_widget_changed(widget_changed_cb);
+ option.set_widget_changed(std::make_any<SCMCallbackWrapper>(widget_changed_cb));
db->register_option(section, std::move(option));
}
diff --git a/gnucash/gnome-utils/dialog-options.cpp b/gnucash/gnome-utils/dialog-options.cpp
index cec82663f1..af93d9822a 100644
--- a/gnucash/gnome-utils/dialog-options.cpp
+++ b/gnucash/gnome-utils/dialog-options.cpp
@@ -42,6 +42,7 @@
#include "gnc-session.h" // for gnc_get_current_session
#include "gnc-ui.h" // for DF_MANUAL
+#include <any>
#include <iostream>
#include <sstream>
@@ -119,18 +120,39 @@ GncOptionsDialog::changed() noexcept
set_sensitive(true);
}
+struct SCMDeleter {
+ void operator()(SCM cb) { scm_gc_unprotect_object(cb); }
+};
+
+class SCMCallbackWrapper
+{
+ std::unique_ptr<scm_unused_struct, SCMDeleter> m_callback;
+public:
+ SCMCallbackWrapper(SCM cb) : m_callback{scm_gc_protect_object(cb)} {}
+ SCMCallbackWrapper(const SCMCallbackWrapper& cbw) : m_callback{scm_gc_protect_object(cbw.get())} {}
+ SCM get() const { return m_callback.get(); }
+};
+
void
gnc_option_changed_widget_cb(GtkWidget *widget, GncOption* option)
{
if (!option || option->is_internal()) return;
auto ui_item{option->get_ui_item()};
g_return_if_fail(ui_item);
- auto widget_changed_cb{option->get_widget_changed()};
+ auto& widget_changed_cb{option->get_widget_changed()};
auto gtk_ui_item{dynamic_cast<GncOptionGtkUIItem*>(ui_item)};
- if (widget_changed_cb && gtk_ui_item)
+ if (widget_changed_cb.has_value() && gtk_ui_item)
{
- SCM widget_value{gtk_ui_item->get_widget_scm_value(*option)};
- scm_call_1(static_cast<SCM>(widget_changed_cb), widget_value);
+ try
+ {
+ auto cb{std::any_cast<SCMCallbackWrapper>(widget_changed_cb)};
+ SCM widget_value{gtk_ui_item->get_widget_scm_value(*option)};
+ scm_call_1(cb.get(), widget_value);
+ }
+ catch(std::bad_any_cast& err)
+ {
+ PERR("Bad widget changed callback type %s", err.what());
+ }
}
const_cast<GncOptionUIItem*>(ui_item)->set_dirty(true);
dialog_changed_internal(widget, true);
diff --git a/libgnucash/engine/gnc-option.hpp b/libgnucash/engine/gnc-option.hpp
index f561414a0d..b2f9ffb8a7 100644
--- a/libgnucash/engine/gnc-option.hpp
+++ b/libgnucash/engine/gnc-option.hpp
@@ -34,9 +34,11 @@
#define GNC_OPTION_HPP_
#include <glib.h>
+#include <any>
#include <string>
#include <iostream>
#include <iomanip>
+#include <type_traits>
#include <variant>
#include <memory>
#include <tuple>
@@ -199,14 +201,13 @@ public:
*/
std::istream& in_stream(std::istream& iss);
friend GncOptionVariant& swig_get_option(GncOption*);
- void set_widget_changed (void* cb) { m_widget_changed = cb; }
- void* get_widget_changed () { return m_widget_changed; }
+ void set_widget_changed (std::any cb) { m_widget_changed = cb; }
+ std::any& get_widget_changed () { return m_widget_changed; }
private:
inline static const std::string c_empty_string{""};
GncOptionVariantPtr m_option;
GncOptionUIItemPtr m_ui_item{nullptr};
-/* This is a hack to reserve space for a Guile callback pointer. */
- void* m_widget_changed{};
+ std::any m_widget_changed{};
};
inline bool
Summary of changes:
bindings/guile/gnc-optiondb.i | 24 ++++++++++++++++++++++--
gnucash/gnome-utils/dialog-options.cpp | 30 ++++++++++++++++++++++++++----
libgnucash/engine/gnc-option.hpp | 9 +++++----
3 files changed, 53 insertions(+), 10 deletions(-)
More information about the gnucash-changes
mailing list