patch 9.1.1291: too many strlen() calls in buffer.c

Problem:  too many strlen() calls in buffer.c
Solution: refactor buffer.c and remove strlen() calls
          (John Marriott)

closes: #17063

Signed-off-by: John Marriott <basilisk@internode.on.net>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/buffer.c b/src/buffer.c
index 90ca596..955800e 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -45,7 +45,7 @@
 static int	otherfile_buf(buf_T *buf, char_u *ffname);
 #endif
 static int	value_changed(char_u *str, char_u **last);
-static int	append_arg_number(win_T *wp, char_u *buf, int buflen, int add_file);
+static int	append_arg_number(win_T *wp, char_u *buf, size_t buflen, int add_file);
 static void	free_buffer(buf_T *);
 static void	free_buffer_stuff(buf_T *buf, int free_options);
 static int	bt_nofileread(buf_T *buf);
@@ -73,6 +73,19 @@
 static garray_T buf_reuse = GA_EMPTY;	// file numbers to recycle
 
 /*
+ * Calculate the percentage that `part` is of the `whole`.
+ */
+    static int
+calc_percentage(long part, long whole)
+{
+    // With 32 bit longs and more than 21,474,836 lines multiplying by 100
+    // causes an overflow, thus for large numbers divide instead.
+    return (part > 1000000L)
+	? (int)(part / (whole / 100L))
+	: (int)((part * 100L) / whole);
+}
+
+/*
  * Return the highest possible buffer number.
  */
     int
@@ -454,7 +467,7 @@
     static void
 buf_hashtab_add(buf_T *buf)
 {
-    sprintf((char *)buf->b_key, "%x", buf->b_fnum);
+    vim_snprintf((char *)buf->b_key, sizeof(buf->b_key), "%x", buf->b_fnum);
     if (hash_add(&buf_hashtab, buf->b_key, "create buffer") == FAIL)
 	emsg(_(e_buffer_cannot_be_registered));
 }
@@ -3088,7 +3101,7 @@
 
     if (nr == 0)
 	nr = curwin->w_alt_fnum;
-    sprintf((char *)key, "%x", nr);
+    vim_snprintf((char *)key, sizeof(key), "%x", nr);
     hi = hash_find(&buf_hashtab, key);
 
     if (!HASHITEM_EMPTY(hi))
@@ -3385,6 +3398,8 @@
     for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next)
 #endif
     {
+	char_u	*name;
+
 #ifdef FEAT_TERMINAL
 	job_running = term_job_running(buf->b_term);
 	job_none_open = term_none_open(buf->b_term);
@@ -3413,8 +3428,9 @@
 		|| (vim_strchr(eap->arg, '#')
 		      && (buf == curbuf || curwin->w_alt_fnum != buf->b_fnum)))
 	    continue;
-	if (buf_spname(buf) != NULL)
-	    vim_strncpy(NameBuff, buf_spname(buf), MAXPATHL - 1);
+	name = buf_spname(buf);
+	if (name != NULL)
+	    vim_strncpy(NameBuff, name, MAXPATHL - 1);
 	else
 	    home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE);
 	if (message_filtered(NameBuff))
@@ -3439,7 +3455,7 @@
 	    ro_char = !buf->b_p_ma ? '-' : (buf->b_p_ro ? '=' : ' ');
 
 	msg_putchar('\n');
-	len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"",
+	len = (int)vim_snprintf_safelen((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"",
 		buf->b_fnum,
 		buf->b_p_bl ? ' ' : 'u',
 		buf == curbuf ? '%' :
@@ -3449,8 +3465,6 @@
 		ro_char,
 		changed_char,
 		NameBuff);
-	if (len > IOSIZE - 20)
-	    len = IOSIZE - 20;
 
 	// put "line 999" in column 40 or after the file name
 	i = 40 - vim_strsize(IObuff);
@@ -3850,83 +3864,81 @@
     int	dont_truncate)
 {
     char_u	*name;
-    int		n;
-    char	*p;
     char	*buffer;
-    size_t	len;
+    size_t	bufferlen = 0;
 
     buffer = alloc(IOSIZE);
     if (buffer == NULL)
 	return;
 
     if (fullname > 1)	    // 2 CTRL-G: include buffer number
-    {
-	vim_snprintf(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum);
-	p = buffer + STRLEN(buffer);
-    }
-    else
-	p = buffer;
+	bufferlen = vim_snprintf_safelen(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum);
 
-    *p++ = '"';
-    if (buf_spname(curbuf) != NULL)
-	vim_strncpy((char_u *)p, buf_spname(curbuf), IOSIZE - (p - buffer) - 1);
+    buffer[bufferlen++] = '"';
+
+    name = buf_spname(curbuf);
+    if (name != NULL)
+	bufferlen += vim_snprintf_safelen(buffer + bufferlen,
+	    IOSIZE - bufferlen, "%s", name);
     else
     {
 	if (!fullname && curbuf->b_fname != NULL)
 	    name = curbuf->b_fname;
 	else
 	    name = curbuf->b_ffname;
-	home_replace(shorthelp ? curbuf : NULL, name, (char_u *)p,
-					  (int)(IOSIZE - (p - buffer)), TRUE);
+	home_replace(shorthelp ? curbuf : NULL, name, (char_u *)buffer + bufferlen,
+					  IOSIZE - (int)bufferlen, TRUE);
+	bufferlen += STRLEN(buffer + bufferlen);
     }
 
-    vim_snprintf_add(buffer, IOSIZE, "\"%s%s%s%s%s%s",
-	    curbufIsChanged() ? (shortmess(SHM_MOD)
-					  ?  " [+]" : _(" [Modified]")) : " ",
-	    (curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf)
-					? _("[Not edited]") : "",
-	    (curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf)
-					   ? new_file_message() : "",
-	    (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "",
-	    curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]")
-						      : _("[readonly]")) : "",
-	    (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK)
-							  || curbuf->b_p_ro) ?
-								    " " : "");
-    // With 32 bit longs and more than 21,474,836 lines multiplying by 100
-    // causes an overflow, thus for large numbers divide instead.
-    if (curwin->w_cursor.lnum > 1000000L)
-	n = (int)(((long)curwin->w_cursor.lnum) /
-				   ((long)curbuf->b_ml.ml_line_count / 100L));
-    else
-	n = (int)(((long)curwin->w_cursor.lnum * 100L) /
-					    (long)curbuf->b_ml.ml_line_count);
+    bufferlen += vim_snprintf_safelen(
+	buffer + bufferlen,
+	IOSIZE - bufferlen,
+	"\"%s%s%s%s%s%s",
+	curbufIsChanged() ? (shortmess(SHM_MOD)
+	    ?  " [+]" : _(" [Modified]")) : " ",
+	(curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf)
+	    ? _("[Not edited]") : "",
+	(curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf)
+	    ? new_file_message() : "",
+	(curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "", curbuf->b_p_ro
+	    ? (shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")) : "",
+	(curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK) || curbuf->b_p_ro)
+	    ? " " : "");
+
     if (curbuf->b_ml.ml_flags & ML_EMPTY)
-	vim_snprintf_add(buffer, IOSIZE, "%s", _(no_lines_msg));
+	bufferlen += vim_snprintf_safelen(buffer + bufferlen,
+	    IOSIZE - bufferlen, "%s", _(no_lines_msg));
     else if (p_ru)
 	// Current line and column are already on the screen -- webb
-	vim_snprintf_add(buffer, IOSIZE,
-		NGETTEXT("%ld line --%d%%--", "%ld lines --%d%%--",
-						   curbuf->b_ml.ml_line_count),
-		(long)curbuf->b_ml.ml_line_count, n);
+	bufferlen += vim_snprintf_safelen(
+	    buffer + bufferlen,
+	    IOSIZE - bufferlen,
+	    NGETTEXT("%ld line --%d%%--", "%ld lines --%d%%--", curbuf->b_ml.ml_line_count),
+	    (long)curbuf->b_ml.ml_line_count,
+	    calc_percentage(curwin->w_cursor.lnum, curbuf->b_ml.ml_line_count));
     else
     {
-	vim_snprintf_add(buffer, IOSIZE,
-		_("line %ld of %ld --%d%%-- col "),
-		(long)curwin->w_cursor.lnum,
-		(long)curbuf->b_ml.ml_line_count,
-		n);
+	bufferlen += vim_snprintf_safelen(
+	    buffer + bufferlen,
+	    IOSIZE - bufferlen,
+	    _("line %ld of %ld --%d%%-- col "),
+	    (long)curwin->w_cursor.lnum,
+	    (long)curbuf->b_ml.ml_line_count,
+	    calc_percentage(curwin->w_cursor.lnum, curbuf->b_ml.ml_line_count));
+
 	validate_virtcol();
-	len = STRLEN(buffer);
-	(void)col_print((char_u *)buffer + len, IOSIZE - len,
+	bufferlen += col_print((char_u *)buffer + bufferlen, IOSIZE - bufferlen,
 		   (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1);
     }
 
-    (void)append_arg_number(curwin, (char_u *)buffer, IOSIZE,
-							 !shortmess(SHM_FILE));
+    (void)append_arg_number(curwin, (char_u *)buffer + bufferlen,
+	IOSIZE - bufferlen, !shortmess(SHM_FILE));
 
     if (dont_truncate)
     {
+	int	n;
+
 	// Temporarily set msg_scroll to avoid the message being truncated.
 	// First call msg_start() to get the message in the right place.
 	msg_start();
@@ -3937,7 +3949,7 @@
     }
     else
     {
-	p = msg_trunc_attr(buffer, FALSE, 0);
+	char	*p = msg_trunc_attr(buffer, FALSE, 0);
 	if (restart_edit != 0 || (msg_scrolled && !need_wait_return))
 	    // Need to repeat the message after redrawing when:
 	    // - When restart_edit is set (otherwise there will be a delay
@@ -3958,9 +3970,9 @@
     int	    vcol)
 {
     if (col == vcol)
-	return vim_snprintf((char *)buf, buflen, "%d", col);
+	return (int)vim_snprintf_safelen((char *)buf, buflen, "%d", col);
 
-    return vim_snprintf((char *)buf, buflen, "%d-%d", col, vcol);
+    return (int)vim_snprintf_safelen((char *)buf, buflen, "%d-%d", col, vcol);
 }
 
 static char_u *lasttitle = NULL;
@@ -3972,14 +3984,11 @@
     void
 maketitle(void)
 {
-    char_u	*p;
     char_u	*title_str = NULL;
     char_u	*icon_str = NULL;
-    int		maxlen = 0;
-    int		len;
     int		mustset;
     char_u	buf[IOSIZE];
-    int		off;
+    size_t	buflen = 0;
 
     if (!redrawing())
     {
@@ -3994,6 +4003,8 @@
 
     if (p_title)
     {
+	int maxlen = 0;
+
 	if (p_titlelen > 0)
 	{
 	    maxlen = p_titlelen * Columns / 100;
@@ -4012,51 +4023,90 @@
 	    else
 #endif
 		title_str = p_titlestring;
+	    buflen = STRLEN(title_str);
 	}
 	else
 	{
-	    // format: "fname + (path) (1 of 2) - VIM"
+	    char_u  *p;
 
-#define SPACE_FOR_FNAME (IOSIZE - 100)
-#define SPACE_FOR_DIR   (IOSIZE - 20)
-#define SPACE_FOR_ARGNR (IOSIZE - 10)  // at least room for " - VIM"
+	    // format: "<filename> [flags] <(path)> [argument info] <- servername>"
+	    // example:
+	    //	    buffer.c + (/home/vim/src) (1 of 2) - VIM
+
+	    // reserve some space for different parts of the title.
+	    // use sizeof() to introduce 'size_t' so we don't have to
+	    // cast sizes to it.
+#define SPACE_FOR_FNAME (sizeof(buf) - 100)
+#define SPACE_FOR_DIR   (sizeof(buf) - 20)
+#define SPACE_FOR_ARGNR (sizeof(buf) - 10)  // at least room for " - VIM"
+
+	    // file name
 	    if (curbuf->b_fname == NULL)
-		vim_strncpy(buf, (char_u *)_("[No Name]"), SPACE_FOR_FNAME);
+		buflen = vim_snprintf_safelen((char *)buf,
+		    SPACE_FOR_FNAME, "%s", _("[No Name]"));
 #ifdef FEAT_TERMINAL
 	    else if (curbuf->b_term != NULL)
-	    {
-		vim_strncpy(buf, term_get_status_text(curbuf->b_term),
-							      SPACE_FOR_FNAME);
-	    }
+		buflen = vim_snprintf_safelen((char *)buf,
+		    SPACE_FOR_FNAME, "%s",
+		    term_get_status_text(curbuf->b_term));
 #endif
 	    else
 	    {
-		p = transstr(gettail(curbuf->b_fname));
-		if (p != NULL)
-		{
-		    vim_strncpy(buf, p, SPACE_FOR_FNAME);
-		    vim_free(p);
-		}
-		else
-		    STRCPY(buf, "");
+		buflen = vim_snprintf_safelen((char *)buf,
+		    SPACE_FOR_FNAME, "%s",
+		    ((p = transstr(gettail(curbuf->b_fname))) != NULL)
+			? p
+			: (char_u *)"");
+		vim_free(p);
 	    }
 
+	    // flags
 #ifdef FEAT_TERMINAL
 	    if (curbuf->b_term == NULL)
 #endif
+	    {
 		switch (bufIsChanged(curbuf)
 			+ (curbuf->b_p_ro * 2)
 			+ (!curbuf->b_p_ma * 4))
 		{
-		    case 1: STRCAT(buf, " +"); break;
-		    case 2: STRCAT(buf, " ="); break;
-		    case 3: STRCAT(buf, " =+"); break;
+		    case 1:
+			// file was modified
+			buflen += vim_snprintf_safelen(
+			    (char *)buf + buflen,
+			    sizeof(buf) - buflen, " +");
+			break;
+		    case 2:
+			// file is readonly
+			buflen += vim_snprintf_safelen(
+			    (char *)buf + buflen,
+			    sizeof(buf) - buflen, " =");
+			break;
+		    case 3:
+			// file was modified and is readonly
+			buflen += vim_snprintf_safelen(
+			    (char *)buf + buflen,
+			    sizeof(buf) - buflen, " =+");
+			break;
 		    case 4:
-		    case 6: STRCAT(buf, " -"); break;
+		    case 6:
+			// file cannot be modified
+			buflen += vim_snprintf_safelen(
+			    (char *)buf + buflen,
+			    sizeof(buf) - buflen, " -");
+			break;
 		    case 5:
-		    case 7: STRCAT(buf, " -+"); break;
+		    case 7:
+			// file cannot be modified but was modified
+			buflen += vim_snprintf_safelen(
+			    (char *)buf + buflen,
+			    sizeof(buf) - buflen, " -+");
+			break;
+		    default:
+			break;
 		}
+	    }
 
+	    // path (surrounded by '()')
 	    if (curbuf->b_fname != NULL
 #ifdef FEAT_TERMINAL
 		    && curbuf->b_term == NULL
@@ -4064,64 +4114,69 @@
 		    )
 	    {
 		// Get path of file, replace home dir with ~
-		off = (int)STRLEN(buf);
-		buf[off++] = ' ';
-		buf[off++] = '(';
+		buflen += vim_snprintf_safelen((char *)buf + buflen,
+		    sizeof(buf) - buflen, " (");
+
 		home_replace(curbuf, curbuf->b_ffname,
-					buf + off, SPACE_FOR_DIR - off, TRUE);
+		    buf + buflen, (int)(SPACE_FOR_DIR - buflen), TRUE);
+
 #ifdef BACKSLASH_IN_FILENAME
 		// avoid "c:/name" to be reduced to "c"
-		if (SAFE_isalpha(buf[off]) && buf[off + 1] == ':')
-		    off += 2;
+		if (SAFE_isalpha(buf[buflen]) && buf[buflen + 1] == ':')
+		    buflen += 2;			// step over "c:"
 #endif
-		// remove the file name
-		p = gettail_sep(buf + off);
-		if (p == buf + off)
+
+		// determine if we have a help or normal buffer
+		p = gettail_sep(buf + buflen);
+		if (p == buf + buflen)
 		{
-		    // must be a help buffer
-		    vim_strncpy(buf + off, (char_u *)_("help"),
-					   (size_t)(SPACE_FOR_DIR - off - 1));
+		    // help buffer
+		    buflen += vim_snprintf_safelen((char *)buf + buflen,
+			SPACE_FOR_DIR - buflen, "%s)", _("help"));
 		}
 		else
-		    *p = NUL;
-
-		// Translate unprintable chars and concatenate.  Keep some
-		// room for the server name.  When there is no room (very long
-		// file name) use (...).
-		if (off < SPACE_FOR_DIR)
 		{
-		    p = transstr(buf + off);
-		    if (p != NULL)
+		    // normal buffer
+
+		    // Translate unprintable chars and concatenate.  Keep some
+		    // room for the server name.  When there is no room (very long
+		    // file name) use (...).
+		    if (buflen < SPACE_FOR_DIR)
 		    {
-			vim_strncpy(buf + off, p, (size_t)(SPACE_FOR_DIR - off));
+			// remove the file name
+			*p = NUL;
+
+			buflen += vim_snprintf_safelen((char *)buf + buflen,
+			    SPACE_FOR_DIR - buflen, "%s)",
+			    ((p = transstr(buf + buflen)) != NULL)
+				? p
+				: (char_u *)"");
 			vim_free(p);
 		    }
+		    else
+			buflen += vim_snprintf_safelen((char *)buf + buflen,
+			    SPACE_FOR_ARGNR - buflen, "...)");
 		}
-		else
-		{
-		    vim_strncpy(buf + off, (char_u *)"...",
-					     (size_t)(SPACE_FOR_ARGNR - off));
-		}
-		STRCAT(buf, ")");
 	    }
 
-	    append_arg_number(curwin, buf, SPACE_FOR_ARGNR, FALSE);
+	    // argument info
+	    buflen += append_arg_number(curwin, buf + buflen,
+		SPACE_FOR_ARGNR - buflen, FALSE);
 
+	    // servername
+	    buflen += vim_snprintf_safelen((char *)buf + buflen,
+		sizeof(buf) - buflen, " - %s",
 #if defined(FEAT_CLIENTSERVER)
-	    if (serverName != NULL)
-	    {
-		STRCAT(buf, " - ");
-		vim_strcat(buf, serverName, IOSIZE);
-	    }
-	    else
+		(serverName != NULL)
+		    ? serverName :
 #endif
-		STRCAT(buf, " - VIM");
+		    (char_u *)"VIM");
 
 	    if (maxlen > 0)
 	    {
 		// make it shorter by removing a bit in the middle
 		if (vim_strsize(buf) > maxlen)
-		    trunc_string(buf, buf, maxlen, IOSIZE);
+		    trunc_string(buf, buf, maxlen, sizeof(buf));
 	    }
 	}
     }
@@ -4142,22 +4197,23 @@
 	}
 	else
 	{
-	    if (buf_spname(curbuf) != NULL)
-		p = buf_spname(curbuf);
-	    else		    // use file name only in icon
-		p = gettail(curbuf->b_ffname);
-	    *icon_str = NUL;
+	    char_u  *name;
+	    int	    namelen;
+
+	    name = buf_spname(curbuf);
+	    if (name == NULL)
+		name = gettail(curbuf->b_ffname);
 	    // Truncate name at 100 bytes.
-	    len = (int)STRLEN(p);
-	    if (len > 100)
+	    namelen = (int)STRLEN(name);
+	    if (namelen > 100)
 	    {
-		len -= 100;
+		namelen -= 100;
 		if (has_mbyte)
-		    len += (*mb_tail_off)(p, p + len) + 1;
-		p += len;
+		    namelen += (*mb_tail_off)(name, name + namelen) + 1;
+		name += namelen;
 	    }
-	    STRCPY(icon_str, p);
-	    trans_characters(icon_str, IOSIZE);
+	    STRCPY(buf, name);
+	    trans_characters(buf, sizeof(buf));
 	}
     }
 
@@ -4270,18 +4326,15 @@
 {
     linenr_T	lnum;
     colnr_T	len;
+    size_t	outputlen;	// length of out[] used (excluding the NUL)
     char_u	*p;
     char_u	*s;
-    char_u	*t;
     int		byteval;
 #ifdef FEAT_EVAL
     int		use_sandbox;
-    win_T	*save_curwin;
-    buf_T	*save_curbuf;
     int		save_VIsual_active;
 #endif
     int		empty_line;
-    colnr_T	virtcol;
     long	l;
     long	n;
     int		prevchar_isflag;
@@ -4293,8 +4346,6 @@
     int		width;
     int		itemcnt;
     int		curitem;
-    int		group_end_userhl;
-    int		group_start_userhl;
     int		groupdepth;
 #ifdef FEAT_EVAL
     int		evaldepth;
@@ -4306,7 +4357,6 @@
     char_u	opt;
 #define TMPLEN 70
     char_u	buf_tmp[TMPLEN];
-    char_u	win_tmp[TMPLEN];
     char_u	*usefmt = fmt;
     stl_hlrec_T *sp;
     int		save_redraw_not_allowed = redraw_not_allowed;
@@ -4429,7 +4479,7 @@
 					    sizeof(int) * new_len);
 	    if (new_separator_locs == NULL)
 		break;
-	    stl_separator_locations = new_separator_locs;;
+	    stl_separator_locations = new_separator_locs;
 
 	    stl_items_len = new_len;
 	}
@@ -4478,6 +4528,8 @@
 	}
 	if (*s == ')')
 	{
+	    char_u  *t;
+
 	    s++;
 	    if (groupdepth < 1)
 		continue;
@@ -4489,9 +4541,11 @@
 	    if (curitem > stl_groupitem[groupdepth] + 1
 		    && stl_items[stl_groupitem[groupdepth]].stl_minwid == 0)
 	    {
+		int group_start_userhl = 0;
+		int group_end_userhl = 0;
+
 		// remove group if all items are empty and highlight group
 		// doesn't change
-		group_start_userhl = group_end_userhl = 0;
 		for (n = stl_groupitem[groupdepth] - 1; n >= 0; n--)
 		{
 		    if (stl_items[n].stl_type == Highlight)
@@ -4693,12 +4747,16 @@
 	case STL_FILEPATH:
 	case STL_FULLPATH:
 	case STL_FILENAME:
+	{
+	    char_u  *name;
+
 	    fillable = FALSE;	// don't change ' ' to fillchar
-	    if (buf_spname(wp->w_buffer) != NULL)
-		vim_strncpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL - 1);
+	    name = buf_spname(wp->w_buffer);
+	    if (name != NULL)
+		vim_strncpy(NameBuff, name, MAXPATHL - 1);
 	    else
 	    {
-		t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
+		char_u	*t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
 					  : wp->w_buffer->b_fname;
 		home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE);
 	    }
@@ -4708,13 +4766,17 @@
 	    else
 		str = gettail(NameBuff);
 	    break;
+	}
 
 	case STL_VIM_EXPR: // '{'
 	{
 #ifdef FEAT_EVAL
-	    char_u *block_start = s - 1;
+	    char_u  *block_start = s - 1;
 #endif
-	    int reevaluate = (*s == '%');
+	    int	    reevaluate = (*s == '%');
+	    char_u  *t;
+	    buf_T   *save_curbuf;
+	    win_T   *save_curwin;
 
 	    if (reevaluate)
 		s++;
@@ -4727,16 +4789,16 @@
 		break;
 	    s++;
 	    if (reevaluate)
-		p[-1] = 0; // remove the % at the end of %{% expr %}
+		p[-1] = NUL; // remove the % at the end of %{% expr %}
 	    else
-		*p = 0;
+		*p = NUL;
 	    p = t;
 #ifdef FEAT_EVAL
 	    vim_snprintf((char *)buf_tmp, sizeof(buf_tmp),
 							 "%d", curbuf->b_fnum);
 	    set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp);
-	    vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->w_id);
-	    set_internal_string_var((char_u *)"g:actual_curwin", win_tmp);
+	    vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "%d", curwin->w_id);
+	    set_internal_string_var((char_u *)"g:actual_curwin", buf_tmp);
 
 	    save_curbuf = curbuf;
 	    save_curwin = curwin;
@@ -4755,7 +4817,7 @@
 	    do_unlet((char_u *)"g:actual_curbuf", TRUE);
 	    do_unlet((char_u *)"g:actual_curwin", TRUE);
 
-	    if (str != NULL && *str != 0)
+	    if (str != NULL && *str != NUL)
 	    {
 		if (*skipdigits(str) == NUL)
 		{
@@ -4767,30 +4829,19 @@
 
 	    // If the output of the expression needs to be evaluated
 	    // replace the %{} block with the result of evaluation
-	    if (reevaluate && str != NULL && *str != 0
+	    if (reevaluate && str != NULL && *str != NUL
 		    && strchr((const char *)str, '%') != NULL
 		    && evaldepth < MAX_STL_EVAL_DEPTH)
 	    {
 		size_t parsed_usefmt = (size_t)(block_start - usefmt);
-		size_t str_length = strlen((const char *)str);
-		size_t fmt_length = strlen((const char *)s);
-		size_t new_fmt_len = parsed_usefmt
-						 + str_length + fmt_length + 3;
-		char_u *new_fmt = (char_u *)alloc(new_fmt_len * sizeof(char_u));
+		size_t new_fmt_len = (parsed_usefmt
+			+ STRLEN(str) + STRLEN(s) + 3) * sizeof(char_u);
+		char_u *new_fmt = (char_u *)alloc(new_fmt_len);
 
 		if (new_fmt != NULL)
 		{
-		    char_u *new_fmt_p = new_fmt;
-
-		    new_fmt_p = (char_u *)memcpy(new_fmt_p, usefmt, parsed_usefmt)
-								   + parsed_usefmt;
-		    new_fmt_p = (char_u *)memcpy(new_fmt_p , str, str_length)
-								      + str_length;
-		    new_fmt_p = (char_u *)memcpy(new_fmt_p, "%}", 2) + 2;
-		    new_fmt_p = (char_u *)memcpy(new_fmt_p , s, fmt_length)
-								      + fmt_length;
-		    *new_fmt_p = 0;
-		    new_fmt_p = NULL;
+		    vim_snprintf((char *)new_fmt, new_fmt_len, "%.*s%s%s%s",
+			(int)parsed_usefmt, usefmt, str, "%}", s);
 
 		    if (usefmt != fmt)
 			vim_free(usefmt);
@@ -4820,7 +4871,9 @@
 
 	case STL_VIRTCOL:
 	case STL_VIRTCOL_ALT:
-	    virtcol = wp->w_virtcol + 1;
+	{
+	    colnr_T virtcol = wp->w_virtcol + 1;
+
 	    // Don't display %V if it's the same as %c.
 	    if (opt == STL_VIRTCOL_ALT
 		    && (virtcol == (colnr_T)((State & MODE_INSERT) == 0
@@ -4828,10 +4881,10 @@
 		break;
 	    num = (long)virtcol;
 	    break;
+	}
 
 	case STL_PERCENTAGE:
-	    num = (int)(((long)wp->w_cursor.lnum * 100L) /
-			(long)wp->w_buffer->b_ml.ml_line_count);
+	    num = calc_percentage((long)wp->w_cursor.lnum, (long)wp->w_buffer->b_ml.ml_line_count);
 	    break;
 
 	case STL_ALTPERCENT:
@@ -4846,8 +4899,8 @@
 
 	case STL_ARGLISTSTAT:
 	    fillable = FALSE;
-	    buf_tmp[0] = 0;
-	    if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), FALSE))
+	    buf_tmp[0] = NUL;
+	    if (append_arg_number(wp, buf_tmp, sizeof(buf_tmp), FALSE) > 0)
 		str = buf_tmp;
 	    break;
 
@@ -4921,6 +4974,8 @@
 	    if (*wp->w_buffer->b_p_ft != NUL
 		    && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2)
 	    {
+		char_u	*t;
+
 		vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), ",%s",
 							wp->w_buffer->b_p_ft);
 		for (t = buf_tmp; *t != 0; t++)
@@ -4963,26 +5018,30 @@
 	    break;
 
 	case STL_HIGHLIGHT:
-	    t = s;
-	    while (*s != '#' && *s != NUL)
-		++s;
-	    if (*s == '#')
 	    {
-		stl_items[curitem].stl_type = Highlight;
-		stl_items[curitem].stl_start = p;
-		stl_items[curitem].stl_minwid = -syn_namen2id(t, (int)(s - t));
-		curitem++;
+		char_u  *t = s;
+
+		while (*s != '#' && *s != NUL)
+		    ++s;
+		if (*s == '#')
+		{
+		    stl_items[curitem].stl_type = Highlight;
+		    stl_items[curitem].stl_start = p;
+		    stl_items[curitem].stl_minwid = -syn_namen2id(t, (int)(s - t));
+		    curitem++;
+		}
+		if (*s != NUL)
+		    ++s;
+		continue;
 	    }
-	    if (*s != NUL)
-		++s;
-	    continue;
 	}
 
 	stl_items[curitem].stl_start = p;
 	stl_items[curitem].stl_type = Normal;
 	if (str != NULL && *str)
 	{
-	    t = str;
+	    char_u  *t = str;
+
 	    if (itemisflag)
 	    {
 		if ((t[0] && t[1])
@@ -5037,13 +5096,13 @@
 	}
 	else if (num >= 0)
 	{
-	    int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16));
-	    char_u nstr[20];
+	    int	    nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16));
+	    char_u  nstr[20];
+	    char_u  *t = nstr;
 
 	    if (p + 20 >= out + outlen)
 		break;		// not sufficient space
 	    prevchar_isitem = TRUE;
-	    t = nstr;
 	    if (opt == STL_VIRTCOL_ALT)
 	    {
 		*t++ = '-';
@@ -5054,12 +5113,13 @@
 		*t++ = '0';
 	    *t++ = '*';
 	    *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd');
-	    *t = 0;
+	    *t = NUL;
 
 	    for (n = num, l = 1; n >= nbase; n /= nbase)
 		l++;
 	    if (opt == STL_VIRTCOL_ALT)
 		l++;
+
 	    if (l > maxwid)
 	    {
 		l += 2;
@@ -5069,14 +5129,13 @@
 		*t++ = '>';
 		*t++ = '%';
 		*t = t[-3];
-		*++t = 0;
-		vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
-								   0, num, n);
+		*++t = NUL;
+		p += vim_snprintf_safelen((char *)p, outlen - (p - out),
+		    (char *)nstr, 0, num, n);
 	    }
 	    else
-		vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
-								 minwid, num);
-	    p += STRLEN(p);
+		p += vim_snprintf_safelen((char *)p, outlen - (p - out),
+		    (char *)nstr, minwid, num);
 	}
 	else
 	    stl_items[curitem].stl_type = Empty;
@@ -5089,6 +5148,7 @@
 	curitem++;
     }
     *p = NUL;
+    outputlen = (size_t)(p - out);
     itemcnt = curitem;
 
 #ifdef FEAT_EVAL
@@ -5145,10 +5205,12 @@
 		    break;
 	    itemcnt = l;
 	    *s++ = '>';
-	    *s = 0;
+	    *s = NUL;
 	}
 	else
 	{
+	    char_u  *end = out + outputlen;
+
 	    if (has_mbyte)
 	    {
 		n = 0;
@@ -5161,7 +5223,8 @@
 	    else
 		n = width - maxwidth + 1;
 	    p = s + n;
-	    STRMOVE(s + 1, p);
+	    mch_memmove(s + 1, p, (size_t)(end - p) + 1);	// +1 for NUL
+	    end -= (size_t)(p - (s + 1));
 	    *s = '<';
 
 	    --n;	// count the '<'
@@ -5176,14 +5239,15 @@
 	    // Fill up for half a double-wide character.
 	    while (++width < maxwidth)
 	    {
-		s = s + STRLEN(s);
+		s = end;
 		MB_CHAR2BYTES(fillchar, s);
 		*s = NUL;
+		end = s;
 	    }
 	}
 	width = maxwidth;
     }
-    else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 < outlen)
+    else if (width < maxwidth && outputlen + maxwidth - width + 1 < outlen)
     {
 	// Find how many separators there are, which we will use when
 	// figuring out how many groups there are.
@@ -5284,7 +5348,7 @@
 #endif // FEAT_STL_OPT
 
 /*
- * Get relative cursor position in window into "buf[buflen]", in the localized
+ * Get relative cursor position in window into "buf[]", in the localized
  * percentage form like %99, 99%; using "Top", "Bot" or "All" when appropriate.
  */
     int
@@ -5295,7 +5359,6 @@
 {
     long	above; // number of lines above window
     long	below; // number of lines below window
-    int		len;
 
     if (buflen < 3) // need at least 3 chars for writing
 	return 0;
@@ -5309,42 +5372,31 @@
 #endif
     below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1;
     if (below <= 0)
-	len = vim_snprintf((char *)buf, buflen, "%s", (above == 0) ? _("All") : _("Bot"));
-    else if (above <= 0)
-	len = vim_snprintf((char *)buf, buflen, "%s", _("Top"));
-    else
-    {
-	int perc = (above > 1000000L)
-		    ?  (int)(above / ((above + below) / 100L))
-		    :  (int)(above * 100L / (above + below));
+	return (int)vim_snprintf_safelen((char *)buf, buflen,
+	    "%s", (above == 0) ? _("All") : _("Bot"));
 
-	// localized percentage value
-	len = vim_snprintf((char *)buf, buflen, _("%s%d%%"), (perc < 10) ? " " : "", perc);
-    }
-    if (len < 0)
-    {
-	buf[0] = NUL;
-	len = 0;
-    }
-    else if (len > buflen - 1)
-	len = buflen - 1;
+    if (above <= 0)
+	return (int)vim_snprintf_safelen((char *)buf, buflen,
+	    "%s", _("Top"));
 
-    return len;
+    // localized percentage value
+    return (int)vim_snprintf_safelen((char *)buf, buflen,
+	_("%2d%%"), calc_percentage(above, above + below));
 }
 
 /*
- * Append (file 2 of 8) to "buf[buflen]", if editing more than one file.
- * Return TRUE if it was appended.
+ * Append (file 2 of 8) to "buf[]", if editing more than one file.
+ * Return the number of characters appended.
  */
     static int
 append_arg_number(
     win_T	*wp,
     char_u	*buf,
-    int		buflen,
+    size_t	buflen,
     int		add_file)	// Add "file" before the arg number
 {
     if (ARGCOUNT <= 1)		// nothing to do
-	return FALSE;
+	return 0;
 
     char *msg;
     switch ((wp->w_arg_idx_invalid ? 1 : 0) + (add_file ? 2 : 0))
@@ -5355,10 +5407,8 @@
 	case 3: msg = _(" (file (%d) of %d)"); break;
     }
 
-    char_u *p = buf + STRLEN(buf);	// go to the end of the buffer
-    vim_snprintf((char *)p, (size_t)(buflen - (p - buf)), msg,
+    return (int)vim_snprintf_safelen((char *)buf, buflen, msg,
 						  wp->w_arg_idx + 1, ARGCOUNT);
-    return TRUE;
 }
 
 /*
@@ -5698,17 +5748,16 @@
     int		flags)		// Same as for do_modelines().
 {
     char_u	*s;
+    char_u	*line_end;		// point to the end of the line
     char_u	*e;
-    char_u	*linecopy;		// local copy of any modeline found
     int		prev;
-    int		vers;
-    int		end;
     int		retval = OK;
-    sctx_T	save_current_sctx;
     ESTACK_CHECK_DECLARATION;
 
     prev = -1;
-    for (s = ml_get(lnum); *s != NUL; ++s)
+    s = ml_get(lnum);
+    line_end = s + ml_get_len(lnum);
+    for (; *s != NUL; ++s)
     {
 	if (prev == -1 || vim_isspace(prev))
 	{
@@ -5718,6 +5767,8 @@
 	    // Accept both "vim" and "Vim".
 	    if ((s[0] == 'v' || s[0] == 'V') && s[1] == 'i' && s[2] == 'm')
 	    {
+		int vers;
+
 		if (s[3] == '<' || s[3] == '=' || s[3] == '>')
 		    e = s + 4;
 		else
@@ -5739,14 +5790,23 @@
 
     if (*s)
     {
+	size_t	len;
+	char_u	*linecopy;		// local copy of any modeline found
+	int	end;
+
 	do				// skip over "ex:", "vi:" or "vim:"
 	    ++s;
 	while (s[-1] != ':');
 
-	s = linecopy = vim_strsave(s);	// copy the line, it will change
+	len = (size_t)(line_end - s);		// remember the line length
+						// so we can restore 'line_end'
+						// after the copy
+	s = linecopy = vim_strnsave(s, len);	// copy the line, it will change
 	if (linecopy == NULL)
 	    return FAIL;
 
+	line_end = s + len;			// restore 'line_end'
+
 	// prepare for emsg()
 	estack_push(ETYPE_MODELINE, (char_u *)"modelines", lnum);
 	ESTACK_CHECK_SETUP;
@@ -5764,7 +5824,10 @@
 	     */
 	    for (e = s; *e != ':' && *e != NUL; ++e)
 		if (e[0] == '\\' && e[1] == ':')
-		    STRMOVE(e, e + 1);
+		{
+		    mch_memmove(e, e + 1, (size_t)(line_end - (e + 1)) + 1);	// +1 for NUL
+		    --line_end;
+		}
 	    if (*e == NUL)
 		end = TRUE;
 
@@ -5781,15 +5844,15 @@
 		if (*e != ':')		// no terminating ':'?
 		    break;
 		end = TRUE;
-		s = vim_strchr(s, ' ') + 1;
+		s += (*(s + 2) == ' ') ? 3 : 4;
 	    }
 	    *e = NUL;			// truncate the set command
 
 	    if (*s != NUL)		// skip over an empty "::"
 	    {
 		int secure_save = secure;
+		sctx_T	save_current_sctx = current_sctx;
 
-		save_current_sctx = current_sctx;
 		current_sctx.sc_version = 1;
 #ifdef FEAT_EVAL
 		current_sctx.sc_sid = SID_MODELINE;
@@ -5807,7 +5870,8 @@
 		if (retval == FAIL)		// stop if error found
 		    break;
 	    }
-	    s = e + 1;			// advance to next part
+	    s = (e == line_end) ? e : e + 1;	// advance to next part
+						// careful not to go off the end
 	}
 
 	ESTACK_CHECK_NOW;
diff --git a/src/proto.h b/src/proto.h
index f04ba05..a5ca9e4 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -140,6 +140,7 @@
 int vim_snprintf_add(char *, size_t, const char *, ...) ATTRIBUTE_FORMAT_PRINTF(3, 4);
 
 int vim_snprintf(char *, size_t, const char *, ...) ATTRIBUTE_FORMAT_PRINTF(3, 4);
+size_t vim_snprintf_safelen(char *, size_t, const char *, ...) ATTRIBUTE_FORMAT_PRINTF(3, 4);
 
 int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap)
 	ATTRIBUTE_FORMAT_PRINTF(3, 0);
diff --git a/src/strings.c b/src/strings.c
index d6f1b38..e1d23e3 100644
--- a/src/strings.c
+++ b/src/strings.c
@@ -2487,6 +2487,33 @@
     return str_l;
 }
 
+/*
+ * Like vim_snprintf() except the return value can be safely used to increment a
+ * buffer length.
+ * Normal `snprintf()` (and `vim_snprintf()`) returns the number of bytes that
+ * would have been copied if the destination buffer was large enough.
+ * This means that you cannot rely on it's return value for the destination
+ * length because the destination may be shorter than the source. This function
+ * guarantees the returned length will never be greater than the destination length.
+ */
+    size_t
+vim_snprintf_safelen(char *str, size_t str_m, const char *fmt, ...)
+{
+    va_list ap;
+    int	    str_l;
+
+    va_start(ap, fmt);
+    str_l = vim_vsnprintf(str, str_m, fmt, ap);
+    va_end(ap);
+
+    if (str_l < 0)
+    {
+	*str = NUL;
+	return 0;
+    }
+    return ((size_t)str_l >= str_m) ? str_m - 1 : (size_t)str_l;
+}
+
     int
 vim_vsnprintf(
     char	*str,
diff --git a/src/version.c b/src/version.c
index 4609244..e500aa1 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1291,
+/**/
     1290,
 /**/
     1289,