From e3af2f22f9aec70131dc2da8d9c9eceade1a52ad Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sun, 22 Aug 2021 11:17:28 +0800 Subject: [PATCH 1/6] [gnc-commodity.c] cache user_symbol into commodity struct Continuation of ff2ceb111 which introduced issue whereby user_symbol returned could become stale, leading to invalid read fixed with c398bef59. There are likely other user_symbol pointers becoming stale without this commit. This change will save the user_symbol into the commodity struct, avoids gchar* becoming stale. --- libgnucash/engine/gnc-commodity.c | 52 ++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/libgnucash/engine/gnc-commodity.c b/libgnucash/engine/gnc-commodity.c index 56763e7a03..745be68bc6 100644 --- a/libgnucash/engine/gnc-commodity.c +++ b/libgnucash/engine/gnc-commodity.c @@ -76,6 +76,7 @@ typedef struct gnc_commodityPrivate const char *cusip; /* CUSIP or other identifying code */ int fraction; char *unique_name; + char *user_symbol; gboolean quote_flag; /* user wants price quotes */ gnc_quote_source *quote_source; /* current/old source of quotes */ @@ -89,6 +90,9 @@ typedef struct gnc_commodityPrivate const char *default_symbol; } gnc_commodityPrivate; +static const char* +is_unset = "unset"; + #define GET_PRIVATE(o) \ ((gnc_commodityPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_COMMODITY)) @@ -667,6 +671,7 @@ gnc_commodity_init(gnc_commodity* com) priv->quote_flag = 0; priv->quote_source = NULL; priv->quote_tz = CACHE_INSERT(""); + priv->user_symbol = (char*) is_unset; reset_printname(priv); reset_unique_name(priv); @@ -951,6 +956,10 @@ commodity_free(gnc_commodity * cm) g_free(priv->unique_name); priv->unique_name = NULL; + if (priv->user_symbol != is_unset) + g_free (priv->user_symbol); + priv->user_symbol = NULL; + #ifdef ACCOUNTS_CLEANED_UP /* Account objects are not actually cleaned up when a book is closed (in fact * a memory leak), but commodities are, so in currently this warning gets hit @@ -1182,14 +1191,17 @@ gnc_commodity_get_quote_tz(const gnc_commodity *cm) const char* gnc_commodity_get_user_symbol(const gnc_commodity *cm) { - GValue v = G_VALUE_INIT; - static char* retval = NULL; - if (!cm) return NULL; - qof_instance_get_kvp (QOF_INSTANCE(cm), &v, 1, "user_symbol"); - g_free (retval); - retval = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v): NULL; - g_value_unset (&v); - return retval; + gnc_commodityPrivate* priv; + g_return_val_if_fail (GNC_IS_COMMODITY (cm), NULL); + priv = GET_PRIVATE(cm); + if (priv->user_symbol == is_unset) + { + GValue v = G_VALUE_INIT; + qof_instance_get_kvp (QOF_INSTANCE(cm), &v, 1, "user_symbol"); + priv->user_symbol = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL; + g_value_unset (&v); + } + return priv->user_symbol; } /******************************************************************** @@ -1477,13 +1489,13 @@ void gnc_commodity_set_user_symbol(gnc_commodity * cm, const char * user_symbol) { struct lconv *lc; - GValue v = G_VALUE_INIT; + gnc_commodityPrivate* priv; + if (!cm) return; + priv = GET_PRIVATE(cm); ENTER ("(cm=%p, symbol=%s)", cm, user_symbol ? user_symbol : "(null)"); - gnc_commodity_begin_edit(cm); - lc = gnc_localeconv(); if (!user_symbol || !*user_symbol) user_symbol = NULL; @@ -1494,15 +1506,33 @@ gnc_commodity_set_user_symbol(gnc_commodity * cm, const char * user_symbol) user_symbol = NULL; else if (!g_strcmp0(user_symbol, gnc_commodity_get_default_symbol(cm))) user_symbol = NULL; + + if (priv->user_symbol != is_unset) + { + if (!g_strcmp0 (user_symbol, priv->user_symbol)) + { + LEAVE ("gnc_commodity_set_user_symbol: no change"); + return; + } + g_free (priv->user_symbol); + } + + gnc_commodity_begin_edit (cm); + if (user_symbol) { + GValue v = G_VALUE_INIT; g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, user_symbol); qof_instance_set_kvp (QOF_INSTANCE(cm), &v, 1, "user_symbol"); + priv->user_symbol = g_strdup (user_symbol); g_value_unset (&v); } else + { qof_instance_set_kvp (QOF_INSTANCE(cm), NULL, 1, "user_symbol"); + priv->user_symbol = NULL; + } mark_commodity_dirty(cm); gnc_commodity_commit_edit(cm); From 3bf49ed8d7c806a550d07fc2d803b64c2e78f196 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Mon, 23 Aug 2021 18:05:08 +0800 Subject: [PATCH 2/6] [Account.cpp] GValue string must be unset --- libgnucash/engine/Account.cpp | 89 +++++++++++++++++++++++++++++++---- libgnucash/engine/AccountP.h | 14 ++++++ 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/libgnucash/engine/Account.cpp b/libgnucash/engine/Account.cpp index ba09427581..14e4dfd4be 100644 --- a/libgnucash/engine/Account.cpp +++ b/libgnucash/engine/Account.cpp @@ -286,6 +286,8 @@ mark_account (Account *acc) /********************************************************************\ \********************************************************************/ +static constexpr const char* is_unset {"unset"}; + /* GObject Initialization */ G_DEFINE_TYPE_WITH_PRIVATE(Account, gnc_account, QOF_TYPE_INSTANCE) @@ -323,6 +325,13 @@ gnc_account_init(Account* acc) priv->starting_reconciled_balance = gnc_numeric_zero(); priv->balance_dirty = FALSE; + priv->color == is_unset; + priv->sort_order == is_unset; + priv->notes == is_unset; + priv->filter == is_unset; + priv->equity_type == TriState::Unset; + priv->sort_reversed == TriState::Unset; + priv->splits = NULL; priv->sort_dirty = FALSE; } @@ -1365,9 +1374,23 @@ xaccFreeAccount (Account *acc) qof_string_cache_remove(priv->description); priv->accountName = priv->accountCode = priv->description = nullptr; + if (priv->color != is_unset) + g_free (priv->color); + if (priv->sort_order != is_unset) + g_free (priv->sort_order); + if (priv->notes != is_unset) + g_free (priv->notes); + if (priv->filter != is_unset) + g_free (priv->filter); + /* zero out values, just in case stray * pointers are pointing here. */ + priv->color == nullptr; + priv->sort_order == nullptr; + priv->notes == nullptr; + priv->filter == nullptr; + priv->parent = nullptr; priv->children = nullptr; @@ -2476,36 +2499,52 @@ set_kvp_string_tag (Account *acc, const char *tag, const char *value) xaccAccountCommitEdit(acc); } -static const char* +static char* get_kvp_string_tag (const Account *acc, const char *tag) { GValue v = G_VALUE_INIT; if (acc == NULL || tag == NULL) return NULL; qof_instance_get_path_kvp (QOF_INSTANCE (acc), &v, {tag}); - return G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL; + auto retval = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL; + g_value_unset (&v); + return retval; } void xaccAccountSetColor (Account *acc, const char *str) { + auto priv = GET_PRIVATE (acc); + if (priv->color != is_unset) + g_free (priv->color); + priv->color = g_strdup (str); set_kvp_string_tag (acc, "color", str); } void xaccAccountSetFilter (Account *acc, const char *str) { + auto priv = GET_PRIVATE (acc); + if (priv->filter != is_unset) + g_free (priv->filter); + priv->filter = g_strdup (str); set_kvp_string_tag (acc, "filter", str); } void xaccAccountSetSortOrder (Account *acc, const char *str) { + auto priv = GET_PRIVATE (acc); + if (priv->sort_order != is_unset) + g_free (priv->sort_order); + priv->sort_order = g_strdup (str); set_kvp_string_tag (acc, "sort-order", str); } void xaccAccountSetSortReversed (Account *acc, gboolean sortreversed) { + auto priv = GET_PRIVATE (acc); + priv->sort_reversed = sortreversed ? TriState::True : TriState::False; set_kvp_string_tag (acc, "sort-reversed", sortreversed ? "true" : NULL); } @@ -2530,6 +2569,10 @@ qofAccountSetParent (Account *acc, QofInstance *parent) void xaccAccountSetNotes (Account *acc, const char *str) { + auto priv = GET_PRIVATE (acc); + if (priv->notes != is_unset) + g_free (priv->notes); + priv->notes = g_strdup (str); set_kvp_string_tag (acc, "notes", str); } @@ -3250,21 +3293,30 @@ const char * xaccAccountGetColor (const Account *acc) { g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL); - return get_kvp_string_tag (acc, "color"); + auto priv = GET_PRIVATE (acc); + if (priv->color == is_unset) + priv->color = get_kvp_string_tag (acc, "color"); + return priv->color; } const char * xaccAccountGetFilter (const Account *acc) { g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0); - return get_kvp_string_tag (acc, "filter"); + auto priv = GET_PRIVATE (acc); + if (priv->filter == is_unset) + priv->filter = get_kvp_string_tag (acc, "filter"); + return priv->filter; } const char * xaccAccountGetSortOrder (const Account *acc) { g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0); - return get_kvp_string_tag (acc, "sort-order"); + auto priv = GET_PRIVATE (acc); + if (priv->sort_order == is_unset) + priv->sort_order = get_kvp_string_tag (acc, "sort-order"); + return priv->sort_order; } gboolean @@ -3272,14 +3324,25 @@ xaccAccountGetSortReversed (const Account *acc) { g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE); - return g_strcmp0 (get_kvp_string_tag (acc, "sort-reversed"), "true") == 0; + auto priv = GET_PRIVATE (acc); + if (priv->sort_reversed == TriState::Unset) + { + auto sort_reversed = get_kvp_string_tag (acc, "sort-reversed"); + priv->sort_reversed = g_strcmp0 (sort_reversed, "true") ? + TriState::False : TriState::True; + g_free (sort_reversed); + } + return (priv->sort_reversed == TriState::True); } const char * xaccAccountGetNotes (const Account *acc) { g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL); - return get_kvp_string_tag (acc, "notes"); + auto priv = GET_PRIVATE (acc); + if (priv->notes == is_unset) + priv->notes = get_kvp_string_tag (acc, "notes"); + return priv->notes; } gnc_commodity * @@ -4142,7 +4205,15 @@ xaccAccountGetIsOpeningBalance (const Account *acc) { if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY) return false; - return g_strcmp0(get_kvp_string_tag(acc, "equity-type"), "opening-balance") == 0; + auto priv = GET_PRIVATE(acc); + if (priv->equity_type == TriState::Unset) + { + auto equity_type = get_kvp_string_tag (acc, "equity-type"); + priv->equity_type = g_strcmp0 (equity_type, "true") ? + TriState::False : TriState::True; + g_free (equity_type); + } + return (priv->equity_type == TriState::True); } void @@ -4150,6 +4221,8 @@ xaccAccountSetIsOpeningBalance (Account *acc, gboolean val) { if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY) return; + auto priv = GET_PRIVATE (acc); + priv->equity_type = val ? TriState::True : TriState::False; set_kvp_string_tag(acc, "equity-type", val ? "opening-balance" : ""); } diff --git a/libgnucash/engine/AccountP.h b/libgnucash/engine/AccountP.h index 746a1c5040..250232206d 100644 --- a/libgnucash/engine/AccountP.h +++ b/libgnucash/engine/AccountP.h @@ -55,6 +55,13 @@ extern "C" { * No one outside of the engine should ever include this file. */ +typedef enum +{ + Unset = -1, + False, + True +} TriState; + /** \struct Account */ typedef struct AccountPrivate { @@ -122,6 +129,13 @@ typedef struct AccountPrivate LotList *lots; /* list of lot pointers */ GNCPolicy *policy; /* Cached pointer to policy method */ + TriState sort_reversed; + TriState equity_type; + char *notes; + char *color; + char *sort_order; + char *filter; + /* The "mark" flag can be used by the user to mark this account * in any way desired. Handy for specialty traversals of the * account tree. */ From ac2afc7e13babd4c850e3831318a705f9996feef Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Mon, 23 Aug 2021 18:44:23 +0800 Subject: [PATCH 3/6] [gncInvoice.c] GValue string must be unset --- libgnucash/engine/gncInvoice.c | 41 +++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/libgnucash/engine/gncInvoice.c b/libgnucash/engine/gncInvoice.c index 6ab155f813..f3a5c5db12 100644 --- a/libgnucash/engine/gncInvoice.c +++ b/libgnucash/engine/gncInvoice.c @@ -65,6 +65,8 @@ struct _gncInvoice time64 date_opened; time64 date_posted; + char *doclink; + gnc_numeric to_charge_amount; gnc_commodity *currency; @@ -282,6 +284,9 @@ impl_get_typed_referring_object_list (const QofInstance* inst, const QofInstance return qof_instance_get_referring_object_list_from_collection (qof_instance_get_collection (inst), ref); } +static const char* +is_unset = "unset"; + static void gnc_invoice_class_init (GncInvoiceClass *klass) { @@ -327,6 +332,7 @@ GncInvoice *gncInvoiceCreate (QofBook *book) invoice->active = TRUE; invoice->to_charge_amount = gnc_numeric_zero (); + invoice->doclink = (char*) is_unset; qof_event_gen (&invoice->inst, QOF_EVENT_CREATE, NULL); @@ -372,6 +378,8 @@ GncInvoice *gncInvoiceCopy (const GncInvoice *from) // Oops. Do not forget to copy the pointer to the correct currency here. invoice->currency = from->currency; + invoice->doclink = from->doclink; + // Copy all invoice->entries for (node = from->entries; node; node = node->next) { @@ -428,6 +436,9 @@ static void gncInvoiceFree (GncInvoice *invoice) if (invoice->terms) gncBillTermDecRef (invoice->terms); + if (invoice->doclink != is_unset) + g_free (invoice->doclink); + /* qof_instance_release (&invoice->inst); */ g_object_unref (invoice); } @@ -538,15 +549,29 @@ void gncInvoiceSetNotes (GncInvoice *invoice, const char *notes) void gncInvoiceSetDocLink (GncInvoice *invoice, const char *doclink) { if (!invoice || !doclink) return; + + if (invoice->doclink != is_unset) + { + if (!g_strcmp0 (doclink, invoice->doclink)) + return; + + g_free (invoice->doclink); + } + gncInvoiceBeginEdit (invoice); - if (g_strcmp0 (doclink, "") == 0) + + if (doclink[0] == '\0') + { + invoice->doclink = NULL; qof_instance_set_kvp (QOF_INSTANCE (invoice), NULL, 1, GNC_INVOICE_DOCLINK); + } else { GValue v = G_VALUE_INIT; g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, doclink); qof_instance_set_kvp (QOF_INSTANCE (invoice), &v, 1, GNC_INVOICE_DOCLINK); + invoice->doclink = g_strdup (doclink); g_value_unset (&v); } qof_instance_set_dirty (QOF_INSTANCE(invoice)); @@ -864,12 +889,16 @@ const char * gncInvoiceGetNotes (const GncInvoice *invoice) const char * gncInvoiceGetDocLink (const GncInvoice *invoice) { - GValue v = G_VALUE_INIT; if (!invoice) return NULL; - qof_instance_get_kvp (QOF_INSTANCE(invoice), &v, 1, GNC_INVOICE_DOCLINK); - if (G_VALUE_HOLDS_STRING(&v)) - return g_value_get_string (&v); - return NULL; + if (invoice->doclink == is_unset) + { + GValue v = G_VALUE_INIT; + GncInvoice *inv = (GncInvoice*) invoice; + qof_instance_get_kvp (QOF_INSTANCE(invoice), &v, 1, GNC_INVOICE_DOCLINK); + inv->doclink = G_VALUE_HOLDS_STRING(&v) ? g_value_dup_string (&v) : NULL; + g_value_unset (&v); + } + return invoice->doclink; } GncOwnerType gncInvoiceGetOwnerType (const GncInvoice *invoice) From 0ecbcb4ef1701732872a205779633e3e36160bbf Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Mon, 23 Aug 2021 23:21:46 +0800 Subject: [PATCH 4/6] [gnc-lot.c] GValue string must be unset --- libgnucash/engine/gnc-lot.c | 56 ++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/libgnucash/engine/gnc-lot.c b/libgnucash/engine/gnc-lot.c index d1b411782a..a8eaf79025 100644 --- a/libgnucash/engine/gnc-lot.c +++ b/libgnucash/engine/gnc-lot.c @@ -86,6 +86,9 @@ typedef struct GNCLotPrivate /* List of splits that belong to this lot. */ SplitList *splits; + char *title; + char *notes; + GncInvoice *cached_invoice; /* Handy cached value to indicate if lot is closed. */ /* If value is negative, then the cache is invalid. */ @@ -103,6 +106,9 @@ typedef struct GNCLotPrivate /* ============================================================= */ +static char* +is_unset = "unset"; + /* GObject Initialization */ G_DEFINE_TYPE_WITH_PRIVATE(GNCLot, gnc_lot, QOF_TYPE_INSTANCE) @@ -116,6 +122,8 @@ gnc_lot_init(GNCLot* lot) priv->splits = NULL; priv->cached_invoice = NULL; priv->is_closed = LOT_CLOSED_UNKNOWN; + priv->title = is_unset; + priv->notes = is_unset; priv->marker = 0; } @@ -295,6 +303,14 @@ gnc_lot_free(GNCLot* lot) if (priv->account && !qof_instance_get_destroying(priv->account)) xaccAccountRemoveLot (priv->account, lot); + if (priv->notes != is_unset) + g_free (priv->notes); + + if (priv->title != is_unset) + g_free (priv->title); + + priv->notes = NULL; + priv->title = NULL; priv->account = NULL; priv->is_closed = TRUE; /* qof_instance_release (&lot->inst); */ @@ -439,30 +455,46 @@ gint gnc_lot_count_splits (const GNCLot *lot) const char * gnc_lot_get_title (const GNCLot *lot) { - GValue v = G_VALUE_INIT; + GNCLotPrivate* priv; if (!lot) return NULL; - qof_instance_get_kvp (QOF_INSTANCE (lot), &v, 1, "title"); - if (G_VALUE_HOLDS_STRING (&v)) - return g_value_get_string (&v); - return NULL; + priv = GET_PRIVATE (lot); + if (priv->title == is_unset) + { + GNCLotPrivate* priv = GET_PRIVATE (lot); + GValue v = G_VALUE_INIT; + qof_instance_get_kvp (QOF_INSTANCE (lot), &v, 1, "title"); + priv->title = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL; + g_value_unset (&v); + } + return priv->title; } const char * gnc_lot_get_notes (const GNCLot *lot) { - GValue v = G_VALUE_INIT; + GNCLotPrivate* priv; if (!lot) return NULL; - qof_instance_get_kvp (QOF_INSTANCE (lot), &v, 1, "notes"); - if (G_VALUE_HOLDS_STRING (&v)) - return g_value_get_string (&v); - return NULL; + priv = GET_PRIVATE (lot); + if (priv->notes == is_unset) + { + GValue v = G_VALUE_INIT; + qof_instance_get_kvp (QOF_INSTANCE (lot), &v, 1, "notes"); + priv->notes = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL; + g_value_unset (&v); + } + return priv->notes; } void gnc_lot_set_title (GNCLot *lot, const char *str) { GValue v = G_VALUE_INIT; + GNCLotPrivate* priv; if (!lot) return; + priv = GET_PRIVATE (lot); + if (priv->title != is_unset) + g_free (priv->title); + qof_begin_edit(QOF_INSTANCE(lot)); g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, str); @@ -476,7 +508,11 @@ void gnc_lot_set_notes (GNCLot *lot, const char *str) { GValue v = G_VALUE_INIT; + GNCLotPrivate* priv; if (!lot) return; + priv = GET_PRIVATE (lot); + if (priv->notes != is_unset) + g_free (priv->notes); qof_begin_edit(QOF_INSTANCE(lot)); g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, str); From a47bee97d634204a742e05e02c18c5ad5107ff00 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Mon, 23 Aug 2021 23:22:06 +0800 Subject: [PATCH 5/6] [Split.c] GValue string must be unset --- libgnucash/engine/Split.c | 35 +++++++++++++++++++++++++++-------- libgnucash/engine/SplitP.h | 2 ++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/libgnucash/engine/Split.c b/libgnucash/engine/Split.c index 9392ddd339..d771e1f8bf 100644 --- a/libgnucash/engine/Split.c +++ b/libgnucash/engine/Split.c @@ -97,6 +97,10 @@ enum }; +static const char * is_unset = "unset"; +static const char * split_type_normal = "normal"; +static const char * split_type_stock_split = "stock-split"; + /* GObject Initialization */ G_DEFINE_TYPE(Split, gnc_split, QOF_TYPE_INSTANCE) @@ -116,6 +120,7 @@ gnc_split_init(Split* split) split->value = gnc_numeric_zero(); split->date_reconciled = 0; + split->split_type = is_unset; split->balance = gnc_numeric_zero(); split->cleared_balance = gnc_numeric_zero(); @@ -714,6 +719,7 @@ xaccFreeSplit (Split *split) split->lot = NULL; split->acc = NULL; split->orig_acc = NULL; + split->split_type = NULL; split->date_reconciled = 0; G_OBJECT_CLASS (QOF_INSTANCE_GET_CLASS (&split->inst))->dispose(G_OBJECT (split)); @@ -1968,14 +1974,26 @@ xaccSplitGetBook (const Split *split) const char * xaccSplitGetType(const Split *s) { - GValue v = G_VALUE_INIT; - const char *split_type = NULL; - if (!s) return NULL; - qof_instance_get_kvp (QOF_INSTANCE (s), &v, 1, "split-type"); - if (G_VALUE_HOLDS_STRING (&v)) - split_type = g_value_get_string (&v); - return split_type ? split_type : "normal"; + if (s->split_type == is_unset) + { + GValue v = G_VALUE_INIT; + Split *split = (Split*) s; + const char* type; + qof_instance_get_kvp (QOF_INSTANCE (s), &v, 1, "split-type"); + type = G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL; + if (!type || !g_strcmp0 (type, split_type_normal)) + split->split_type = (char*) split_type_normal; + else if (!g_strcmp0 (type, split_type_stock_split)) + split->split_type = (char*) split_type_stock_split; + else + { + PERR ("unexpected split-type %s, reset to normal.", type); + split->split_type = split_type_normal; + } + g_value_unset (&v); + } + return s->split_type; } /* reconfigure a split to be a stock split - after this, you shouldn't @@ -1988,7 +2006,8 @@ xaccSplitMakeStockSplit(Split *s) s->value = gnc_numeric_zero(); g_value_init (&v, G_TYPE_STRING); - g_value_set_string (&v, "stock-split"); + g_value_set_static_string (&v, split_type_stock_split); + s->split_type = split_type_stock_split; qof_instance_set_kvp (QOF_INSTANCE (s), &v, 1, "split-type"); SET_GAINS_VDIRTY(s); mark_split(s); diff --git a/libgnucash/engine/SplitP.h b/libgnucash/engine/SplitP.h index 335db91746..4192cc5dde 100644 --- a/libgnucash/engine/SplitP.h +++ b/libgnucash/engine/SplitP.h @@ -115,6 +115,8 @@ struct split_s gnc_numeric value; gnc_numeric amount; + const gchar * split_type; + /* -------------------------------------------------------------- */ /* Below follow some 'temporary' fields */ From 001b34616f0b0b9450ae55ca0effab4ae9f55c5a Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Tue, 24 Aug 2021 21:38:16 +0800 Subject: [PATCH 6/6] [Transaction.c] GValue string must be unset --- libgnucash/engine/Transaction.c | 111 ++++++++++++++++++++----------- libgnucash/engine/TransactionP.h | 4 ++ 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/libgnucash/engine/Transaction.c b/libgnucash/engine/Transaction.c index 4890c2f015..7f12f80a21 100644 --- a/libgnucash/engine/Transaction.c +++ b/libgnucash/engine/Transaction.c @@ -255,6 +255,9 @@ void gen_event_trans (Transaction *trans) } } +static const char* +is_unset = "unset"; + /* GObject Initialization */ G_DEFINE_TYPE(Transaction, gnc_transaction, QOF_TYPE_INSTANCE) @@ -274,6 +277,9 @@ gnc_transaction_init(Transaction* trans) trans->readonly_reason = NULL; trans->reason_cache_valid = FALSE; trans->isClosingTxn_cached = -1; + trans->notes = (char*) is_unset; + trans->doclink = (char*) is_unset; + trans->void_reason = (char*) is_unset; LEAVE (" "); } @@ -813,6 +819,12 @@ xaccFreeTransaction (Transaction *trans) CACHE_REMOVE(trans->num); CACHE_REMOVE(trans->description); g_free (trans->readonly_reason); + if (trans->doclink != is_unset) + g_free (trans->doclink); + if (trans->void_reason != is_unset) + g_free (trans->void_reason); + if (trans->notes != is_unset) + g_free (trans->notes); /* Just in case someone looks up freed memory ... */ trans->num = (char *) 1; @@ -821,6 +833,9 @@ xaccFreeTransaction (Transaction *trans) trans->date_posted = 0; trans->readonly_reason = NULL; trans->reason_cache_valid = FALSE; + trans->doclink = NULL; + trans->notes = NULL; + trans->void_reason = NULL; if (trans->orig) { xaccFreeTransaction (trans->orig); @@ -2189,12 +2204,24 @@ void xaccTransSetDocLink (Transaction *trans, const char *doclink) { if (!trans || !doclink) return; + + if (trans->doclink != is_unset) + { + if (!g_strcmp0 (doclink, trans->doclink)) + return; + + g_free (trans->doclink); + } xaccTransBeginEdit(trans); - if (g_strcmp0 (doclink, "") == 0) + if (doclink[0] == '\0') + { + trans->doclink = NULL; qof_instance_set_kvp (QOF_INSTANCE (trans), NULL, 1, doclink_uri_str); + } else { GValue v = G_VALUE_INIT; + trans->doclink = g_strdup (doclink); g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, doclink); qof_instance_set_kvp (QOF_INSTANCE (trans), &v, 1, doclink_uri_str); @@ -2217,10 +2244,18 @@ xaccTransSetNotes (Transaction *trans, const char *notes) { GValue v = G_VALUE_INIT; if (!trans || !notes) return; + if (trans->notes != is_unset) + { + if (!g_strcmp0 (notes, trans->notes)) + return; + + g_free (trans->notes); + } g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, notes); xaccTransBeginEdit(trans); + trans->notes = g_strdup (notes); qof_instance_set_kvp (QOF_INSTANCE (trans), &v, 1, trans_notes_str); qof_instance_set_dirty(QOF_INSTANCE(trans)); g_value_unset (&v); @@ -2382,23 +2417,31 @@ xaccTransGetDescription (const Transaction *trans) const char * xaccTransGetDocLink (const Transaction *trans) { - GValue v = G_VALUE_INIT; - if (!trans) return NULL; - qof_instance_get_kvp (QOF_INSTANCE (trans), &v, 1, doclink_uri_str); - if (G_VALUE_HOLDS_STRING (&v)) - return g_value_get_string (&v); - return NULL; + g_return_val_if_fail (trans, NULL); + if (trans->doclink == is_unset) + { + GValue v = G_VALUE_INIT; + Transaction *t = (Transaction*) trans; + qof_instance_get_kvp (QOF_INSTANCE (trans), &v, 1, doclink_uri_str); + t->doclink = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL; + g_value_unset (&v); + } + return trans->doclink; } const char * xaccTransGetNotes (const Transaction *trans) { - GValue v = G_VALUE_INIT; - if (!trans) return NULL; - qof_instance_get_kvp (QOF_INSTANCE (trans), &v, 1, trans_notes_str); - if (G_VALUE_HOLDS_STRING (&v)) - return g_value_get_string (&v); - return NULL; + g_return_val_if_fail (trans, NULL); + if (trans->notes == is_unset) + { + GValue v = G_VALUE_INIT; + Transaction *t = (Transaction*) trans; + qof_instance_get_kvp (QOF_INSTANCE (trans), &v, 1, trans_notes_str); + t->notes = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL; + g_value_unset (&v); + } + return trans->notes; } gboolean @@ -2746,6 +2789,9 @@ xaccTransVoid(Transaction *trans, const char *reason) qof_instance_set_kvp (QOF_INSTANCE (trans), &v, 1, trans_notes_str); g_value_set_string (&v, reason); qof_instance_set_kvp (QOF_INSTANCE (trans), &v, 1, void_reason_str); + if (trans->void_reason != is_unset) + g_free (trans->void_reason); + trans->void_reason = g_strdup (reason); gnc_time64_to_iso8601_buff (gnc_time(NULL), iso8601_str); g_value_set_string (&v, iso8601_str); @@ -2762,31 +2808,23 @@ xaccTransVoid(Transaction *trans, const char *reason) gboolean xaccTransGetVoidStatus(const Transaction *trans) { - const char *s = NULL; - GValue v = G_VALUE_INIT; - gboolean retval = FALSE; - g_return_val_if_fail(trans, FALSE); - - qof_instance_get_kvp (QOF_INSTANCE (trans), &v, 1, void_reason_str); - if (G_VALUE_HOLDS_STRING (&v)) - { - s = g_value_get_string (&v); - retval = (s && (s[0] != '\0')); - } - g_value_unset (&v); - return retval; + const char *s = xaccTransGetVoidReason (trans); + return (s && *s); } const char * xaccTransGetVoidReason(const Transaction *trans) { - GValue v = G_VALUE_INIT; - g_return_val_if_fail(trans, FALSE); - - qof_instance_get_kvp (QOF_INSTANCE (trans), &v, 1, void_reason_str); - if (G_VALUE_HOLDS_STRING (&v)) - return g_value_get_string (&v); - return NULL; + g_return_val_if_fail (trans, NULL); + if (trans->void_reason == is_unset) + { + GValue v = G_VALUE_INIT; + Transaction *t = (Transaction*) trans; + qof_instance_get_kvp (QOF_INSTANCE (trans), &v, 1, void_reason_str); + t->void_reason = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL; + g_value_unset (&v); + } + return trans->void_reason; } time64 @@ -2815,10 +2853,7 @@ xaccTransUnvoid (Transaction *trans) const char *s = NULL; g_return_if_fail(trans); - qof_instance_get_kvp (QOF_INSTANCE (trans), &v, 1, void_reason_str); - if (G_VALUE_HOLDS_STRING (&v)) - s = g_value_get_string (&v); - g_value_unset (&v); + s = xaccTransGetVoidReason (trans); if (s == NULL) return; /* Transaction isn't voided. Bail. */ xaccTransBeginEdit(trans); @@ -2829,6 +2864,8 @@ xaccTransUnvoid (Transaction *trans) qof_instance_set_kvp (QOF_INSTANCE (trans), NULL, 1, void_reason_str); qof_instance_set_kvp (QOF_INSTANCE (trans), NULL, 1, void_time_str); g_value_unset (&v); + g_free (trans->void_reason); + trans->void_reason = NULL; FOR_EACH_SPLIT(trans, xaccSplitUnvoid(s)); diff --git a/libgnucash/engine/TransactionP.h b/libgnucash/engine/TransactionP.h index c42f988c11..f7c4553814 100644 --- a/libgnucash/engine/TransactionP.h +++ b/libgnucash/engine/TransactionP.h @@ -120,6 +120,10 @@ struct transaction_s char * readonly_reason; gboolean reason_cache_valid; + char * doclink; + char * void_reason; + char * notes; + /* Cached bool value to indicate whether this is a closing txn. This is * cached from the KVP value because it is queried a lot. Tri-state value: -1 * = uninitialized; 0 = FALSE, 1 = TRUE. */