Coverage Report

Created: 2025-07-11 06:57

/src/curl/lib/memdebug.c
Line
Count
Source (jump to first uncovered line)
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
25
#include "curl_setup.h"
26
27
#ifdef CURLDEBUG
28
29
#include <curl/curl.h>
30
31
#include "urldata.h"
32
33
/* The last 3 #include files should be in this order */
34
#include "curl_printf.h"
35
#include "curl_memory.h"
36
#include "memdebug.h"
37
38
struct memdebug {
39
  size_t size;
40
  union {
41
    curl_off_t o;
42
    double d;
43
    void *p;
44
  } mem[1];
45
  /* I am hoping this is the thing with the strictest alignment
46
   * requirements. That also means we waste some space :-( */
47
};
48
49
/*
50
 * Note that these debug functions are simple and they are meant to remain so.
51
 * For advanced analysis, record a log file and write perl scripts to analyze
52
 * them!
53
 *
54
 * Do not use these with multithreaded test programs!
55
 */
56
57
FILE *curl_dbg_logfile = NULL;
58
static bool registered_cleanup = FALSE; /* atexit registered cleanup */
59
static bool memlimit = FALSE; /* enable memory limit */
60
static long memsize = 0;  /* set number of mallocs allowed */
61
62
/* LeakSantizier (LSAN) calls _exit() instead of exit() when a leak is detected
63
   on exit so the logfile must be closed explicitly or data could be lost.
64
   Though _exit() does not call atexit handlers such as this, LSAN's call to
65
   _exit() comes after the atexit handlers are called. curl/curl#6620 */
66
static void curl_dbg_cleanup(void)
67
0
{
68
0
  if(curl_dbg_logfile &&
69
0
     curl_dbg_logfile != stderr &&
70
0
     curl_dbg_logfile != stdout) {
71
0
    (fclose)(curl_dbg_logfile);
72
0
  }
73
0
  curl_dbg_logfile = NULL;
74
0
}
75
76
/* this sets the log filename */
77
void curl_dbg_memdebug(const char *logname)
78
0
{
79
0
  if(!curl_dbg_logfile) {
80
0
    if(logname && *logname)
81
#ifdef CURL_FOPEN
82
      curl_dbg_logfile = CURL_FOPEN(logname, FOPEN_WRITETEXT);
83
#else
84
0
      curl_dbg_logfile = (fopen)(logname, FOPEN_WRITETEXT);
85
0
#endif
86
0
    else
87
0
      curl_dbg_logfile = stderr;
88
#ifdef MEMDEBUG_LOG_SYNC
89
    /* Flush the log file after every line so the log is not lost in a crash */
90
    if(curl_dbg_logfile)
91
      setbuf(curl_dbg_logfile, (char *)NULL);
92
#endif
93
0
  }
94
0
  if(!registered_cleanup)
95
0
    registered_cleanup = !atexit(curl_dbg_cleanup);
96
0
}
97
98
/* This function sets the number of malloc() calls that should return
99
   successfully! */
100
void curl_dbg_memlimit(long limit)
101
0
{
102
0
  if(!memlimit) {
103
0
    memlimit = TRUE;
104
0
    memsize = limit;
105
0
  }
106
0
}
107
108
/* returns TRUE if this is not allowed! */
109
static bool countcheck(const char *func, int line, const char *source)
110
9.10k
{
111
  /* if source is NULL, then the call is made internally and this check
112
     should not be made */
113
9.10k
  if(memlimit && source) {
114
0
    if(!memsize) {
115
      /* log to file */
116
0
      curl_dbg_log("LIMIT %s:%d %s reached memlimit\n",
117
0
                   source, line, func);
118
      /* log to stderr also */
119
0
      fprintf(stderr, "LIMIT %s:%d %s reached memlimit\n",
120
0
              source, line, func);
121
0
      fflush(curl_dbg_logfile); /* because it might crash now */
122
      /* !checksrc! disable ERRNOVAR 1 */
123
0
      CURL_SETERRNO(ENOMEM);
124
0
      return TRUE; /* RETURN ERROR! */
125
0
    }
126
0
    else
127
0
      memsize--; /* countdown */
128
0
  }
129
130
9.10k
  return FALSE; /* allow this */
131
9.10k
}
132
133
ALLOC_FUNC
134
void *curl_dbg_malloc(size_t wantedsize, int line, const char *source)
135
2.23k
{
136
2.23k
  struct memdebug *mem;
137
2.23k
  size_t size;
138
139
2.23k
  DEBUGASSERT(wantedsize != 0);
140
141
2.23k
  if(countcheck("malloc", line, source))
142
0
    return NULL;
143
144
  /* alloc at least 64 bytes */
145
2.23k
  size = sizeof(struct memdebug) + wantedsize;
146
147
2.23k
  mem = (Curl_cmalloc)(size);
148
2.23k
  if(mem) {
149
2.23k
    mem->size = wantedsize;
150
2.23k
  }
151
152
2.23k
  if(source)
153
828
    curl_dbg_log("MEM %s:%d malloc(%zu) = %p\n",
154
828
                 source, line, wantedsize,
155
828
                 mem ? (void *)mem->mem : (void *)0);
156
157
2.23k
  return mem ? mem->mem : NULL;
158
2.23k
}
159
160
ALLOC_FUNC
161
void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size,
162
                      int line, const char *source)
163
1.92k
{
164
1.92k
  struct memdebug *mem;
165
1.92k
  size_t size, user_size;
166
167
1.92k
  DEBUGASSERT(wanted_elements != 0);
168
1.92k
  DEBUGASSERT(wanted_size != 0);
169
170
1.92k
  if(countcheck("calloc", line, source))
171
0
    return NULL;
172
173
  /* alloc at least 64 bytes */
174
1.92k
  user_size = wanted_size * wanted_elements;
175
1.92k
  size = sizeof(struct memdebug) + user_size;
176
177
1.92k
  mem = (Curl_ccalloc)(1, size);
178
1.92k
  if(mem)
179
1.92k
    mem->size = user_size;
180
181
1.92k
  if(source)
182
1.92k
    curl_dbg_log("MEM %s:%d calloc(%zu,%zu) = %p\n",
183
1.92k
                 source, line, wanted_elements, wanted_size,
184
1.92k
                 mem ? (void *)mem->mem : (void *)0);
185
186
1.92k
  return mem ? mem->mem : NULL;
187
1.92k
}
188
189
ALLOC_FUNC
190
char *curl_dbg_strdup(const char *str, int line, const char *source)
191
1.40k
{
192
1.40k
  char *mem;
193
1.40k
  size_t len;
194
195
1.40k
  DEBUGASSERT(str != NULL);
196
197
1.40k
  if(countcheck("strdup", line, source))
198
0
    return NULL;
199
200
1.40k
  len = strlen(str) + 1;
201
202
1.40k
  mem = curl_dbg_malloc(len, 0, NULL); /* NULL prevents logging */
203
1.40k
  if(mem)
204
1.40k
    memcpy(mem, str, len);
205
206
1.40k
  if(source)
207
1.40k
    curl_dbg_log("MEM %s:%d strdup(%p) (%zu) = %p\n",
208
1.40k
                 source, line, (const void *)str, len, (const void *)mem);
209
210
1.40k
  return mem;
211
1.40k
}
212
213
#if defined(_WIN32) && defined(UNICODE)
214
ALLOC_FUNC
215
wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source)
216
{
217
  wchar_t *mem;
218
  size_t wsiz, bsiz;
219
220
  DEBUGASSERT(str != NULL);
221
222
  if(countcheck("wcsdup", line, source))
223
    return NULL;
224
225
  wsiz = wcslen(str) + 1;
226
  bsiz = wsiz * sizeof(wchar_t);
227
228
  mem = curl_dbg_malloc(bsiz, 0, NULL); /* NULL prevents logging */
229
  if(mem)
230
    memcpy(mem, str, bsiz);
231
232
  if(source)
233
    curl_dbg_log("MEM %s:%d wcsdup(%p) (%zu) = %p\n",
234
                source, line, (const void *)str, bsiz, (void *)mem);
235
236
  return mem;
237
}
238
#endif
239
240
/* We provide a realloc() that accepts a NULL as pointer, which then
241
   performs a malloc(). In order to work with ares. */
242
void *curl_dbg_realloc(void *ptr, size_t wantedsize,
243
                       int line, const char *source)
244
3.53k
{
245
3.53k
  struct memdebug *mem = NULL;
246
247
3.53k
  size_t size = sizeof(struct memdebug) + wantedsize;
248
249
3.53k
  DEBUGASSERT(wantedsize != 0);
250
251
3.53k
  if(countcheck("realloc", line, source))
252
0
    return NULL;
253
254
#ifdef __INTEL_COMPILER
255
#  pragma warning(push)
256
#  pragma warning(disable:1684)
257
   /* 1684: conversion from pointer to same-sized integral type */
258
#endif
259
260
3.53k
  if(ptr)
261
1.33k
    mem = (void *)((char *)ptr - offsetof(struct memdebug, mem));
262
263
#ifdef __INTEL_COMPILER
264
#  pragma warning(pop)
265
#endif
266
267
3.53k
  mem = (Curl_crealloc)(mem, size);
268
3.53k
  if(source)
269
3.53k
    curl_dbg_log("MEM %s:%d realloc(%p, %zu) = %p\n",
270
3.53k
                source, line, (void *)ptr, wantedsize,
271
3.53k
                mem ? (void *)mem->mem : (void *)0);
272
273
3.53k
  if(mem) {
274
3.53k
    mem->size = wantedsize;
275
3.53k
    return mem->mem;
276
3.53k
  }
277
278
0
  return NULL;
279
3.53k
}
280
281
void curl_dbg_free(void *ptr, int line, const char *source)
282
46.8k
{
283
46.8k
  if(ptr) {
284
6.36k
    struct memdebug *mem;
285
286
#ifdef __INTEL_COMPILER
287
#  pragma warning(push)
288
#  pragma warning(disable:1684)
289
   /* 1684: conversion from pointer to same-sized integral type */
290
#endif
291
292
6.36k
    mem = (void *)((char *)ptr - offsetof(struct memdebug, mem));
293
294
#ifdef __INTEL_COMPILER
295
#  pragma warning(pop)
296
#endif
297
298
    /* free for real */
299
6.36k
    (Curl_cfree)(mem);
300
6.36k
  }
301
302
46.8k
  if(source && ptr)
303
6.36k
    curl_dbg_log("MEM %s:%d free(%p)\n", source, line, (void *)ptr);
304
46.8k
}
305
306
curl_socket_t curl_dbg_socket(int domain, int type, int protocol,
307
                             int line, const char *source)
308
0
{
309
0
  curl_socket_t sockfd;
310
311
0
  if(countcheck("socket", line, source))
312
0
    return CURL_SOCKET_BAD;
313
314
0
  sockfd = (socket)(domain, type, protocol);
315
316
0
  if(source && (sockfd != CURL_SOCKET_BAD))
317
0
    curl_dbg_log("FD %s:%d socket() = %" FMT_SOCKET_T "\n",
318
0
                 source, line, sockfd);
319
320
0
  return sockfd;
321
0
}
322
323
SEND_TYPE_RETV curl_dbg_send(SEND_TYPE_ARG1 sockfd,
324
                            SEND_QUAL_ARG2 SEND_TYPE_ARG2 buf,
325
                            SEND_TYPE_ARG3 len, SEND_TYPE_ARG4 flags, int line,
326
                            const char *source)
327
0
{
328
0
  SEND_TYPE_RETV rc;
329
0
  if(countcheck("send", line, source))
330
0
    return -1;
331
0
  rc = (send)(sockfd, buf, len, flags);
332
0
  if(source)
333
0
    curl_dbg_log("SEND %s:%d send(%lu) = %ld\n",
334
0
                source, line, (unsigned long)len, (long)rc);
335
0
  return rc;
336
0
}
337
338
RECV_TYPE_RETV curl_dbg_recv(RECV_TYPE_ARG1 sockfd, RECV_TYPE_ARG2 buf,
339
                            RECV_TYPE_ARG3 len, RECV_TYPE_ARG4 flags, int line,
340
                            const char *source)
341
0
{
342
0
  RECV_TYPE_RETV rc;
343
0
  if(countcheck("recv", line, source))
344
0
    return -1;
345
0
  rc = (recv)(sockfd, buf, len, flags);
346
0
  if(source)
347
0
    curl_dbg_log("RECV %s:%d recv(%lu) = %ld\n",
348
0
                source, line, (unsigned long)len, (long)rc);
349
0
  return rc;
350
0
}
351
352
#ifdef HAVE_SOCKETPAIR
353
int curl_dbg_socketpair(int domain, int type, int protocol,
354
                       curl_socket_t socket_vector[2],
355
                       int line, const char *source)
356
0
{
357
0
  int res = (socketpair)(domain, type, protocol, socket_vector);
358
359
0
  if(source && (0 == res))
360
0
    curl_dbg_log("FD %s:%d socketpair() = "
361
0
                 "%" FMT_SOCKET_T " %" FMT_SOCKET_T "\n",
362
0
                 source, line, socket_vector[0], socket_vector[1]);
363
364
0
  return res;
365
0
}
366
#endif
367
368
curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen,
369
                             int line, const char *source)
370
0
{
371
0
  struct sockaddr *addr = (struct sockaddr *)saddr;
372
0
  curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen;
373
374
0
  curl_socket_t sockfd = (accept)(s, addr, addrlen);
375
376
0
  if(source && (sockfd != CURL_SOCKET_BAD))
377
0
    curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n",
378
0
                 source, line, sockfd);
379
380
0
  return sockfd;
381
0
}
382
383
#ifdef HAVE_ACCEPT4
384
curl_socket_t curl_dbg_accept4(curl_socket_t s, void *saddr, void *saddrlen,
385
                               int flags,
386
                               int line, const char *source)
387
0
{
388
0
  struct sockaddr *addr = (struct sockaddr *)saddr;
389
0
  curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen;
390
391
0
  curl_socket_t sockfd = (accept4)(s, addr, addrlen, flags);
392
393
0
  if(source && (sockfd != CURL_SOCKET_BAD))
394
0
    curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n",
395
0
                 source, line, sockfd);
396
397
0
  return sockfd;
398
0
}
399
#endif
400
401
/* separate function to allow libcurl to mark a "faked" close */
402
void curl_dbg_mark_sclose(curl_socket_t sockfd, int line, const char *source)
403
0
{
404
0
  if(source)
405
0
    curl_dbg_log("FD %s:%d sclose(%" FMT_SOCKET_T ")\n",
406
0
                 source, line, sockfd);
407
0
}
408
409
/* this is our own defined way to close sockets on *ALL* platforms */
410
int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source)
411
0
{
412
0
  int res = CURL_SCLOSE(sockfd);
413
0
  curl_dbg_mark_sclose(sockfd, line, source);
414
0
  return res;
415
0
}
416
417
ALLOC_FUNC
418
FILE *curl_dbg_fopen(const char *file, const char *mode,
419
                     int line, const char *source)
420
0
{
421
0
  FILE *res;
422
#ifdef CURL_FOPEN
423
  res = CURL_FOPEN(file, mode);
424
#else
425
0
  res = (fopen)(file, mode);
426
0
#endif
427
428
0
  if(source)
429
0
    curl_dbg_log("FILE %s:%d fopen(\"%s\",\"%s\") = %p\n",
430
0
                source, line, file, mode, (void *)res);
431
432
0
  return res;
433
0
}
434
435
ALLOC_FUNC
436
FILE *curl_dbg_fdopen(int filedes, const char *mode,
437
                      int line, const char *source)
438
0
{
439
0
  FILE *res = (fdopen)(filedes, mode);
440
0
  if(source)
441
0
    curl_dbg_log("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n",
442
0
                 source, line, filedes, mode, (void *)res);
443
0
  return res;
444
0
}
445
446
int curl_dbg_fclose(FILE *file, int line, const char *source)
447
0
{
448
0
  int res;
449
450
0
  DEBUGASSERT(file != NULL);
451
452
0
  if(source)
453
0
    curl_dbg_log("FILE %s:%d fclose(%p)\n",
454
0
                 source, line, (void *)file);
455
456
0
  res = (fclose)(file);
457
458
0
  return res;
459
0
}
460
461
/* this does the writing to the memory tracking log file */
462
void curl_dbg_log(const char *format, ...)
463
14.0k
{
464
14.0k
  char buf[1024];
465
14.0k
  int nchars;
466
14.0k
  va_list ap;
467
468
14.0k
  if(!curl_dbg_logfile)
469
14.0k
    return;
470
471
0
  va_start(ap, format);
472
0
  nchars = mvsnprintf(buf, sizeof(buf), format, ap);
473
0
  va_end(ap);
474
475
0
  if(nchars > (int)sizeof(buf) - 1)
476
0
    nchars = (int)sizeof(buf) - 1;
477
478
0
  if(nchars > 0)
479
0
    (fwrite)(buf, 1, (size_t)nchars, curl_dbg_logfile);
480
0
}
481
482
#endif /* CURLDEBUG */