When receiving strings *from* msgpack, we don't need to duplicate/free since
the data only lives in the msgpack parse buffer until the end of the call.
But in order to reuse `msgpack_rpc_free_object` when sending event data(which is
sent *to* msgpack), Strings must be freed, which means they must also be
allocated separately.
Relates to issue #760
These coverity warnings are of the form:
>>> CID 62602: Buffer not null terminated (BUFFER_SIZE_WARNING)
>>> Calling strncpy with a maximum size argument of 256 bytes...
This is caused by strncpy not alway NULL-terminated the destination buffer
(for example in the case where strlen(src) >= size(dst)). It's better to
replace that with (x)strlcpy, which always NULL-terminates.
Most of these are related to the set_api_error macro, which uses strncpy.
The error struct is used (for example) in msgpack_rpc_error, where strlen is
executed on it, so it needs to be NULL-terminated. (x)strlcpy, unlike
strncpy, always NULL-terminates the destination buffer.
Relevant parts of the coverity report:
*** CID 62602: Buffer not null terminated (BUFFER_SIZE_WARNING)
/src/nvim/api/vim.c: 236 in vim_set_current_buffer()
230 if (try_end(err)) {
231 return;
232 }
233
234 char msg[256];
235 snprintf(msg, sizeof(msg),
"failed to switch to buffer %d", (int)buffer);
>>> CID 62602: Buffer not null terminated (BUFFER_SIZE_WARNING)
>>> Calling strncpy with a maximum size argument of 256 bytes on
>>> destination array "err->msg" of size 256 bytes might leave the
>>> destination string unterminated.
236 set_api_error(msg, err);
237 return;
238 }
239
240 try_end(err);
241 }
*** CID 62603: Buffer not null terminated (BUFFER_SIZE_WARNING)
/src/nvim/api/private/helpers.c: 70 in try_end()
64 } else if (msg_list != NULL && *msg_list != NULL) {
65 int should_free;
66 char *msg = (char *)get_exception_string(*msg_list,
67 ET_ERROR,
68 NULL,
69 &should_free);
>>> CID 62603: Buffer not null terminated (BUFFER_SIZE_WARNING)
>>> Calling strncpy with a maximum size argument of 256 bytes on
>>> destination array "err->msg" of size 256 bytes might leave the
>>> destination string unterminated.
70 strncpy(err->msg, msg, sizeof(err->msg));
71 err->set = true;
72 free_global_msglist();
73
74 if (should_free) {
75 free(msg);
/src/nvim/api/private/helpers.c: 78 in try_end()
72 free_global_msglist();
73
74 if (should_free) {
75 free(msg);
76 }
77 } else if (did_throw) {
>>> CID 62603: Buffer not null terminated (BUFFER_SIZE_WARNING)
>>> Calling strncpy with a maximum size argument of 256 bytes on
>>> destination array "err->msg" of size 256 bytes might leave the
>>> destination string unterminated.
78 set_api_error((char *)current_exception->value, err);
79 }
80
81 return err->set;
82 }
83
*** CID 62604: Buffer not null terminated (BUFFER_SIZE_WARNING)
/src/nvim/api/private/helpers.c: 592 in set_option_value_err()
586 opt_flags)))
587 {
588 if (try_end(err)) {
589 return;
590 }
591
>>> CID 62604: Buffer not null terminated (BUFFER_SIZE_WARNING)
>>> Calling strncpy with a maximum size argument of 256 bytes on
>>> destination array "err->msg" of size 256 bytes might leave the
>>> destination string unterminated.
592 set_api_error(errmsg, err);
593 }
*** CID 62605: Buffer not null terminated (BUFFER_SIZE_WARNING)
/src/nvim/os/server.c: 114 in server_start()
108 if (addr_len > sizeof(ip) - 1) {
109 // Maximum length of a ip address buffer is 15(eg: 255.255.255.255)
110 addr_len = sizeof(ip);
111 }
112
113 // Extract the address part
>>> CID 62605: Buffer not null terminated (BUFFER_SIZE_WARNING)
>>> Calling strncpy with a maximum size argument of 16 bytes on
>>> destination array "ip" of size 16 bytes might leave the destination
>>> string unterminated.
114 strncpy(ip, addr, addr_len);
115
116 int port = NEOVIM_DEFAULT_TCP_PORT;
117
118 if (*ip_end == ':') {
119 char *port_end;
/src/nvim/os/server.c: 88 in server_start()
82
83 void server_start(char *endpoint, ChannelProtocol prot)
84 {
85 char addr[ADDRESS_MAX_SIZE];
86
87 // Trim to `ADDRESS_MAX_SIZE`
>>> CID 62605: Buffer not null terminated (BUFFER_SIZE_WARNING)
>>> Calling strncpy with a maximum size argument of 256 bytes on
>>> destination array "addr" of size 256 bytes might leave the
>>> destination string unterminated.
88 strncpy(addr, endpoint, sizeof(addr));
89
90 // Check if the server already exists
91 if (map_has(cstr_t)(servers, addr)) {
92 EMSG2("Already listening on %s", addr);
93 return;
*** CID 62606: Buffer not null terminated (BUFFER_SIZE_WARNING)
/src/nvim/os/server.c: 186 in server_stop()
180 void server_stop(char *endpoint)
181 {
182 Server *server;
183 char addr[ADDRESS_MAX_SIZE];
184
185 // Trim to `ADDRESS_MAX_SIZE`
>>> CID 62606: Buffer not null terminated (BUFFER_SIZE_WARNING)
>>> Calling strncpy with a maximum size argument of 256 bytes on
>>> destination array "addr" of size 256 bytes might leave the
>>> destination string unterminated.
187
188 if ((server = map_get(cstr_t)(servers, addr)) == NULL) {
189 EMSG2("Not listening on %s", addr);
190 return;
191 }
Less "blow a hole in your foot" than strncpy. As also indicated by coverity.
Implementation inspired by the linux kernel (very similar to OSX's Libc
implementation as well).
Current type of some other parameters/variables can be improved:
- hashtab_T : ht_error : int -> bool.
- hash_clear_all() : off : int -> unsigned int.
- hash_clear_all() : todo : long -> size_t.
- hash_may_resize() : todo : int -> size_t.
hashtab.h:
- hash_T: long_u -> size_t.
In principle, a hash value could thought of as just an unsigned number
without size semantics (uint32_t or uint64_t). But it is used as
index at some places, and so, size_t is also eligible. Therea re some
places where assignments occur between hash_T and size_t variables, in
both directions. Therefore, if we define hash_T to be of a type having
a different width than that of size_t, we will have an incorrect
assignment somewhere that will require an assert/guard. So the most
sensible option here seems to do hast_T to be size_t too.
- hashtab_T.ht_mask: long_u -> hash_T.
Masks are used to be combined with hash_T values, so they should be of
the same type.
hashtab.c:
- hash_may_resize(): oldsize: long_u -> size_t.
- hash_may_resize(): newsize: long_u -> size_t.
- hash_may_resize(): newmask: long_u -> hash_T.
hashtab.h:
- Add missing includes.
- Move hash_T to the top and use it to define hashtab_T.
- Move hash_removed related definitions to the top, as they are used in
the definition of hashtab_T.
- Reformat multiline expression (start continuation with operator).
- Reformat function declaration into one single line.
hashtab.c:
- Use C99 style variable declarations (move declarations as near to
first-usage point as possible).
- Simplify oldarray/newarray computation.
- Simplify unneeded else branch.
- Remove redundant casts.
- Add a 'expect' utility script that can run simple API tests using clients
developed for any platform.
- Extend travis build matrix to run API tests using the python client and
valgrind.
This script can be used to write API tests without having to manage nvim's
lifetime:
- It starts a single nvim instance listening on a known socket
- Invokes the test runner, which should connect to NEOVIM_LISTEN_ADDRESS
- The nvim instance started by the script provides a `BeforeEachTest` function,
which should be called before each test to reset nvim to a clean state.
- It takes care of shutting down nvim once the tests are finished.
As explained
[here](https://github.com/neovim/neovim/pull/737#issuecomment-43941520), it's
not possible to fully reset nvim to it's initial state, but the `BeforeEachTest`
function should be enough for most test cases. Tests requiring a fully clean
nvim instance should take care of starting/stopping nvim.
- Define specialized arrays for each remote object type
- Implement msgpack_rpc functions for dealing with the new types
- Refactor all functions dealing with buffers, windows and tabpages to
return/accept handles instead of list indexes.
- Add the 'handle' field to `tabpage_T`
- Add declare/implement functions for registering/unregistering/retrieving
tabpages
- Register/unregister tabpages when they are created/destroyed.
- Add the 'handle' field to `win_T`
- Add declare/implement functions for registering/unregistering/retrieving
windows
- Register/unregister windows when they are created/destroyed.
- Add the 'handle' field to `buf_T`
- Add declare/implement functions for registering/unregistering/retrieving
buffers
- Register/unregister buffers when they are created/destroyed.
This module will be used to implement remote management of objects through the
API. Object types to be registered must have a `uint64_t` field named 'handle'.
While the mb_string2cells function accepts a length parameter, it only seems to
work properly with 0-terminated strings, since valgrind reports a conditional
jump that depends on uninitialized values(means it reads after the string
boundaries which could result in overflows or wrong results)