mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-08 12:41:29 +00:00
glibcompat: Sync g_string_replace()
Ever since its introduction, g_string_replace() has received various bugfies and improvements, e.g.: 0a8c7e57a g_string_replace: Don't replace empty string more than once per location b13777841 g_string_replace: Document behaviour of zero-length match pattern e8517e777 remove quadratic behavior in g_string_replace c9e48947e gstring: Fix a heap buffer overflow in the new g_string_replace() code to name a few. Sync our implementation with the one from current main branch of glib. Some code style adjustments have been made to match our coding style. Signed-off-by: Adam Julis <ajulis@redhat.com> Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
This commit is contained in:
parent
8cf042d983
commit
1e88b8983c
@ -65,7 +65,7 @@
|
||||
|
||||
/**
|
||||
* Adapted (to pass syntax check) from 'g_string_replace' from
|
||||
* glib-2.81.1. Drop once minimum glib is bumped to 2.68.
|
||||
* glib-2.83.3. Drop once minimum glib is bumped to 2.68.
|
||||
*
|
||||
* g_string_replace:
|
||||
* @string: a #GString
|
||||
@ -94,35 +94,120 @@ vir_g_string_replace(GString *string,
|
||||
const gchar *replace,
|
||||
guint limit)
|
||||
{
|
||||
gsize f_len, r_len, pos;
|
||||
gchar *cur, *next;
|
||||
guint n = 0;
|
||||
GString *new_string = NULL;
|
||||
gsize f_len, r_len, new_len;
|
||||
gchar *cur, *next, *first, *dst;
|
||||
guint n;
|
||||
|
||||
g_return_val_if_fail(string != NULL, 0);
|
||||
g_return_val_if_fail(find != NULL, 0);
|
||||
g_return_val_if_fail(replace != NULL, 0);
|
||||
|
||||
first = strstr(string->str, find);
|
||||
|
||||
if (first == NULL)
|
||||
return 0;
|
||||
|
||||
new_len = string->len;
|
||||
f_len = strlen(find);
|
||||
r_len = strlen(replace);
|
||||
cur = string->str;
|
||||
|
||||
while ((next = strstr(cur, find)) != NULL) {
|
||||
pos = next - string->str;
|
||||
g_string_erase(string, pos, f_len);
|
||||
g_string_insert(string, pos, replace);
|
||||
cur = string->str + pos + r_len;
|
||||
n++;
|
||||
/* Only match the empty string once at any given position, to
|
||||
* avoid infinite loops */
|
||||
if (f_len == 0) {
|
||||
if (cur[0] == '\0')
|
||||
break;
|
||||
else
|
||||
cur++;
|
||||
/* It removes a lot of branches and possibility for infinite loops if we
|
||||
* handle the case of an empty @find string separately. */
|
||||
if (G_UNLIKELY(f_len == 0)) {
|
||||
size_t i;
|
||||
if (limit == 0 || limit > string->len) {
|
||||
if (string->len > G_MAXSIZE - 1)
|
||||
g_error("inserting in every position in string would overflow");
|
||||
|
||||
limit = string->len + 1;
|
||||
}
|
||||
if (n == limit)
|
||||
break;
|
||||
|
||||
if (r_len > 0 &&
|
||||
(limit > G_MAXSIZE / r_len ||
|
||||
limit * r_len > G_MAXSIZE - string->len))
|
||||
g_error("inserting in every position in string would overflow");
|
||||
|
||||
new_len = string->len + limit * r_len;
|
||||
new_string = g_string_sized_new(new_len);
|
||||
for (i = 0; i < limit; i++) {
|
||||
g_string_append_len(new_string, replace, r_len);
|
||||
if (i < string->len)
|
||||
g_string_append_c(new_string, string->str[i]);
|
||||
}
|
||||
if (limit < string->len)
|
||||
g_string_append_len(new_string, string->str + limit, string->len - limit);
|
||||
|
||||
g_free(string->str);
|
||||
string->allocated_len = new_string->allocated_len;
|
||||
string->len = new_string->len;
|
||||
string->str = g_string_free(g_steal_pointer(&new_string), FALSE);
|
||||
|
||||
return limit;
|
||||
}
|
||||
/* Potentially do two passes: the first to calculate the length of the new string,
|
||||
* new_len, if it’s going to be longer than the original string; and the second to
|
||||
* do the replacements. The first pass is skipped if the new string is going to be
|
||||
* no longer than the original.
|
||||
*
|
||||
* The second pass calls various g_string_insert_len() (and similar) methods
|
||||
* which would normally potentially reallocate string->str, and hence
|
||||
* invalidate the cur/next/first/dst pointers. Because we’ve pre-calculated
|
||||
* the new_len and do all the string manipulations on new_string, that
|
||||
* shouldn’t happen. This means we scan `string` while modifying
|
||||
* `new_string`. */
|
||||
do {
|
||||
dst = first;
|
||||
cur = first;
|
||||
n = 0;
|
||||
while ((next = strstr(cur, find)) != NULL) {
|
||||
n++;
|
||||
|
||||
if (r_len <= f_len) {
|
||||
memmove(dst, cur, next - cur);
|
||||
dst += next - cur;
|
||||
memcpy(dst, replace, r_len);
|
||||
dst += r_len;
|
||||
} else {
|
||||
if (new_string == NULL) {
|
||||
new_len += r_len - f_len;
|
||||
} else {
|
||||
g_string_append_len(new_string, cur, next - cur);
|
||||
g_string_append_len(new_string, replace, r_len);
|
||||
}
|
||||
}
|
||||
cur = next + f_len;
|
||||
|
||||
if (n == limit)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Append the trailing characters from after the final instance of @find
|
||||
* in the input string. */
|
||||
if (r_len <= f_len) {
|
||||
/* First pass skipped. */
|
||||
gchar *end = string->str + string->len;
|
||||
memmove(dst, cur, end - cur);
|
||||
end = dst + (end - cur);
|
||||
*end = 0;
|
||||
string->len = end - string->str;
|
||||
break;
|
||||
} else {
|
||||
if (new_string == NULL) {
|
||||
/* First pass. */
|
||||
new_string = g_string_sized_new(new_len);
|
||||
g_string_append_len(new_string, string->str, first - string->str);
|
||||
} else {
|
||||
/* Second pass. */
|
||||
g_string_append_len(new_string, cur, (string->str + string->len) - cur);
|
||||
g_free(string->str);
|
||||
string->allocated_len = new_string->allocated_len;
|
||||
string->len = new_string->len;
|
||||
string->str = g_string_free(g_steal_pointer(&new_string), FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (1);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user