mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 03:48:18 +00:00
xstrlcat: Allow overlapped pointers. (#6017)
memcpy is not equivalent to memmove (which is used by vim_strcat), this could cause subtle bugs if xstrlcat is used as a replacement for vim_strcat. But vim_strcat is inconsistent: in the `else` branch it uses strcpy, which doesn't allow overlap. Helped-by: oni-link <knil.ino@gmail.com> Helped-by: James McCoy <jamessan@jamessan.com> Helped-by: Nikolai Aleksandrovich Pavlov <kp-pav@yandex.ru>
This commit is contained in:
@@ -3176,7 +3176,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension,
|
|||||||
if match:
|
if match:
|
||||||
error(filename, linenum, 'runtime/printf', 4,
|
error(filename, linenum, 'runtime/printf', 4,
|
||||||
'Use xstrlcpy or snprintf instead of %s' % match.group(1))
|
'Use xstrlcpy or snprintf instead of %s' % match.group(1))
|
||||||
match = Search(r'\b(STRNCAT|strncat|strcat)\b', line)
|
match = Search(r'\b(STRNCAT|strncat|strcat|vim_strcat)\b', line)
|
||||||
if match:
|
if match:
|
||||||
error(filename, linenum, 'runtime/printf', 4,
|
error(filename, linenum, 'runtime/printf', 4,
|
||||||
'Use xstrlcat or snprintf instead of %s' % match.group(1))
|
'Use xstrlcat or snprintf instead of %s' % match.group(1))
|
||||||
|
@@ -372,15 +372,15 @@ char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen)
|
|||||||
///
|
///
|
||||||
/// @param dst Buffer to store the result
|
/// @param dst Buffer to store the result
|
||||||
/// @param src String to be copied
|
/// @param src String to be copied
|
||||||
/// @param size Size of `dst`
|
/// @param dsize Size of `dst`
|
||||||
/// @return strlen(src). If retval >= dstsize, truncation occurs.
|
/// @return strlen(src). If retval >= dstsize, truncation occurs.
|
||||||
size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size)
|
size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t dsize)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
size_t slen = strlen(src);
|
size_t slen = strlen(src);
|
||||||
|
|
||||||
if (size) {
|
if (dsize) {
|
||||||
size_t len = MIN(slen, size - 1);
|
size_t len = MIN(slen, dsize - 1);
|
||||||
memcpy(dst, src, len);
|
memcpy(dst, src, len);
|
||||||
dst[len] = '\0';
|
dst[len] = '\0';
|
||||||
}
|
}
|
||||||
@@ -388,31 +388,34 @@ size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size)
|
|||||||
return slen; // Does not include NUL.
|
return slen; // Does not include NUL.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// xstrlcat - Appends string src to the end of dst.
|
/// 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.
|
||||||
///
|
///
|
||||||
/// Compatible with *BSD strlcat: Appends at most (dstsize - strlen(dst) - 1)
|
/// @see vim_strcat from Vim.
|
||||||
/// characters. dst will be NUL-terminated.
|
/// @see strlcat from OpenBSD.
|
||||||
///
|
///
|
||||||
/// Note: Replaces `vim_strcat`.
|
/// @param dst Buffer to be appended-to. Must have a NUL byte.
|
||||||
///
|
/// @param src String to put at the end of `dst`
|
||||||
/// @param dst Buffer to store the string
|
/// @param dsize Size of `dst` including NUL byte. Must be greater than 0.
|
||||||
/// @param src String to be copied
|
/// @return strlen(src) + strlen(initial dst)
|
||||||
/// @param dstsize Size of destination buffer, must be greater than 0
|
/// If retval >= dsize, truncation occurs.
|
||||||
/// @return strlen(src) + MIN(dstsize, strlen(initial dst)).
|
size_t xstrlcat(char *const dst, const char *const src, const size_t dsize)
|
||||||
/// If retval >= dstsize, truncation occurs.
|
|
||||||
size_t xstrlcat(char *restrict dst, const char *restrict src, size_t dstsize)
|
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
assert(dstsize > 0);
|
assert(dsize > 0);
|
||||||
size_t srclen = strlen(src);
|
const size_t dlen = strlen(dst);
|
||||||
size_t dstlen = strlen(dst);
|
assert(dlen < dsize);
|
||||||
size_t ret = srclen + dstlen; // Total string length (excludes NUL)
|
const size_t slen = strlen(src);
|
||||||
if (srclen) {
|
|
||||||
size_t len = (ret >= dstsize) ? dstsize - 1 : ret;
|
if (slen > dsize - dlen - 1) {
|
||||||
memcpy(dst + dstlen, src, len - dstlen);
|
memmove(dst + dlen, src, dsize - dlen - 1);
|
||||||
dst[len] = '\0';
|
dst[dsize - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
memmove(dst + dlen, src, slen + 1);
|
||||||
}
|
}
|
||||||
return ret; // Does not include NUL.
|
|
||||||
|
return slen + dlen; // Does not include NUL.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// strdup() wrapper
|
/// strdup() wrapper
|
||||||
|
@@ -129,9 +129,10 @@ newwindow:
|
|||||||
vim_snprintf(cbuf, sizeof(cbuf) - 5, "%" PRId64, (int64_t)Prenum);
|
vim_snprintf(cbuf, sizeof(cbuf) - 5, "%" PRId64, (int64_t)Prenum);
|
||||||
else
|
else
|
||||||
cbuf[0] = NUL;
|
cbuf[0] = NUL;
|
||||||
if (nchar == 'v' || nchar == Ctrl_V)
|
if (nchar == 'v' || nchar == Ctrl_V) {
|
||||||
strcat(cbuf, "v");
|
xstrlcat(cbuf, "v", sizeof(cbuf));
|
||||||
strcat(cbuf, "new");
|
}
|
||||||
|
xstrlcat(cbuf, "new", sizeof(cbuf));
|
||||||
do_cmdline_cmd(cbuf);
|
do_cmdline_cmd(cbuf);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
51
test/unit/memory_spec.lua
Normal file
51
test/unit/memory_spec.lua
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
local helpers = require("test.unit.helpers")
|
||||||
|
|
||||||
|
local cimport = helpers.cimport
|
||||||
|
local cstr = helpers.cstr
|
||||||
|
local eq = helpers.eq
|
||||||
|
local ffi = helpers.ffi
|
||||||
|
local to_cstr = helpers.to_cstr
|
||||||
|
|
||||||
|
local cimp = cimport('stdlib.h', './src/nvim/memory.h')
|
||||||
|
|
||||||
|
describe('xstrlcat()', function()
|
||||||
|
local function test_xstrlcat(dst, src, dsize)
|
||||||
|
assert.is_true(dsize >= 1 + string.len(dst)) -- sanity check for tests
|
||||||
|
local dst_cstr = cstr(dsize, dst)
|
||||||
|
local src_cstr = to_cstr(src)
|
||||||
|
eq(string.len(dst .. src), cimp.xstrlcat(dst_cstr, src_cstr, dsize))
|
||||||
|
return ffi.string(dst_cstr)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_xstrlcat_overlap(dst, src_idx, dsize)
|
||||||
|
assert.is_true(dsize >= 1 + string.len(dst)) -- sanity check for tests
|
||||||
|
local dst_cstr = cstr(dsize, dst)
|
||||||
|
local src_cstr = dst_cstr + src_idx -- pointer into `dst` (overlaps)
|
||||||
|
eq(string.len(dst) + string.len(dst) - src_idx,
|
||||||
|
cimp.xstrlcat(dst_cstr, src_cstr, dsize))
|
||||||
|
return ffi.string(dst_cstr)
|
||||||
|
end
|
||||||
|
|
||||||
|
it('concatenates strings', function()
|
||||||
|
eq('ab', test_xstrlcat('a', 'b', 3))
|
||||||
|
eq('ab', test_xstrlcat('a', 'b', 4096))
|
||||||
|
eq('ABCיהZdefgiיהZ', test_xstrlcat('ABCיהZ', 'defgiיהZ', 4096))
|
||||||
|
eq('b', test_xstrlcat('', 'b', 4096))
|
||||||
|
eq('a', test_xstrlcat('a', '', 4096))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('concatenates overlapping strings', function()
|
||||||
|
eq('abcabc', test_xstrlcat_overlap('abc', 0, 7))
|
||||||
|
eq('abca', test_xstrlcat_overlap('abc', 0, 5))
|
||||||
|
eq('abcb', test_xstrlcat_overlap('abc', 1, 5))
|
||||||
|
eq('abcc', test_xstrlcat_overlap('abc', 2, 10))
|
||||||
|
eq('abcabc', test_xstrlcat_overlap('abc', 0, 2343))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('truncates if `dsize` is too small', function()
|
||||||
|
eq('a', test_xstrlcat('a', 'b', 2))
|
||||||
|
eq('', test_xstrlcat('', 'b', 1))
|
||||||
|
eq('ABCיהZd', test_xstrlcat('ABCיהZ', 'defgiיהZ', 10))
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
Reference in New Issue
Block a user