Files
gnucash/libgnucash/engine/qofid.cpp
T
Stefan Koch 0e3853bfb7 Fix undefined behaviour in collection_compare_cb function.
The collection_compare_cb function set the user_data pointer to point to a local variable
of the function.  That pointer is then used in the (only) calling function
qof_collection_compare to make decisions.  This is undefined.  It is likely not an actual
problem because the stack depth of the qof_collection_foreach followed by
collection_compare_cb is deeper than the qof_collection_get_data call (and others
in between if any) that the stack data user data stays uncorrupted.

But, it is undefined behavior, and could cause really subtle bugs if these there are code
changes that have deeper stack between the setting and using.

Also, using this local variable is not necessary, the qof_collection_compare function
already  sets up a variable local to its scope for this that the collection_compare_cb can
use directly.

This commit removes the local (to collection_compare_cb) variable and uses the one setup
in qof_collection_compare.

The full coverage test for qofid.cpp passed before and after this change.
2026-01-15 08:52:50 -05:00

310 lines
8.0 KiB
C++

/********************************************************************\
* qofid.c -- QOF entity identifier implementation *
* Copyright (C) 2000 Dave Peticolas <dave@krondo.com> *
* Copyright (C) 2003 Linas Vepstas <linas@linas.org> *
* *
* 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 2 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, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
* *
\********************************************************************/
#include <glib.h>
#include <config.h>
#include <string.h>
#include "qof.h"
#include "qofid-p.h"
#include "qofinstance-p.h"
static QofLogModule log_module = QOF_MOD_ENGINE;
struct QofCollection_s
{
QofIdType e_type;
gboolean is_dirty;
GHashTable * hash_of_entities;
gpointer data; /* place where object class can hang arbitrary data */
};
/* =============================================================== */
QofCollection *
qof_collection_new (QofIdType type)
{
QofCollection *col;
col = g_new0(QofCollection, 1);
col->e_type = static_cast<QofIdType>(CACHE_INSERT (type));
col->is_dirty = FALSE;
col->hash_of_entities = guid_hash_table_new();
col->data = NULL;
return col;
}
void
qof_collection_destroy (QofCollection *col)
{
CACHE_REMOVE (col->e_type);
g_hash_table_destroy(col->hash_of_entities);
col->e_type = NULL;
col->hash_of_entities = NULL;
col->data = NULL; /** XXX there should be a destroy notifier for this */
g_free (col);
}
/* =============================================================== */
/* getters */
QofIdType
qof_collection_get_type (const QofCollection *col)
{
return col->e_type;
}
/* =============================================================== */
void
qof_collection_remove_entity (QofInstance *ent)
{
QofCollection *col;
const GncGUID *guid;
if (!ent) return;
col = qof_instance_get_collection(ent);
if (!col) return;
guid = qof_instance_get_guid(ent);
g_hash_table_remove (col->hash_of_entities, guid);
qof_instance_set_collection(ent, NULL);
}
void
qof_collection_insert_entity (QofCollection *col, QofInstance *ent)
{
const GncGUID *guid;
if (!col || !ent) return;
guid = qof_instance_get_guid(ent);
if (guid_equal(guid, guid_null())) return;
g_return_if_fail (col->e_type == ent->e_type);
qof_collection_remove_entity (ent);
g_hash_table_insert (col->hash_of_entities, (gpointer)guid, ent);
qof_instance_set_collection(ent, col);
}
gboolean
qof_collection_add_entity (QofCollection *coll, QofInstance *ent)
{
QofInstance *e;
const GncGUID *guid;
e = NULL;
if (!coll || !ent)
{
return FALSE;
}
guid = qof_instance_get_guid(ent);
if (guid_equal(guid, guid_null()))
{
return FALSE;
}
g_return_val_if_fail (coll->e_type == ent->e_type, FALSE);
e = qof_collection_lookup_entity(coll, guid);
if ( e != NULL )
{
return FALSE;
}
g_hash_table_insert (coll->hash_of_entities, (gpointer)guid, ent);
return TRUE;
}
static void
collection_compare_cb (QofInstance *ent, gpointer user_data)
{
QofCollection *target;
QofInstance *e;
const GncGUID *guid;
gint* value;
e = NULL;
target = (QofCollection*)user_data;
if (!target || !ent)
{
return;
}
value = (gint*)qof_collection_get_data(target);
if (*value != 0)
{
return;
}
guid = qof_instance_get_guid(ent);
if (guid_equal(guid, guid_null()))
{
*value = -1;
return;
}
g_return_if_fail (target->e_type == ent->e_type);
e = qof_collection_lookup_entity(target, guid);
if ( e == NULL )
{
*value = 1;
return;
}
*value = 0;
}
gint
qof_collection_compare (QofCollection *target, QofCollection *merge)
{
gint value;
value = 0;
if (!target && !merge)
{
return 0;
}
if (target == merge)
{
return 0;
}
if (!target && merge)
{
return -1;
}
if (target && !merge)
{
return 1;
}
if (target->e_type != merge->e_type)
{
return -1;
}
qof_collection_set_data(target, &value);
qof_collection_foreach(merge, collection_compare_cb, target);
value = *(gint*)qof_collection_get_data(target);
if (value == 0)
{
qof_collection_set_data(merge, &value);
qof_collection_foreach(target, collection_compare_cb, merge);
value = *(gint*)qof_collection_get_data(merge);
}
return value;
}
QofInstance *
qof_collection_lookup_entity (const QofCollection *col, const GncGUID * guid)
{
QofInstance *ent;
g_return_val_if_fail (col, NULL);
if (guid == NULL) return NULL;
ent = static_cast<QofInstance*>(g_hash_table_lookup (col->hash_of_entities,
guid));
if (ent != NULL && qof_instance_get_destroying(ent)) return NULL;
return ent;
}
guint
qof_collection_count (const QofCollection *col)
{
guint c;
c = g_hash_table_size(col->hash_of_entities);
return c;
}
/* =============================================================== */
gboolean
qof_collection_is_dirty (const QofCollection *col)
{
return col ? col->is_dirty : FALSE;
}
void
qof_collection_mark_clean (QofCollection *col)
{
if (col)
{
col->is_dirty = FALSE;
}
}
void
qof_collection_mark_dirty (QofCollection *col)
{
if (col)
{
col->is_dirty = TRUE;
}
}
void
qof_collection_print_dirty (const QofCollection *col, gpointer dummy)
{
if (col->is_dirty)
printf("%s collection is dirty.\n", col->e_type);
qof_collection_foreach(col, (QofInstanceForeachCB)qof_instance_print_dirty, NULL);
}
/* =============================================================== */
gpointer
qof_collection_get_data (const QofCollection *col)
{
return col ? col->data : NULL;
}
void
qof_collection_set_data (QofCollection *col, gpointer user_data)
{
if (col)
{
col->data = user_data;
}
}
/* =============================================================== */
void
qof_collection_foreach_sorted (const QofCollection *col, QofInstanceForeachCB cb_func,
gpointer user_data, GCompareFunc sort_fn)
{
GList *entries;
g_return_if_fail (col);
g_return_if_fail (cb_func);
PINFO("Hash Table size of %s before is %d", col->e_type, g_hash_table_size(col->hash_of_entities));
entries = g_hash_table_get_values (col->hash_of_entities);
if (sort_fn)
entries = g_list_sort (entries, sort_fn);
g_list_foreach (entries, (GFunc)cb_func, user_data);
g_list_free (entries);
PINFO("Hash Table size of %s after is %d", col->e_type, g_hash_table_size(col->hash_of_entities));
}
void
qof_collection_foreach (const QofCollection *col, QofInstanceForeachCB cb_func,
gpointer user_data)
{
qof_collection_foreach_sorted (col, cb_func, user_data, nullptr);
}
/* =============================================================== */