diff --git a/src/core/wee-string.c b/src/core/wee-string.c index 376903767..29c181bd0 100644 --- a/src/core/wee-string.c +++ b/src/core/wee-string.c @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef HAVE_ICONV #include @@ -47,11 +48,17 @@ #include "weechat.h" #include "wee-string.h" #include "wee-config.h" +#include "wee-hashtable.h" #include "wee-utf8.h" #include "../gui/gui-color.h" #include "../plugins/plugin.h" +typedef uint32_t string_shared_count_t; + +struct t_hashtable *string_hashtable_shared = NULL; + + /* * Defines a "strndup" function for systems where this function does not exist * (FreeBSD and maybe others). @@ -1052,6 +1059,9 @@ string_has_highlight_regex (const char *string, const char *regex) /* * Splits a string according to separators. * + * This function must not be called directly (call string_split or + * string_split_shared instead). + * * Examples: * string_split ("abc de fghi", " ", 0, 0, NULL) * ==> array[0] = "abc" @@ -1066,12 +1076,13 @@ string_has_highlight_regex (const char *string, const char *regex) */ char ** -string_split (const char *string, const char *separators, int keep_eol, - int num_items_max, int *num_items) +string_split_internal (const char *string, const char *separators, int keep_eol, + int num_items_max, int *num_items, int shared) { int i, j, n_items; char *string2, **array; char *ptr, *ptr1, *ptr2; + const char *str_shared; if (num_items != NULL) *num_items = 0; @@ -1141,13 +1152,18 @@ string_split (const char *string, const char *separators, int keep_eol, { if (keep_eol) { - array[i] = strdup (ptr1); + array[i] = (shared) ? (char *)string_shared_get (ptr1) : strdup (ptr1); if (!array[i]) { for (j = 0; j < n_items; j++) { if (array[j]) - free (array[j]); + { + if (shared) + string_shared_free (array[j]); + else + free (array[j]); + } } free (array); free (string2); @@ -1162,7 +1178,12 @@ string_split (const char *string, const char *separators, int keep_eol, for (j = 0; j < n_items; j++) { if (array[j]) - free (array[j]); + { + if (shared) + string_shared_free (array[j]); + else + free (array[j]); + } } free (array); free (string2); @@ -1170,6 +1191,23 @@ string_split (const char *string, const char *separators, int keep_eol, } strncpy (array[i], ptr1, ptr2 - ptr1); array[i][ptr2 - ptr1] = '\0'; + if (shared) + { + str_shared = string_shared_get (array[i]); + if (!str_shared) + { + for (j = 0; j < n_items; j++) + { + if (array[j]) + string_shared_free (array[j]); + } + free (array); + free (string2); + return NULL; + } + free (array[i]); + array[i] = (char *)str_shared; + } } ptr1 = ++ptr2; } @@ -1189,6 +1227,35 @@ string_split (const char *string, const char *separators, int keep_eol, return array; } +/* + * Splits a string according to separators. + * + * For full description, see function string_split_internal. + */ + +char ** +string_split (const char *string, const char *separators, int keep_eol, + int num_items_max, int *num_items) +{ + return string_split_internal (string, separators, keep_eol, + num_items_max, num_items, 0); +} + +/* + * Splits a string according to separators, and use shared strings for the + * strings in the array returned. + * + * For full description, see function string_split_internal. + */ + +char ** +string_split_shared (const char *string, const char *separators, int keep_eol, + int num_items_max, int *num_items) +{ + return string_split_internal (string, separators, keep_eol, + num_items_max, num_items, 1); +} + /* * Splits a string like the shell does for a command with arguments. * @@ -1398,6 +1465,23 @@ string_free_split (char **split_string) } } +/* + * Frees a split string (using shared strings). + */ + +void +string_free_split_shared (char **split_string) +{ + int i; + + if (split_string) + { + for (i = 0; split_string[i]; i++) + string_shared_free (split_string[i]); + free (split_string); + } +} + /* * Builds a string with a split string. * @@ -2193,3 +2277,163 @@ string_replace_with_callback (const char *string, return result; } + +/* + * Hashes a shared string. + * The string starts after the reference count, which is skipped. + * + * Returns the hash of the shared string (variant of djb2). + */ + +unsigned long +string_shared_hash_key (struct t_hashtable *hashtable, + const void *key) +{ + /* make C compiler happy */ + (void) hashtable; + + return hashtable_hash_key_djb2 (((const char *)key) + sizeof (string_shared_count_t)); +} + +/* + * Compares two shared strings. + * Each string starts after the reference count, which is skipped. + * + * Returns: + * < 0: key1 < key2 + * 0: key1 == key2 + * > 0: key1 > key2 + */ + +int +string_shared_keycmp (struct t_hashtable *hashtable, + const void *key1, const void *key2) +{ + /* make C compiler happy */ + (void) hashtable; + + return strcmp (((const char *)key1) + sizeof (string_shared_count_t), + ((const char *)key2) + sizeof (string_shared_count_t)); +} + +/* + * Frees a shared string. + */ + +void +string_shared_free_key (struct t_hashtable *hashtable, + void *key, const void *value) +{ + /* make C compiler happy */ + (void) hashtable; + (void) value; + + free (key); +} + +/* + * Gets a pointer to a shared string. + * + * A shared string is an entry in the hashtable "string_hashtable_shared", with: + * - key: reference count (unsigned integer on 32 bits) + string + * - value: NULL pointer (not used) + * + * The initial reference count is set to 1 and is incremented each time this + * function is called for a same string (string content, not the pointer). + * + * Returns the pointer to the shared string (start of string in key, after the + * reference count), NULL if error. + * The string returned has exactly same content as string received in argument, + * but the pointer to the string is different. + * + * IMPORTANT: the returned string must NEVER be changed in any way, because it + * is used itself as the key of the hashtable. + */ + +const char * +string_shared_get (const char *string) +{ + struct t_hashtable_item *ptr_item; + char *key; + int length; + + if (!string_hashtable_shared) + { + /* + * use large htable inside hashtable to prevent too many collisions, + * which would slow down search of a string in the hashtable + */ + string_hashtable_shared = hashtable_new (1024, + WEECHAT_HASHTABLE_POINTER, + WEECHAT_HASHTABLE_POINTER, + &string_shared_hash_key, + &string_shared_keycmp); + if (!string_hashtable_shared) + return NULL; + + string_hashtable_shared->callback_free_key = &string_shared_free_key; + } + + length = sizeof (string_shared_count_t) + strlen (string) + 1; + key = malloc (length); + if (!key) + return NULL; + *((string_shared_count_t *)key) = 1; + strcpy (key + sizeof (string_shared_count_t), string); + + ptr_item = hashtable_get_item (string_hashtable_shared, key, NULL); + if (ptr_item) + { + /* + * the string already exists in the hashtable, then just increase the + * reference count on the string + */ + (*((string_shared_count_t *)(ptr_item->key)))++; + free (key); + } + else + { + /* add the shared string in the hashtable */ + ptr_item = hashtable_set (string_hashtable_shared, key, NULL); + if (!ptr_item) + free (key); + } + + return (ptr_item) ? + ((const char *)ptr_item->key) + sizeof (string_shared_count_t) : NULL; +} + +/* + * Frees a shared string. + * + * The reference count of the string is decremented. If it becomes 0, then the + * shared string is removed from the hashtable (and then the string is really + * destroyed). + */ + +void +string_shared_free (const char *string) +{ + string_shared_count_t *ptr_count; + + ptr_count = (string_shared_count_t *)(string - sizeof (string_shared_count_t)); + + (*ptr_count)--; + + if (*ptr_count == 0) + hashtable_remove (string_hashtable_shared, ptr_count); +} + +/* + * Frees all allocated data. + */ + +void +string_end () +{ + if (string_hashtable_shared) + { + hashtable_free (string_hashtable_shared); + string_hashtable_shared = NULL; + } +} diff --git a/src/core/wee-string.h b/src/core/wee-string.h index f79795707..d79ae8fac 100644 --- a/src/core/wee-string.h +++ b/src/core/wee-string.h @@ -59,8 +59,12 @@ extern int string_has_highlight_regex_compiled (const char *string, extern int string_has_highlight_regex (const char *string, const char *regex); extern char **string_split (const char *string, const char *separators, int keep_eol, int num_items_max, int *num_items); +extern char **string_split_shared (const char *string, const char *separators, + int keep_eol, int num_items_max, + int *num_items); extern char **string_split_shell (const char *string); extern void string_free_split (char **split_string); +extern void string_free_split_shared (char **split_string); extern char *string_build_with_split_string (const char **split_string, const char *separator); extern char **string_split_command (const char *command, char separator); @@ -84,5 +88,8 @@ extern char *string_replace_with_callback (const char *string, char *(*callback)(void *data, const char *text), void *callback_data, int *errors); +extern const char *string_shared_get (const char *string); +extern void string_shared_free (const char *string); +extern void string_end (); #endif /* __WEECHAT_STRING_H */ diff --git a/src/core/weechat.c b/src/core/weechat.c index 218028075..e7a6d030a 100644 --- a/src/core/weechat.c +++ b/src/core/weechat.c @@ -488,6 +488,7 @@ main (int argc, char *argv[]) unhook_all (); /* remove all hooks */ hdata_end (); /* end hdata */ eval_end (); /* end eval */ + string_end (); /* end string */ weechat_shutdown (EXIT_SUCCESS, 0); /* quit WeeChat (oh no, why?) */ return EXIT_SUCCESS; /* make C compiler happy */ diff --git a/src/gui/gui-line.c b/src/gui/gui-line.c index 92bb6f5ef..724d632f8 100644 --- a/src/gui/gui-line.c +++ b/src/gui/gui-line.c @@ -86,6 +86,40 @@ gui_lines_free (struct t_gui_lines *lines) free (lines); } +/* + * Allocates array with tags in a line_data. + */ + +void +gui_line_tags_alloc (struct t_gui_line_data *line_data, const char *tags) +{ + if (tags) + { + line_data->tags_array = string_split_shared (tags, ",", 0, 0, + &line_data->tags_count); + } + else + { + line_data->tags_count = 0; + line_data->tags_array = NULL; + } +} + +/* + * Frees array with tags in a line_data. + */ + +void +gui_line_tags_free (struct t_gui_line_data *line_data) +{ + if (line_data->tags_array) + { + string_free_split_shared (line_data->tags_array); + line_data->tags_count = 0; + line_data->tags_array = NULL; + } +} + /* * Checks if prefix on line is a nick and is the same as nick on previous line. * @@ -871,10 +905,9 @@ gui_line_remove_from_list (struct t_gui_buffer *buffer, { if (line->data->str_time) free (line->data->str_time); - if (line->data->tags_array) - string_free_split (line->data->tags_array); + gui_line_tags_free (line->data); if (line->data->prefix) - free (line->data->prefix); + string_shared_free (line->data->prefix); if (line->data->message) free (line->data->message); free (line->data); @@ -1090,19 +1123,10 @@ gui_line_add (struct t_gui_buffer *buffer, time_t date, new_line->data->date = date; new_line->data->date_printed = date_printed; new_line->data->str_time = gui_chat_get_time_string (date); - if (tags) - { - new_line->data->tags_array = string_split (tags, ",", 0, 0, - &new_line->data->tags_count); - } - else - { - new_line->data->tags_count = 0; - new_line->data->tags_array = NULL; - } + gui_line_tags_alloc (new_line->data, tags); new_line->data->refresh_needed = 0; new_line->data->prefix = (prefix) ? - strdup (prefix) : ((date != 0) ? strdup ("") : NULL); + (char *)string_shared_get (prefix) : ((date != 0) ? (char *)string_shared_get ("") : NULL); new_line->data->prefix_length = (prefix) ? gui_chat_strlen_screen (prefix) : 0; new_line->data->message = (message) ? strdup (message) : strdup (""); @@ -1318,8 +1342,8 @@ void gui_line_clear (struct t_gui_line *line) { if (line->data->prefix) - free (line->data->prefix); - line->data->prefix = strdup (""); + string_shared_free (line->data->prefix); + line->data->prefix = (char *)string_shared_get (""); if (line->data->message) free (line->data->message); @@ -1521,18 +1545,8 @@ gui_line_hdata_line_data_update_cb (void *data, if (hashtable_has_key (hashtable, "tags_array")) { value = hashtable_get (hashtable, "tags_array"); - if (line_data->tags_array) - string_free_split (line_data->tags_array); - if (value) - { - line_data->tags_array = string_split (value, ",", 0, 0, - &line_data->tags_count); - } - else - { - line_data->tags_count = 0; - line_data->tags_array = NULL; - } + gui_line_tags_free (line_data); + gui_line_tags_alloc (line_data, value); rc++; } diff --git a/src/gui/gui-nicklist.c b/src/gui/gui-nicklist.c index e0e21eb77..7c82699e6 100644 --- a/src/gui/gui-nicklist.c +++ b/src/gui/gui-nicklist.c @@ -268,8 +268,8 @@ gui_nicklist_add_group (struct t_gui_buffer *buffer, if (!new_group) return NULL; - new_group->name = strdup (name); - new_group->color = (color) ? strdup (color) : NULL; + new_group->name = (char *)string_shared_get (name); + new_group->color = (color) ? (char *)string_shared_get (color) : NULL; new_group->visible = visible; new_group->parent = (parent_group) ? parent_group : buffer->nicklist_root; new_group->level = (new_group->parent) ? new_group->parent->level + 1 : 0; @@ -442,10 +442,10 @@ gui_nicklist_add_nick (struct t_gui_buffer *buffer, return NULL; new_nick->group = (group) ? group : buffer->nicklist_root; - new_nick->name = strdup (name); - new_nick->color = (color) ? strdup (color) : NULL; - new_nick->prefix = (prefix) ? strdup (prefix) : NULL; - new_nick->prefix_color = (prefix_color) ? strdup (prefix_color) : NULL; + new_nick->name = (char *)string_shared_get (name); + new_nick->color = (color) ? (char *)string_shared_get (color) : NULL; + new_nick->prefix = (prefix) ? (char *)string_shared_get (prefix) : NULL; + new_nick->prefix_color = (prefix_color) ? (char *)string_shared_get (prefix_color) : NULL; new_nick->visible = visible; gui_nicklist_insert_nick_sorted (new_nick->group, new_nick); @@ -495,13 +495,13 @@ gui_nicklist_remove_nick (struct t_gui_buffer *buffer, /* free data */ if (nick->name) - free (nick->name); + string_shared_free (nick->name); if (nick->color) - free (nick->color); + string_shared_free (nick->color); if (nick->prefix) - free (nick->prefix); + string_shared_free (nick->prefix); if (nick->prefix_color) - free (nick->prefix_color); + string_shared_free (nick->prefix_color); buffer->nicklist_count--; buffer->nicklist_nicks_count--; @@ -575,9 +575,9 @@ gui_nicklist_remove_group (struct t_gui_buffer *buffer, /* free data */ if (group->name) - free (group->name); + string_shared_free (group->name); if (group->color) - free (group->color); + string_shared_free (group->color); if (group->visible) { @@ -884,8 +884,8 @@ gui_nicklist_group_set (struct t_gui_buffer *buffer, if (string_strcasecmp (property, "color") == 0) { if (group->color) - free (group->color); - group->color = (value[0]) ? strdup (value) : NULL; + string_shared_free (group->color); + group->color = (value[0]) ? (char *)string_shared_get (value) : NULL; group_changed = 1; } else if (string_strcasecmp (property, "visible") == 0) @@ -995,22 +995,22 @@ gui_nicklist_nick_set (struct t_gui_buffer *buffer, if (string_strcasecmp (property, "color") == 0) { if (nick->color) - free (nick->color); - nick->color = (value[0]) ? strdup (value) : NULL; + string_shared_free (nick->color); + nick->color = (value[0]) ? (char *)string_shared_get (value) : NULL; nick_changed = 1; } else if (string_strcasecmp (property, "prefix") == 0) { if (nick->prefix) - free (nick->prefix); - nick->prefix = (value[0]) ? strdup (value) : NULL; + string_shared_free (nick->prefix); + nick->prefix = (value[0]) ? (char *)string_shared_get (value) : NULL; nick_changed = 1; } else if (string_strcasecmp (property, "prefix_color") == 0) { if (nick->prefix_color) - free (nick->prefix_color); - nick->prefix_color = (value[0]) ? strdup (value) : NULL; + string_shared_free (nick->prefix_color); + nick->prefix_color = (value[0]) ? (char *)string_shared_get (value) : NULL; nick_changed = 1; } else if (string_strcasecmp (property, "visible") == 0)