vim/src/alloc.c
RestorerZ 68ebcee023 patch 9.0.1594: some internal error messages are translated
Problem:    Some internal error messages are translated.
Solution:   Consistently do not translate internal error messages.
            (closes #12459)
2023-05-31 17:12:14 +01:00

895 lines
19 KiB
C

/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* alloc.c: functions for memory management
*/
#include "vim.h"
/**********************************************************************
* Various routines dealing with allocation and deallocation of memory.
*/
#if defined(MEM_PROFILE) || defined(PROTO)
# define MEM_SIZES 8200
static long_u mem_allocs[MEM_SIZES];
static long_u mem_frees[MEM_SIZES];
static long_u mem_allocated;
static long_u mem_freed;
static long_u mem_peak;
static long_u num_alloc;
static long_u num_freed;
static void
mem_pre_alloc_s(size_t *sizep)
{
*sizep += sizeof(size_t);
}
static void
mem_pre_alloc_l(size_t *sizep)
{
*sizep += sizeof(size_t);
}
static void
mem_post_alloc(
void **pp,
size_t size)
{
if (*pp == NULL)
return;
size -= sizeof(size_t);
*(long_u *)*pp = size;
if (size <= MEM_SIZES-1)
mem_allocs[size-1]++;
else
mem_allocs[MEM_SIZES-1]++;
mem_allocated += size;
if (mem_allocated - mem_freed > mem_peak)
mem_peak = mem_allocated - mem_freed;
num_alloc++;
*pp = (void *)((char *)*pp + sizeof(size_t));
}
static void
mem_pre_free(void **pp)
{
long_u size;
*pp = (void *)((char *)*pp - sizeof(size_t));
size = *(size_t *)*pp;
if (size <= MEM_SIZES-1)
mem_frees[size-1]++;
else
mem_frees[MEM_SIZES-1]++;
mem_freed += size;
num_freed++;
}
/*
* called on exit via atexit()
*/
void
vim_mem_profile_dump(void)
{
int i, j;
printf("\r\n");
j = 0;
for (i = 0; i < MEM_SIZES - 1; i++)
{
if (mem_allocs[i] == 0 && mem_frees[i] == 0)
continue;
if (mem_frees[i] > mem_allocs[i])
printf("\r\n%s", _("ERROR: "));
printf("[%4d / %4lu-%-4lu] ", i + 1, mem_allocs[i], mem_frees[i]);
j++;
if (j > 3)
{
j = 0;
printf("\r\n");
}
}
i = MEM_SIZES - 1;
if (mem_allocs[i])
{
printf("\r\n");
if (mem_frees[i] > mem_allocs[i])
puts(_("ERROR: "));
printf("[>%d / %4lu-%-4lu]", i, mem_allocs[i], mem_frees[i]);
}
printf(_("\n[bytes] total alloc-freed %lu-%lu, in use %lu, peak use %lu\n"),
mem_allocated, mem_freed, mem_allocated - mem_freed, mem_peak);
printf(_("[calls] total re/malloc()'s %lu, total free()'s %lu\n\n"),
num_alloc, num_freed);
}
#endif // MEM_PROFILE
#ifdef FEAT_EVAL
int
alloc_does_fail(size_t size)
{
if (alloc_fail_countdown == 0)
{
if (--alloc_fail_repeat <= 0)
alloc_fail_id = 0;
do_outofmem_msg(size);
return TRUE;
}
--alloc_fail_countdown;
return FALSE;
}
#endif
/*
* Some memory is reserved for error messages and for being able to
* call mf_release_all(), which needs some memory for mf_trans_add().
*/
#define KEEP_ROOM (2 * 8192L)
#define KEEP_ROOM_KB (KEEP_ROOM / 1024L)
/*
* The normal way to allocate memory. This handles an out-of-memory situation
* as well as possible, still returns NULL when we're completely out.
*/
void *
alloc(size_t size)
{
return lalloc(size, TRUE);
}
#if defined(FEAT_QUICKFIX) || defined(PROTO)
/*
* alloc() with an ID for alloc_fail().
*/
void *
alloc_id(size_t size, alloc_id_T id UNUSED)
{
# ifdef FEAT_EVAL
if (alloc_fail_id == id && alloc_does_fail(size))
return NULL;
# endif
return lalloc(size, TRUE);
}
#endif
/*
* Allocate memory and set all bytes to zero.
*/
void *
alloc_clear(size_t size)
{
void *p;
p = lalloc(size, TRUE);
if (p != NULL)
(void)vim_memset(p, 0, size);
return p;
}
/*
* Same as alloc_clear() but with allocation id for testing
*/
void *
alloc_clear_id(size_t size, alloc_id_T id UNUSED)
{
#ifdef FEAT_EVAL
if (alloc_fail_id == id && alloc_does_fail(size))
return NULL;
#endif
return alloc_clear(size);
}
/*
* Allocate memory like lalloc() and set all bytes to zero.
*/
void *
lalloc_clear(size_t size, int message)
{
void *p;
p = lalloc(size, message);
if (p != NULL)
(void)vim_memset(p, 0, size);
return p;
}
/*
* Low level memory allocation function.
* This is used often, KEEP IT FAST!
*/
void *
lalloc(size_t size, int message)
{
void *p; // pointer to new storage space
static int releasing = FALSE; // don't do mf_release_all() recursive
int try_again;
#if defined(HAVE_AVAIL_MEM)
static size_t allocated = 0; // allocated since last avail check
#endif
// Safety check for allocating zero bytes
if (size == 0)
{
// Don't hide this message
emsg_silent = 0;
iemsg(e_internal_error_lalloc_zero);
return NULL;
}
#ifdef MEM_PROFILE
mem_pre_alloc_l(&size);
#endif
// Loop when out of memory: Try to release some memfile blocks and
// if some blocks are released call malloc again.
for (;;)
{
// Handle three kinds of systems:
// 1. No check for available memory: Just return.
// 2. Slow check for available memory: call mch_avail_mem() after
// allocating KEEP_ROOM amount of memory.
// 3. Strict check for available memory: call mch_avail_mem()
if ((p = malloc(size)) != NULL)
{
#ifndef HAVE_AVAIL_MEM
// 1. No check for available memory: Just return.
goto theend;
#else
// 2. Slow check for available memory: call mch_avail_mem() after
// allocating (KEEP_ROOM / 2) amount of memory.
allocated += size;
if (allocated < KEEP_ROOM / 2)
goto theend;
allocated = 0;
// 3. check for available memory: call mch_avail_mem()
if (mch_avail_mem(TRUE) < KEEP_ROOM_KB && !releasing)
{
free(p); // System is low... no go!
p = NULL;
}
else
goto theend;
#endif
}
// Remember that mf_release_all() is being called to avoid an endless
// loop, because mf_release_all() may call alloc() recursively.
if (releasing)
break;
releasing = TRUE;
clear_sb_text(TRUE); // free any scrollback text
try_again = mf_release_all(); // release as many blocks as possible
releasing = FALSE;
if (!try_again)
break;
}
if (message && p == NULL)
do_outofmem_msg(size);
theend:
#ifdef MEM_PROFILE
mem_post_alloc(&p, size);
#endif
return p;
}
/*
* lalloc() with an ID for alloc_fail().
*/
#if defined(FEAT_SIGNS) || defined(PROTO)
void *
lalloc_id(size_t size, int message, alloc_id_T id UNUSED)
{
#ifdef FEAT_EVAL
if (alloc_fail_id == id && alloc_does_fail(size))
return NULL;
#endif
return (lalloc(size, message));
}
#endif
#if defined(MEM_PROFILE) || defined(PROTO)
/*
* realloc() with memory profiling.
*/
void *
mem_realloc(void *ptr, size_t size)
{
void *p;
mem_pre_free(&ptr);
mem_pre_alloc_s(&size);
p = realloc(ptr, size);
mem_post_alloc(&p, size);
return p;
}
#endif
/*
* Avoid repeating the error message many times (they take 1 second each).
* Did_outofmem_msg is reset when a character is read.
*/
void
do_outofmem_msg(size_t size)
{
if (did_outofmem_msg)
return;
// Don't hide this message
emsg_silent = 0;
// Must come first to avoid coming back here when printing the error
// message fails, e.g. when setting v:errmsg.
did_outofmem_msg = TRUE;
semsg(_(e_out_of_memory_allocating_nr_bytes), (long_u)size);
if (starting == NO_SCREEN)
// Not even finished with initializations and already out of
// memory? Then nothing is going to work, exit.
mch_exit(123);
}
#if defined(EXITFREE) || defined(PROTO)
/*
* Free everything that we allocated.
* Can be used to detect memory leaks, e.g., with ccmalloc.
* NOTE: This is tricky! Things are freed that functions depend on. Don't be
* surprised if Vim crashes...
* Some things can't be freed, esp. things local to a library function.
*/
void
free_all_mem(void)
{
buf_T *buf, *nextbuf;
// 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_free_all_mem = TRUE;
// Don't want to trigger autocommands from here on.
block_autocmds();
// Close all tabs and windows. Reset 'equalalways' to avoid redraws.
p_ea = FALSE;
if (first_tabpage != NULL && first_tabpage->tp_next != NULL)
do_cmdline_cmd((char_u *)"tabonly!");
if (!ONE_WINDOW)
do_cmdline_cmd((char_u *)"only!");
# if defined(FEAT_SPELL)
// Free all spell info.
spell_free_all();
# endif
# if defined(FEAT_BEVAL_TERM)
ui_remove_balloon();
# endif
# ifdef FEAT_PROP_POPUP
if (curwin != NULL)
close_all_popups(TRUE);
# endif
// Clear user commands (before deleting buffers).
ex_comclear(NULL);
// When exiting from mainerr_arg_missing curbuf has not been initialized,
// and not much else.
if (curbuf != NULL)
{
# ifdef FEAT_MENU
// Clear menus.
do_cmdline_cmd((char_u *)"aunmenu *");
do_cmdline_cmd((char_u *)"tlunmenu *");
# ifdef FEAT_MULTI_LANG
do_cmdline_cmd((char_u *)"menutranslate clear");
# endif
# endif
// Clear mappings, abbreviations, breakpoints.
do_cmdline_cmd((char_u *)"lmapclear");
do_cmdline_cmd((char_u *)"xmapclear");
do_cmdline_cmd((char_u *)"mapclear");
do_cmdline_cmd((char_u *)"mapclear!");
do_cmdline_cmd((char_u *)"abclear");
# if defined(FEAT_EVAL)
do_cmdline_cmd((char_u *)"breakdel *");
# endif
# if defined(FEAT_PROFILE)
do_cmdline_cmd((char_u *)"profdel *");
# endif
# if defined(FEAT_KEYMAP)
do_cmdline_cmd((char_u *)"set keymap=");
# endif
}
free_titles();
free_findfile();
// Obviously named calls.
free_all_autocmds();
clear_termcodes();
free_all_marks();
alist_clear(&global_alist);
free_homedir();
free_users();
free_search_patterns();
free_old_sub();
free_last_insert();
free_insexpand_stuff();
free_prev_shellcmd();
free_regexp_stuff();
free_tag_stuff();
free_xim_stuff();
free_cd_dir();
# ifdef FEAT_SIGNS
free_signs();
# endif
# ifdef FEAT_EVAL
set_expr_line(NULL, NULL);
# endif
# ifdef FEAT_DIFF
if (curtab != NULL)
diff_clear(curtab);
# endif
clear_sb_text(TRUE); // free any scrollback text
// Free some global vars.
free_username();
# ifdef FEAT_CLIPBOARD
vim_regfree(clip_exclude_prog);
# endif
vim_free(last_cmdline);
vim_free(new_last_cmdline);
set_keep_msg(NULL, 0);
// Clear cmdline history.
p_hi = 0;
init_history();
# ifdef FEAT_PROP_POPUP
clear_global_prop_types();
# endif
# ifdef FEAT_QUICKFIX
free_quickfix();
# endif
// Close all script inputs.
close_all_scripts();
if (curwin != NULL)
// Destroy all windows. Must come before freeing buffers.
win_free_all();
// Free all option values. Must come after closing windows.
free_all_options();
// Free all buffers. Reset 'autochdir' to avoid accessing things that
// were freed already.
# ifdef FEAT_AUTOCHDIR
p_acd = FALSE;
# endif
for (buf = firstbuf; buf != NULL; )
{
bufref_T bufref;
set_bufref(&bufref, buf);
nextbuf = buf->b_next;
close_buffer(NULL, buf, DOBUF_WIPE, FALSE, FALSE);
if (bufref_valid(&bufref))
buf = nextbuf; // didn't work, try next one
else
buf = firstbuf;
}
# ifdef FEAT_ARABIC
free_arshape_buf();
# endif
// Clear registers.
clear_registers();
ResetRedobuff();
ResetRedobuff();
# if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11)
vim_free(serverDelayedStartName);
# endif
// highlight info
free_highlight();
reset_last_sourcing();
if (first_tabpage != NULL)
{
free_tabpage(first_tabpage);
first_tabpage = NULL;
}
# ifdef UNIX
// Machine-specific free.
mch_free_mem();
# endif
// message history
for (;;)
if (delete_first_msg() == FAIL)
break;
# ifdef FEAT_JOB_CHANNEL
channel_free_all();
# endif
# ifdef FEAT_TIMERS
timer_free_all();
# endif
# ifdef FEAT_EVAL
// must be after channel_free_all() with unrefs partials
eval_clear();
# endif
# ifdef FEAT_JOB_CHANNEL
// must be after eval_clear() with unrefs jobs
job_free_all();
# endif
free_termoptions();
free_cur_term();
// screenlines (can't display anything now!)
free_screenlines();
# if defined(FEAT_SOUND)
sound_free();
# endif
# if defined(USE_XSMP)
xsmp_close();
# endif
# ifdef FEAT_GUI_GTK
gui_mch_free_all();
# endif
# ifdef FEAT_TCL
vim_tcl_finalize();
# endif
clear_hl_tables();
vim_free(IObuff);
vim_free(NameBuff);
# ifdef FEAT_QUICKFIX
check_quickfix_busy();
# endif
# ifdef FEAT_EVAL
free_resub_eval_result();
# endif
free_vbuf();
}
#endif
/*
* Copy "p[len]" into allocated memory, ignoring NUL characters.
* Returns NULL when out of memory.
*/
char_u *
vim_memsave(char_u *p, size_t len)
{
char_u *ret = alloc(len);
if (ret != NULL)
mch_memmove(ret, p, len);
return ret;
}
/*
* Replacement for free() that ignores NULL pointers.
* Also skip free() when exiting for sure, this helps when we caught a deadly
* signal that was caused by a crash in free().
* If you want to set NULL after calling this function, you should use
* VIM_CLEAR() instead.
*/
void
vim_free(void *x)
{
if (x != NULL && !really_exiting)
{
#ifdef MEM_PROFILE
mem_pre_free(&x);
#endif
free(x);
}
}
/************************************************************************
* Functions for handling growing arrays.
*/
/*
* Clear an allocated growing array.
*/
void
ga_clear(garray_T *gap)
{
vim_free(gap->ga_data);
ga_init(gap);
}
/*
* Clear a growing array that contains a list of strings.
*/
void
ga_clear_strings(garray_T *gap)
{
int i;
if (gap->ga_data != NULL)
for (i = 0; i < gap->ga_len; ++i)
vim_free(((char_u **)(gap->ga_data))[i]);
ga_clear(gap);
}
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* Copy a growing array that contains a list of strings.
*/
int
ga_copy_strings(garray_T *from, garray_T *to)
{
int i;
ga_init2(to, sizeof(char_u *), 1);
if (ga_grow(to, from->ga_len) == FAIL)
return FAIL;
for (i = 0; i < from->ga_len; ++i)
{
char_u *orig = ((char_u **)from->ga_data)[i];
char_u *copy;
if (orig == NULL)
copy = NULL;
else
{
copy = vim_strsave(orig);
if (copy == NULL)
{
to->ga_len = i;
ga_clear_strings(to);
return FAIL;
}
}
((char_u **)to->ga_data)[i] = copy;
}
to->ga_len = from->ga_len;
return OK;
}
#endif
/*
* Initialize a growing array. Don't forget to set ga_itemsize and
* ga_growsize! Or use ga_init2().
*/
void
ga_init(garray_T *gap)
{
gap->ga_data = NULL;
gap->ga_maxlen = 0;
gap->ga_len = 0;
}
void
ga_init2(garray_T *gap, size_t itemsize, int growsize)
{
ga_init(gap);
gap->ga_itemsize = (int)itemsize;
gap->ga_growsize = growsize;
}
/*
* Make room in growing array "gap" for at least "n" items.
* Return FAIL for failure, OK otherwise.
*/
int
ga_grow(garray_T *gap, int n)
{
if (gap->ga_maxlen - gap->ga_len < n)
return ga_grow_inner(gap, n);
return OK;
}
/*
* Same as ga_grow() but uses an allocation id for testing.
*/
int
ga_grow_id(garray_T *gap, int n, alloc_id_T id UNUSED)
{
#ifdef FEAT_EVAL
if (alloc_fail_id == id && alloc_does_fail(sizeof(list_T)))
return FAIL;
#endif
return ga_grow(gap, n);
}
int
ga_grow_inner(garray_T *gap, int n)
{
size_t old_len;
size_t new_len;
char_u *pp;
if (n < gap->ga_growsize)
n = gap->ga_growsize;
// A linear growth is very inefficient when the array grows big. This
// is a compromise between allocating memory that won't be used and too
// many copy operations. A factor of 1.5 seems reasonable.
if (n < gap->ga_len / 2)
n = gap->ga_len / 2;
new_len = (size_t)gap->ga_itemsize * (gap->ga_len + n);
pp = vim_realloc(gap->ga_data, new_len);
if (pp == NULL)
return FAIL;
old_len = (size_t)gap->ga_itemsize * gap->ga_maxlen;
vim_memset(pp + old_len, 0, new_len - old_len);
gap->ga_maxlen = gap->ga_len + n;
gap->ga_data = pp;
return OK;
}
/*
* For a growing array that contains a list of strings: concatenate all the
* strings with a separating "sep".
* Returns NULL when out of memory.
*/
char_u *
ga_concat_strings(garray_T *gap, char *sep)
{
int i;
int len = 0;
int sep_len = (int)STRLEN(sep);
char_u *s;
char_u *p;
for (i = 0; i < gap->ga_len; ++i)
len += (int)STRLEN(((char_u **)(gap->ga_data))[i]) + sep_len;
s = alloc(len + 1);
if (s == NULL)
return NULL;
*s = NUL;
p = s;
for (i = 0; i < gap->ga_len; ++i)
{
if (p != s)
{
STRCPY(p, sep);
p += sep_len;
}
STRCPY(p, ((char_u **)(gap->ga_data))[i]);
p += STRLEN(p);
}
return s;
}
/*
* Make a copy of string "p" and add it to "gap".
* When out of memory nothing changes and FAIL is returned.
*/
int
ga_copy_string(garray_T *gap, char_u *p)
{
char_u *cp = vim_strsave(p);
if (cp == NULL)
return FAIL;
if (ga_grow(gap, 1) == FAIL)
{
vim_free(cp);
return FAIL;
}
((char_u **)(gap->ga_data))[gap->ga_len++] = cp;
return OK;
}
/*
* Add string "p" to "gap".
* When out of memory FAIL is returned (caller may want to free "p").
*/
int
ga_add_string(garray_T *gap, char_u *p)
{
if (ga_grow(gap, 1) == FAIL)
return FAIL;
((char_u **)(gap->ga_data))[gap->ga_len++] = p;
return OK;
}
/*
* Concatenate a string to a growarray which contains bytes.
* When "s" is NULL memory allocation fails does not do anything.
* Note: Does NOT copy the NUL at the end!
*/
void
ga_concat(garray_T *gap, char_u *s)
{
int len;
if (s == NULL || *s == NUL)
return;
len = (int)STRLEN(s);
if (ga_grow(gap, len) == OK)
{
mch_memmove((char *)gap->ga_data + gap->ga_len, s, (size_t)len);
gap->ga_len += len;
}
}
/*
* Concatenate 'len' bytes from string 's' to a growarray.
* When "s" is NULL does not do anything.
*/
void
ga_concat_len(garray_T *gap, char_u *s, size_t len)
{
if (s == NULL || *s == NUL || len == 0)
return;
if (ga_grow(gap, (int)len) == OK)
{
mch_memmove((char *)gap->ga_data + gap->ga_len, s, len);
gap->ga_len += (int)len;
}
}
/*
* Append one byte to a growarray which contains bytes.
*/
int
ga_append(garray_T *gap, int c)
{
if (ga_grow(gap, 1) == FAIL)
return FAIL;
*((char *)gap->ga_data + gap->ga_len) = c;
++gap->ga_len;
return OK;
}
#if (defined(UNIX) && !defined(USE_SYSTEM)) || defined(MSWIN) \
|| defined(PROTO)
/*
* Append the text in "gap" below the cursor line and clear "gap".
*/
void
append_ga_line(garray_T *gap)
{
// Remove trailing CR.
if (gap->ga_len > 0
&& !curbuf->b_p_bin
&& ((char_u *)gap->ga_data)[gap->ga_len - 1] == CAR)
--gap->ga_len;
ga_append(gap, NUL);
ml_append(curwin->w_cursor.lnum++, gap->ga_data, 0, FALSE);
gap->ga_len = 0;
}
#endif