NetworkManager/src/libnm-glib-aux/nm-enum-utils.c
Thomas Haller 0554ef3808
all: use nm_g_array_index() instead of g_array_index()
nm_g_array_index() performs additional nm_assert() checks for
correctness.

In this case, it's pretty clear that the assertion will hold and that
the code is correct. Note that most of the time when having assertions,
we expect that they hold. Since nm_assert() is disabled in release
build, arguing that an assertion holds is not a strong argument against
having the assert (they are always supposed to hold, quite obviously so!).

The reason to change is that we should use the wrappers that perform
additional checks. Especially when the additional checks are nm_assert()
or static-asserts, as they are not present in release builds. To find
how well we are doing in this regard we can check `git grep -w
g_array_index`. If that gives many uses of the unchecked function, then
we cannot manually check them all to be really obviously correct.
Instead, we should not use g_array_index() and trivially see that all
array accesses are guarded by assertions.

"checkpatch.pl" also recommends against g_array_index().
2023-10-30 17:15:06 +01:00

437 lines
15 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2017 Red Hat, Inc.
*/
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
#include "nm-enum-utils.h"
#include "nm-str-buf.h"
/*****************************************************************************/
#define IS_FLAGS_SEPARATOR(ch) (NM_IN_SET((ch), ' ', '\t', ',', '\n', '\r'))
static void
_ASSERT_enum_values_info(GType type, const NMUtilsEnumValueInfo *value_infos)
{
#if NM_MORE_ASSERTS > 5
nm_auto_unref_gtypeclass GTypeClass *klass = NULL;
gs_unref_hashtable GHashTable *ht = NULL;
klass = g_type_class_ref(type);
g_assert(G_IS_ENUM_CLASS(klass) || G_IS_FLAGS_CLASS(klass));
if (!value_infos)
return;
ht = g_hash_table_new(g_str_hash, g_str_equal);
for (; value_infos->nick; value_infos++) {
g_assert(value_infos->nick[0]);
/* duplicate nicks make no sense!! */
g_assert(!g_hash_table_contains(ht, value_infos->nick));
g_hash_table_add(ht, (gpointer) value_infos->nick);
if (G_IS_ENUM_CLASS(klass)) {
GEnumValue *enum_value;
enum_value = g_enum_get_value_by_nick(G_ENUM_CLASS(klass), value_infos->nick);
if (enum_value) {
/* we do allow specifying the same name via @value_infos and @type.
* That might make sense, if @type comes from a library where older versions
* of the library don't yet support the value. In this case, the caller can
* provide the nick via @value_infos, to support the older library version.
* And then, when actually running against a newer library version where
* @type knows the nick, we have this situation.
*
* Another reason for specifying a nick both in @value_infos and @type,
* is to specify an alias which is not used with highest preference. For
* example, if you add an alias "disabled" for "none" (both numerically
* equal), then the first alias in @value_infos will be preferred over
* the name from @type. So, to still use "none" as preferred name, you may
* explicitly specify the "none" alias in @value_infos before "disabled".
*
* However, what never is allowed, is to use a name (nick) to re-number
* the value. That is, if both @value_infos and @type contain a particular
* nick, their numeric values must agree as well.
* Allowing this, would be very confusing, because the name would have a different
* value from the regular GLib GEnum API.
*/
g_assert(enum_value->value == value_infos->value);
}
} else {
GFlagsValue *flags_value;
flags_value = g_flags_get_value_by_nick(G_FLAGS_CLASS(klass), value_infos->nick);
if (flags_value) {
/* see ENUM case above. */
g_assert(flags_value->value == (guint) value_infos->value);
}
}
}
#endif
}
static gboolean
_is_hex_string(const char *str, gboolean allow_sign)
{
if (allow_sign && str[0] == '-')
str++;
return str[0] == '0' && str[1] == 'x' && str[2]
&& NM_STRCHAR_ALL(&str[2], ch, g_ascii_isxdigit(ch));
}
static gboolean
_is_dec_string(const char *str, gboolean allow_sign)
{
if (allow_sign && str[0] == '-')
str++;
return str[0] && NM_STRCHAR_ALL(&str[0], ch, g_ascii_isdigit(ch));
}
static gboolean
_enum_is_valid_enum_nick(const char *str)
{
return str[0] && !NM_STRCHAR_ANY(str, ch, g_ascii_isspace(ch)) && !_is_dec_string(str, TRUE)
&& !_is_hex_string(str, TRUE);
}
static gboolean
_enum_is_valid_flags_nick(const char *str)
{
return str[0] && !NM_STRCHAR_ANY(str, ch, IS_FLAGS_SEPARATOR(ch)) && !_is_dec_string(str, FALSE)
&& !_is_hex_string(str, FALSE);
}
char *
_nm_utils_enum_to_str_full(GType type,
int value,
const char *flags_separator,
const NMUtilsEnumValueInfo *value_infos)
{
nm_auto_unref_gtypeclass GTypeClass *klass = NULL;
_ASSERT_enum_values_info(type, value_infos);
if (flags_separator
&& (!flags_separator[0] || NM_STRCHAR_ANY(flags_separator, ch, !IS_FLAGS_SEPARATOR(ch))))
g_return_val_if_reached(NULL);
klass = g_type_class_ref(type);
if (G_IS_ENUM_CLASS(klass)) {
GEnumValue *enum_value;
for (; value_infos && value_infos->nick; value_infos++) {
if (value_infos->value == value)
return g_strdup(value_infos->nick);
}
enum_value = g_enum_get_value(G_ENUM_CLASS(klass), value);
if (!enum_value || !_enum_is_valid_enum_nick(enum_value->value_nick))
return g_strdup_printf("%d", value);
else
return g_strdup(enum_value->value_nick);
} else if (G_IS_FLAGS_CLASS(klass)) {
unsigned uvalue = (unsigned) value;
gboolean uvalue_was_zero = (uvalue == 0);
GFlagsValue *flags_value;
NMStrBuf strbuf;
flags_separator = flags_separator ?: " ";
strbuf = NM_STR_BUF_INIT(16, FALSE);
for (; value_infos && value_infos->nick; value_infos++) {
nm_assert(_enum_is_valid_flags_nick(value_infos->nick));
if (value_infos->value == 0 && !uvalue_was_zero)
continue;
if (uvalue == 0) {
if (value_infos->value != 0)
continue;
} else {
if (!NM_FLAGS_ALL(uvalue, (unsigned) value_infos->value))
continue;
}
if (strbuf.len)
nm_str_buf_append(&strbuf, flags_separator);
nm_str_buf_append(&strbuf, value_infos->nick);
uvalue &= ~((unsigned) value_infos->value);
if (uvalue == 0) {
/* we printed all flags. Done. */
goto flags_done;
}
}
do {
flags_value = g_flags_get_first_value(G_FLAGS_CLASS(klass), uvalue);
if (strbuf.len)
nm_str_buf_append(&strbuf, flags_separator);
if (!flags_value || !_enum_is_valid_flags_nick(flags_value->value_nick)) {
if (uvalue)
nm_str_buf_append_printf(&strbuf, "0x%x", uvalue);
break;
}
nm_str_buf_append(&strbuf, flags_value->value_nick);
uvalue &= ~flags_value->value;
} while (uvalue);
flags_done:
return nm_str_buf_finalize(&strbuf, NULL);
}
g_return_val_if_reached(NULL);
}
static const NMUtilsEnumValueInfo *
_find_value_info(const NMUtilsEnumValueInfo *value_infos, const char *needle)
{
if (value_infos) {
for (; value_infos->nick; value_infos++) {
if (nm_streq(needle, value_infos->nick))
return value_infos;
}
}
return NULL;
}
gboolean
_nm_utils_enum_from_str_full(GType type,
const char *str,
int *out_value,
char **err_token,
const NMUtilsEnumValueInfo *value_infos)
{
nm_auto_unref_gtypeclass GTypeClass *klass = NULL;
gboolean ret = FALSE;
int value = 0;
gs_free char *str_clone = NULL;
char *s;
gint64 v64;
const NMUtilsEnumValueInfo *nick;
g_return_val_if_fail(str, FALSE);
_ASSERT_enum_values_info(type, value_infos);
s = nm_strdup_maybe_a(300, nm_str_skip_leading_spaces(str), &str_clone);
g_strchomp(s);
klass = g_type_class_ref(type);
if (G_IS_ENUM_CLASS(klass)) {
GEnumValue *enum_value;
G_STATIC_ASSERT(G_MAXINT < G_MAXINT64);
G_STATIC_ASSERT(G_MININT > G_MININT64);
if (s[0]) {
if (_is_hex_string(s, TRUE)) {
if (s[0] == '-') {
v64 = _nm_utils_ascii_str_to_int64(&s[3],
16,
-((gint64) G_MAXINT),
-((gint64) G_MININT),
G_MAXINT64);
if (v64 != G_MAXINT64) {
value = (int) (-v64);
ret = TRUE;
}
} else {
v64 = _nm_utils_ascii_str_to_int64(&s[2], 16, G_MININT, G_MAXINT, G_MAXINT64);
if (v64 != G_MAXINT64) {
value = (int) v64;
ret = TRUE;
}
}
} else if (_is_dec_string(s, TRUE)) {
v64 = _nm_utils_ascii_str_to_int64(s, 10, G_MININT, G_MAXINT, G_MAXINT64);
if (v64 != G_MAXINT64) {
value = (int) v64;
ret = TRUE;
}
} else if ((nick = _find_value_info(value_infos, s))) {
value = nick->value;
ret = TRUE;
} else if ((enum_value = g_enum_get_value_by_nick(G_ENUM_CLASS(klass), s))) {
value = enum_value->value;
ret = TRUE;
}
}
} else if (G_IS_FLAGS_CLASS(klass)) {
GFlagsValue *flags_value;
unsigned uvalue = 0;
ret = TRUE;
while (s[0]) {
char *s_end;
for (s_end = s; s_end[0]; s_end++) {
if (IS_FLAGS_SEPARATOR(s_end[0])) {
s_end[0] = '\0';
s_end++;
break;
}
}
if (s[0]) {
if (_is_hex_string(s, FALSE)) {
v64 = _nm_utils_ascii_str_to_int64(&s[2], 16, 0, G_MAXUINT, -1);
if (v64 == -1) {
ret = FALSE;
break;
}
uvalue |= (unsigned) v64;
} else if (_is_dec_string(s, FALSE)) {
v64 = _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT, -1);
if (v64 == -1) {
ret = FALSE;
break;
}
uvalue |= (unsigned) v64;
} else if ((nick = _find_value_info(value_infos, s)))
uvalue |= (unsigned) nick->value;
else if ((flags_value = g_flags_get_value_by_nick(G_FLAGS_CLASS(klass), s)))
uvalue |= flags_value->value;
else {
ret = FALSE;
break;
}
}
s = s_end;
}
value = (int) uvalue;
} else
g_return_val_if_reached(FALSE);
NM_SET_OUT(err_token, !ret && s[0] ? g_strdup(s) : NULL);
NM_SET_OUT(out_value, ret ? value : 0);
return ret;
}
const char **
_nm_utils_enum_get_values(GType type, int from, int to)
{
int i;
GArray *values_full = _nm_utils_enum_get_values_full(type, from, to, NULL);
GPtrArray *values = g_ptr_array_sized_new(values_full->len + 1);
for (i = 0; i < values_full->len; i++) {
NMUtilsEnumValueInfoFull *v = &nm_g_array_index(values_full, NMUtilsEnumValueInfoFull, i);
g_ptr_array_add(values, (gpointer) v->nick);
}
g_ptr_array_add(values, NULL);
g_array_unref(values_full);
return (const char **) g_ptr_array_free(values, FALSE);
}
static void
_free_value_info_full(NMUtilsEnumValueInfoFull *value_info_full)
{
g_free(value_info_full->aliases);
}
static void
_init_value_info_full(NMUtilsEnumValueInfoFull *v, bool is_flag, const char *nick, int value)
{
char sbuf[64];
const char *value_str = is_flag ? g_intern_string(nm_sprintf_buf(sbuf, "0x%x", value))
: g_intern_string(nm_sprintf_buf(sbuf, "%d", value));
v->nick = _enum_is_valid_enum_nick(nick) ? nick : value_str;
v->aliases = NULL;
v->value_str = value_str;
v->value = value;
}
/**
* _nm_utils_enum_get_values_full:
* @type: the enum or flags type
* @from: lowest value to return
* @to: highest value to return
* @value_infos: (nullable): additional value aliases
*
* Get the enum or flags values within the given range, putting together the
* value, name and aliases of each of them.
*
* If @value_infos is NULL, no memory will be allocated and deallocated for the
* aliases and #NMUtilsEnumValueInfoFull:aliases will be NULL in the returned
* data.
*
* The caller is responsible of releasing the container, but not the contained
* data. Only #NMUtilsEnumValueInfoFull:aliases can be stolen (and set to NULL),
* and then the caller becomes the responsible to release it.
*
* Return: (transfer container): an array of #NMUtilsEnumValueInfoFull.
*/
GArray *
_nm_utils_enum_get_values_full(GType type,
int from,
int to,
const NMUtilsEnumValueInfo *value_infos)
{
NMUtilsEnumValueInfoFull v;
GArray *array;
int i;
nm_auto_unref_gtypeclass GTypeClass *klass = g_type_class_ref(type);
g_return_val_if_fail(G_IS_ENUM_CLASS(klass) || G_IS_FLAGS_CLASS(klass), NULL);
_ASSERT_enum_values_info(type, value_infos);
array = g_array_new(FALSE, FALSE, sizeof(NMUtilsEnumValueInfoFull));
if (G_IS_ENUM_CLASS(klass)) {
GEnumClass *enum_class = G_ENUM_CLASS(klass);
for (i = 0; i < enum_class->n_values; i++) {
GEnumValue *enum_val = &enum_class->values[i];
if (enum_val->value >= from && enum_val->value <= to) {
_init_value_info_full(&v, FALSE, enum_val->value_nick, enum_val->value);
g_array_append_val(array, v);
}
}
} else {
GFlagsClass *flags_class = G_FLAGS_CLASS(klass);
for (i = 0; i < flags_class->n_values; i++) {
GFlagsValue *flags_val = &flags_class->values[i];
if (flags_val->value >= (guint) from && flags_val->value <= (guint) to) {
_init_value_info_full(&v, TRUE, flags_val->value_nick, flags_val->value);
g_array_append_val(array, v);
}
}
}
if (value_infos) {
g_array_set_clear_func(array, (GDestroyNotify) _free_value_info_full);
for (i = 0; i < array->len; i++) {
NMUtilsEnumValueInfoFull *vi_full =
&nm_g_array_index(array, NMUtilsEnumValueInfoFull, i);
GPtrArray *aliases = g_ptr_array_new();
const NMUtilsEnumValueInfo *vi;
for (vi = value_infos; vi && vi->nick; vi++) {
if (vi->value == vi_full->value)
g_ptr_array_add(aliases, (gpointer) vi->nick);
}
g_ptr_array_add(aliases, NULL);
vi_full->aliases = (const char **) g_ptr_array_free(aliases, FALSE);
}
}
return array;
}