...
In the noncompliant example below, the function utf8_to_ucs()
attempts to convert a sequence of UTF-8 characters to UCS. It first invokes setlocale()
to set the global locale to "en_US.UTF-8," but it fails to check for failure. setlocale()
will fail by returning a null pointer, for example when the locale is not installed. (The function may fail for other reasons, as well, such as the lack of resources.) Depending on the sequence of characters pointed to by utf8
, the subsequent call to mbstowcs()
may fail or result in the function storing an unexpected sequence of wide characters in the supplied buffer ucs
.
Code Block | ||||
---|---|---|---|---|
| ||||
size_t utf8_to_ucs(wchar_t *ucs, size_t n, const char *utf8) { setlocale(LC_CTYPE, "en_US.UTF-8"); return mbstowcs(ucs, utf8, n); } |
...
A compliant solution checks the value returned by setlocale()
and avoids calling mbstowcs()
if the function fails. The function also takes care to restore the locale to its initial setting before returning control to the caller.
Code Block | ||||
---|---|---|---|---|
| ||||
size_t utf8_to_ucs(wchar_t *ucs, size_t n, const char *utf8) { const char *save; save = setlocale(LC_CTYPE, "en_US.UTF-8"); if (NULL == save) { /* Propagate error to caller */ return (size_t)-1; } n = mbstowcs(ucs, utf8, n); if (NULL == setlocale(LC_CTYPE, save)) n = -1; return n; } |
...
In the noncompliant example below the function signal()
is invoked to install a handler for the SIGINT
signal. signal()
returns a pointer to the previously installed handler on success and the value SIG_ERR
on failure. However, since the caller fails to check for errors, when signal()
fails, the function may proceed with the lengthy computation without the ability to interrupt it.
Code Block | ||||
---|---|---|---|---|
| ||||
volatile sig_atomic_t interrupted; void handle_interrupt(int signo) { interrupted = 1; } int f() { int result = 0; signal(SIGINT, handle_interrupt); while (0 == result && 0 == interrupted) { /* Perform a lengthy computation */ } /* Indicate success or failure */ return interrupted ? -1 : result; } |
...
A compliant solution checks the value returned by the signal()
function and avoids performing the lengthy computation when signal()
fails. The calling function also takes care to restore the disposition for the SIGINT
signal to its initial setting before returning control to the caller.
Code Block | ||||
---|---|---|---|---|
| ||||
volatile sig_atomic_t interrupted; void handle_interrupt(int signo) { interrupted = 1; } int f() { int result = 0; void (*saved_handler)(int); saved_handler = signal(SIGINT, handle_interrupt); if (SIG_ERR == saved_handler) { /* Indicate failure */ return -1; } while (0 == result && 0 == interrupted) { /* Perform a lengthy computation */ } if (SIG_ERR == signal(SIGINT, saved_handler)) return -1; /* Indicate success or failure */ return interrupted ? -1 : result; } |
...
See also rule MEM32-C. Detect and handle memory allocation errors.
Code Block | ||||
---|---|---|---|---|
| ||||
void f(char *input_string) { size_t size = strlen(input_string) + 1; char *str = (char *)malloc(size); strcpy(str, input_string); /* ... */ } |
...
The malloc()
function, as well as the other memory allocation functions, returns either a null pointer or a pointer to the allocated space. Always test the returned pointer to ensure it is not NULL
before referencing the pointer. Handle the error condition appropriately when the returned pointer is NULL
. When recovery from the allocation failure isn't possible, propagate the failure to the caller.
Code Block | ||||
---|---|---|---|---|
| ||||
int f(char *input_string) { size_t size = strlen(input_string) + 1; char *str = (char *)malloc(size); if (str == NULL) { /* Handle allocation failure and return error status */ return -1; } strcpy(str, input_string); /* ... */ free(str); return 0; } |
...