diff options
Diffstat (limited to 'src/nvim/memory.c')
-rw-r--r-- | src/nvim/memory.c | 232 |
1 files changed, 167 insertions, 65 deletions
diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 8db47b79c1..d38079ca72 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -1,3 +1,6 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + // Various routines dealing with allocation and deallocation of memory. #include <assert.h> @@ -7,26 +10,53 @@ #include "nvim/vim.h" #include "nvim/eval.h" +#include "nvim/highlight.h" #include "nvim/memfile.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/misc1.h" #include "nvim/ui.h" +#include "nvim/api/vim.h" #ifdef HAVE_JEMALLOC // Force je_ prefix on jemalloc functions. # define JEMALLOC_NO_DEMANGLE # include <jemalloc/jemalloc.h> -# define malloc(size) je_malloc(size) -# define calloc(count, size) je_calloc(count, size) -# define realloc(ptr, size) je_realloc(ptr, size) -# define free(ptr) je_free(ptr) +#endif + +#ifdef UNIT_TESTING +# define malloc(size) mem_malloc(size) +# define calloc(count, size) mem_calloc(count, size) +# define realloc(ptr, size) mem_realloc(ptr, size) +# define free(ptr) mem_free(ptr) +# ifdef HAVE_JEMALLOC +MemMalloc mem_malloc = &je_malloc; +MemFree mem_free = &je_free; +MemCalloc mem_calloc = &je_calloc; +MemRealloc mem_realloc = &je_realloc; +# else +MemMalloc mem_malloc = &malloc; +MemFree mem_free = &free; +MemCalloc mem_calloc = &calloc; +MemRealloc mem_realloc = &realloc; +# endif +#else +# ifdef HAVE_JEMALLOC +# define malloc(size) je_malloc(size) +# define calloc(count, size) je_calloc(count, size) +# define realloc(ptr, size) je_realloc(ptr, size) +# define free(ptr) je_free(ptr) +# endif #endif #ifdef INCLUDE_GENERATED_DECLARATIONS # include "memory.c.generated.h" #endif +#ifdef EXITFREE +bool entered_free_all_mem = false; +#endif + /// Try to free memory. Used when trying to recover from out of memory errors. /// @see {xmalloc} void try_to_free_memory(void) @@ -38,7 +68,7 @@ void try_to_free_memory(void) trying_to_free = true; // free any scrollback text - clear_sb_text(); + clear_sb_text(true); // Try to save all buffers and release as many blocks as possible mf_release_all(); @@ -283,18 +313,16 @@ size_t memcnt(const void *data, char c, size_t len) return cnt; } -/// The xstpcpy() function shall copy the string pointed to by src (including -/// the terminating NUL character) into the array pointed to by dst. +/// Copies the string pointed to by src (including the terminating NUL +/// character) into the array pointed to by dst. /// -/// The xstpcpy() function shall return a pointer to the terminating NUL -/// character copied into the dst buffer. This is the only difference with -/// strcpy(), which returns dst. +/// @returns pointer to the terminating NUL char copied into the dst buffer. +/// This is the only difference with strcpy(), which returns dst. /// -/// WARNING: If copying takes place between objects that overlap, the behavior is -/// undefined. +/// WARNING: If copying takes place between objects that overlap, the behavior +/// is undefined. /// -/// This is the Neovim version of stpcpy(3) as defined in POSIX 2008. We -/// don't require that supported platforms implement POSIX 2008, so we +/// Nvim version of POSIX 2008 stpcpy(3). We do not require POSIX 2008, so /// implement our own version. /// /// @param dst @@ -306,24 +334,19 @@ char *xstpcpy(char *restrict dst, const char *restrict src) return (char *)memcpy(dst, src, len + 1) + len; } -/// The xstpncpy() function shall copy not more than n bytes (bytes that follow -/// a NUL character are not copied) from the array pointed to by src to the -/// array pointed to by dst. +/// Copies not more than n bytes (bytes that follow a NUL character are not +/// copied) from the array pointed to by src to the array pointed to by dst. /// -/// If a NUL character is written to the destination, the xstpncpy() function -/// shall return the address of the first such NUL character. Otherwise, it -/// shall return &dst[maxlen]. +/// If a NUL character is written to the destination, xstpncpy() returns the +/// address of the first such NUL character. Otherwise, it shall return +/// &dst[maxlen]. /// -/// WARNING: If copying takes place between objects that overlap, the behavior is -/// undefined. +/// WARNING: If copying takes place between objects that overlap, the behavior +/// is undefined. /// /// WARNING: xstpncpy will ALWAYS write maxlen bytes. If src is shorter than /// maxlen, zeroes will be written to the remaining bytes. /// -/// TODO(aktau): I don't see a good reason to have this last behaviour, and -/// it is potentially wasteful. Could we perhaps deviate from the standard -/// and not zero the rest of the buffer? -/// /// @param dst /// @param src /// @param maxlen @@ -342,29 +365,62 @@ char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen) } } -/// xstrlcpy - Copy a %NUL terminated string into a sized buffer +/// xstrlcpy - Copy a NUL-terminated string into a sized buffer +/// +/// Compatible with *BSD strlcpy: the result is always a valid NUL-terminated +/// string that fits in the buffer (unless, of course, the buffer size is +/// zero). It does not pad out the result like strncpy() does. /// -/// Compatible with *BSD strlcpy: the result is always a valid -/// NUL-terminated string that fits in the buffer (unless, -/// of course, the buffer size is zero). It does not pad -/// out the result like strncpy() does. +/// @param[out] dst Buffer to store the result. +/// @param[in] src String to be copied. +/// @param[in] dsize Size of `dst`. /// -/// @param dst Where to copy the string to -/// @param src Where to copy the string from -/// @param size Size of destination buffer -/// @return Length of the source string (i.e.: strlen(src)) -size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size) +/// @return Length of `src`. May be greater than `dsize - 1`, which would mean +/// that string was truncated. +size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t dsize) FUNC_ATTR_NONNULL_ALL { - size_t ret = strlen(src); + size_t slen = strlen(src); - if (size) { - size_t len = (ret >= size) ? size - 1 : ret; - memcpy(dst, src, len); - dst[len] = '\0'; - } + if (dsize) { + size_t len = MIN(slen, dsize - 1); + memcpy(dst, src, len); + dst[len] = '\0'; + } + + return slen; // Does not include NUL. +} + +/// Appends `src` to string `dst` of size `dsize` (unlike strncat, dsize is the +/// full size of `dst`, not space left). At most dsize-1 characters +/// will be copied. Always NUL terminates. `src` and `dst` may overlap. +/// +/// @see vim_strcat from Vim. +/// @see strlcat from OpenBSD. +/// +/// @param[in,out] dst Buffer to be appended-to. Must have a NUL byte. +/// @param[in] src String to put at the end of `dst`. +/// @param[in] dsize Size of `dst` including NUL byte. Must be greater than 0. +/// +/// @return Length of the resulting string as if destination size was #SIZE_MAX. +/// May be greater than `dsize - 1`, which would mean that string was +/// truncated. +size_t xstrlcat(char *const dst, const char *const src, const size_t dsize) + FUNC_ATTR_NONNULL_ALL +{ + assert(dsize > 0); + const size_t dlen = strlen(dst); + assert(dlen < dsize); + const size_t slen = strlen(src); + + if (slen > dsize - dlen - 1) { + memmove(dst + dlen, src, dsize - dlen - 1); + dst[dsize - 1] = '\0'; + } else { + memmove(dst + dlen, src, slen + 1); + } - return ret; + return slen + dlen; // Does not include NUL. } /// strdup() wrapper @@ -374,10 +430,24 @@ size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size) /// @return pointer to a copy of the string char *xstrdup(const char *str) FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NONNULL_ALL { return xmemdupz(str, strlen(str)); } +/// strdup() wrapper +/// +/// Unlike xstrdup() allocates a new empty string if it receives NULL. +char *xstrdupnul(const char *const str) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET +{ + if (str == NULL) { + return xmallocz(0); + } else { + return xstrdup(str); + } +} + /// A version of memchr that starts the search at `src + len`. /// /// Based on glibc's memrchr. @@ -404,6 +474,7 @@ void *xmemrchr(const void *src, uint8_t c, size_t len) /// @return pointer to a copy of the string char *xstrndup(const char *str, size_t len) FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NONNULL_ALL { char *p = memchr(str, '\0', len); return xmemdupz(str, p ? (size_t)(p - str) : len); @@ -422,6 +493,20 @@ void *xmemdup(const void *data, size_t len) return memcpy(xmalloc(len), data, len); } +/// Returns true if strings `a` and `b` are equal. Arguments may be NULL. +bool strequal(const char *a, const char *b) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (a == NULL && b == NULL) || (a && b && strcmp(a, b) == 0); +} + +/// Case-insensitive `strequal`. +bool striequal(const char *a, const char *b) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (a == NULL && b == NULL) || (a && b && STRICMP(a, b) == 0); +} + /* * Avoid repeating the error message many times (they take 1 second each). * Did_outofmem_msg is reset when a character is read. @@ -440,6 +525,16 @@ void do_outofmem_msg(size_t size) } } +/// Writes time_t to "buf[8]". +void time_to_bytes(time_t time_, uint8_t buf[8]) +{ + // time_t can be up to 8 bytes in size, more than uintmax_t in 32 bits + // systems, thus we can't use put_bytes() here. + for (size_t i = 7, bufi = 0; bufi < 8; i--, bufi++) { + buf[bufi] = (uint8_t)((uint64_t)time_ >> (i * 8)); + } +} + #if defined(EXITFREE) #include "nvim/file_search.h" @@ -470,6 +565,7 @@ void do_outofmem_msg(size_t size) #include "nvim/tag.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/eval/typval.h" /* * Free everything that we allocated. @@ -481,13 +577,13 @@ void do_outofmem_msg(size_t size) void free_all_mem(void) { buf_T *buf, *nextbuf; - static bool entered = false; - /* When we cause a crash here it is caught and Vim tries to exit cleanly. - * Don't try freeing everything again. */ - if (entered) + // When we cause a crash here it is caught and Vim tries to exit cleanly. + // Don't try freeing everything again. + if (entered_free_all_mem) { return; - entered = true; + } + entered_free_all_mem = true; // Don't want to trigger autocommands from here on. block_autocmds(); @@ -496,7 +592,7 @@ void free_all_mem(void) p_ea = false; if (first_tabpage->tp_next != NULL) do_cmdline_cmd("tabonly!"); - if (firstwin != lastwin) + if (!ONE_WINDOW) do_cmdline_cmd("only!"); /* Free all spell info. */ @@ -524,7 +620,6 @@ void free_all_mem(void) /* Obviously named calls. */ free_all_autocmds(); - free_all_options(); free_all_marks(); alist_clear(&global_alist); free_homedir(); @@ -539,7 +634,7 @@ void free_all_mem(void) free_signs(); set_expr_line(NULL); diff_clear(curtab); - clear_sb_text(); /* free any scrollback text */ + clear_sb_text(true); // free any scrollback text /* Free some global vars. */ xfree(last_cmdline); @@ -562,17 +657,8 @@ void free_all_mem(void) /* Destroy all windows. Must come before freeing buffers. */ win_free_all(); - /* Free all buffers. Reset 'autochdir' to avoid accessing things that - * were freed already. */ - p_acd = false; - for (buf = firstbuf; buf != NULL; ) { - nextbuf = buf->b_next; - close_buffer(NULL, buf, DOBUF_WIPE, false); - if (buf_valid(buf)) - buf = nextbuf; /* didn't work, try next one */ - else - buf = firstbuf; - } + // Free all option values. Must come after closing windows. + free_all_options(); free_cmdline_buf(); @@ -596,11 +682,27 @@ void free_all_mem(void) break; eval_clear(); + api_vim_free_all_mem(); + + // Free all buffers. Reset 'autochdir' to avoid accessing things that + // were freed already. + // Must be after eval_clear to avoid it trying to access b:changedtick after + // freeing it. + p_acd = false; + for (buf = firstbuf; buf != NULL; ) { + bufref_T bufref; + set_bufref(&bufref, buf); + nextbuf = buf->b_next; + close_buffer(NULL, buf, DOBUF_WIPE, false); + // Didn't work, try next one. + buf = bufref_valid(&bufref) ? nextbuf : firstbuf; + } - /* screenlines (can't display anything now!) */ - free_screenlines(); + // free screenlines (can't display anything now!) + screen_free_all_mem(); - clear_hl_tables(); + clear_hl_tables(false); + list_free_log(); } #endif |