I am trying to create C-based HTML client. I`m quite new to C memory leaks debugging, so please see such (simplified) code of HTML response parser:
//=============
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int str_index_of(const char *a, char *b) {
char *offset = (char *) strstr(a, b);
return offset - a;
}
char *get_until(char *haystack, char *until) {
int offset = str_index_of(haystack, until);
return strndup(haystack, offset);
}
char *str_replace(const char *search, const char *replace, const char *subject) {
size_t search_size = strlen(search);
size_t replace_size = strlen(replace);
size_t subject_size = strlen(subject);
// Count the number of occurrences of search in subject
size_t count = 0;
const char *p = subject;
while ((p = strstr(p, search)) != NULL) {
count++;
p += search_size;
}
// Allocate memory for the new string
size_t new_size = subject_size + count * (replace_size - search_size);
char *new_subject = (char *) calloc(new_size + 1, sizeof(char));
// Replace search with replace in subject
const char *old = subject;
char *new_p = new_subject;
while ((p = strstr(old, search)) != NULL) {
size_t old_size = p - old;
memcpy(new_p, old, old_size);
new_p += old_size;
memcpy(new_p, replace, replace_size);
new_p += replace_size;
old = p + search_size;
}
strcpy(new_p, old);
return new_subject;
}
void getStatusCode(char *response, char *status_code) {
char *status_line = get_until(response, "\r\n");
status_line = str_replace("HTTP/1.1 ", "", status_line);
char *code = strndup(status_line, 4);
code = str_replace(" ", "", code);
status_code = strcpy(status_code, code);
free(code);
free(status_line);
}
void mockRequest(char *status_code) {
char *res = "HTTP/1.1 200 OK\n"
"Server: nginx\n"
"Date: Fri, 07 Apr 2023 18:44:22 GMT\n"
"Content-Type: application/json; charset=utf-8\n"
"Connection: close\n"
"Access-Control-Allow-Origin: *\n"
"\n"
"73\n"
"some-body-content-here\n"
"0";
char *status_line = NULL;
getStatusCode(res, status_code);
free(status_line);
}
int main() {
char status_code[4];
mockRequest(status_code);
printf("STATUS: %s", status_code);
return 0;
}
//=============
I use address sanitizer. The sanitizer reports:
==8055==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 190 byte(s) in 1 object(s) allocated from:
#0 0x7f31ccabc808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
#1 0x7f31cca25243 in __interceptor_strndup ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:379
#2 0x55d0abad935c in get_until /home/user/CLionProjects/untitled/main.c:12
#3 0x55d0abad94e0 in getStatusCode /home/user/CLionProjects/untitled/main.c:48
#4 0x55d0abad959d in mockRequest /home/user/CLionProjects/untitled/main.c:70
#5 0x55d0abad963f in main /home/user/CLionProjects/untitled/main.c:78
#6 0x7f31cc7e1082 in __libc_start_main ../csu/libc-start.c:308
Direct leak of 5 byte(s) in 1 object(s) allocated from:
#0 0x7f31ccabc808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
#1 0x7f31cca25243 in __interceptor_strndup ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:379
#2 0x55d0abad9513 in getStatusCode /home/user/CLionProjects/untitled/main.c:50
#3 0x55d0abad959d in mockRequest /home/user/CLionProjects/untitled/main.c:70
#4 0x55d0abad963f in main /home/user/CLionProjects/untitled/main.c:78
#5 0x7f31cc7e1082 in __libc_start_main ../csu/libc-start.c:308
SUMMARY: AddressSanitizer: 195 byte(s) leaked in 2 allocation(s).
The leaks are when calling strndup, if I understand well. I completely have no ideas about where the leaks may be located. Your advice?
void getStatusCode(char *response, char *status_code) { char *status_line = get_until(response, "\r\n"); status_line = str_replace("HTTP/1.1 ", "", status_line); char *code = strndup(status_line, 4); code = str_replace(" ", "", code); status_code = strcpy(status_code, code); free(code); free(status_line); }
Notice that the variable code
stores the result of strndup
but in the very next line code
is reassigned to the result of str_replace
. The memory allocated by strndup
is never freed, and cannot be, because there is no longer a pointer to it.
The same issue exists with status_line
.
To avoid this in the future, ensure that every memory allocation maintains a pointer to it that can be freed.
char *a = memory_allocating_function();
char *b = other_memory_allocating_function(a);
// ...
free(a);
free(b);