Line data Source code
1 : //
2 : // utils.c
3 : // cloudsync
4 : //
5 : // Created by Marco Bambini on 21/08/24.
6 : //
7 :
8 : #include "utils.h"
9 : #include <ctype.h>
10 : #include <stdlib.h>
11 :
12 : #ifdef _WIN32
13 : #include <windows.h>
14 : #include <objbase.h>
15 : #include <bcrypt.h>
16 : #include <ntstatus.h> //for STATUS_SUCCESS
17 : #include <io.h>
18 : #define file_close _close
19 : #else
20 : #include <unistd.h>
21 : #if defined(__APPLE__) && !defined(CLOUDSYNC_POSTGRESQL_BUILD)
22 : #include <Security/Security.h>
23 : #elif !defined(__ANDROID__)
24 : #include <sys/random.h>
25 : #endif
26 : #define file_close close
27 : #endif
28 :
29 : #ifdef CLOUDSYNC_DESKTOP_OS
30 : #include <fcntl.h>
31 : #include <errno.h>
32 : #include <sys/stat.h>
33 : #include <sys/types.h>
34 : #endif
35 :
36 : #define FNV_OFFSET_BASIS 0xcbf29ce484222325ULL
37 : #define FNV_PRIME 0x100000001b3ULL
38 : #define HASH_CHAR(_c) do { h ^= (uint8_t)(_c); h *= FNV_PRIME; h_final = h;} while (0)
39 :
40 : // MARK: - UUIDv7 -
41 :
42 : /*
43 : UUIDv7 is a 128-bit unique identifier like it's older siblings, such as the widely used UUIDv4.
44 : But unlike v4, UUIDv7 is time-sortable with 1 ms precision.
45 : By combining the timestamp and the random parts, UUIDv7 becomes an excellent choice for record identifiers in databases, including distributed ones.
46 :
47 : UUIDv7 offers several advantages.
48 : It includes a 48-bit Unix timestamp with millisecond accuracy and will overflow far in the future (10899 AD).
49 : It also include 74 random bits which means billions can be created every second without collisions.
50 : Because of its structure UUIDv7s are globally sortable and can be created in parallel in a distributed system.
51 :
52 : https://antonz.org/uuidv7/#c
53 : https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7
54 : */
55 :
56 4325 : int cloudsync_uuid_v7 (uint8_t value[UUID_LEN]) {
57 : // fill the buffer with high-quality random data
58 : #ifdef _WIN32
59 : if (BCryptGenRandom(NULL, (BYTE*)value, UUID_LEN, BCRYPT_USE_SYSTEM_PREFERRED_RNG) != STATUS_SUCCESS) return -1;
60 : #elif defined(__APPLE__) && !defined(CLOUDSYNC_POSTGRESQL_BUILD)
61 : // Use SecRandomCopyBytes for macOS/iOS
62 4325 : if (SecRandomCopyBytes(kSecRandomDefault, UUID_LEN, value) != errSecSuccess) return -1;
63 : #elif defined(__APPLE__) && defined(CLOUDSYNC_POSTGRESQL_BUILD)
64 : // PostgreSQL build: use getentropy to avoid Security.framework type conflicts
65 : if (getentropy(value, UUID_LEN) != 0) return -1;
66 : #elif defined(__ANDROID__)
67 : //arc4random_buf doesn't have a return value to check for success
68 : arc4random_buf(value, UUID_LEN);
69 : #else
70 : if (getentropy(value, UUID_LEN) != 0) return -1;
71 : #endif
72 :
73 : // get current timestamp in ms
74 : struct timespec ts;
75 : #ifdef __ANDROID__
76 : if (clock_gettime(CLOCK_REALTIME, &ts) != 0) return -1;
77 : #else
78 4325 : if (timespec_get(&ts, TIME_UTC) == 0) return -1;
79 : #endif
80 :
81 : // add timestamp part to UUID
82 4325 : uint64_t timestamp = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
83 4325 : value[0] = (timestamp >> 40) & 0xFF;
84 4325 : value[1] = (timestamp >> 32) & 0xFF;
85 4325 : value[2] = (timestamp >> 24) & 0xFF;
86 4325 : value[3] = (timestamp >> 16) & 0xFF;
87 4325 : value[4] = (timestamp >> 8) & 0xFF;
88 4325 : value[5] = timestamp & 0xFF;
89 :
90 : // version and variant
91 4325 : value[6] = (value[6] & 0x0F) | 0x70; // UUID version 7
92 4325 : value[8] = (value[8] & 0x3F) | 0x80; // RFC 4122 variant
93 :
94 4325 : return 0;
95 4325 : }
96 :
97 4365 : char *cloudsync_uuid_v7_stringify (uint8_t uuid[UUID_LEN], char value[UUID_STR_MAXLEN], bool dash_format) {
98 4365 : if (dash_format) {
99 2083 : snprintf(value, UUID_STR_MAXLEN, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
100 : uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
101 : uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]
102 : );
103 2083 : } else {
104 2282 : snprintf(value, UUID_STR_MAXLEN, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
105 : uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
106 : uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]
107 : );
108 : }
109 :
110 4365 : return (char *)value;
111 : }
112 :
113 2080 : char *cloudsync_uuid_v7_string (char value[UUID_STR_MAXLEN], bool dash_format) {
114 : uint8_t uuid[UUID_LEN];
115 :
116 2080 : if (cloudsync_uuid_v7(uuid) != 0) return NULL;
117 2080 : return cloudsync_uuid_v7_stringify(uuid, value, dash_format);
118 2080 : }
119 :
120 96 : static int cloudsync_hex_nibble (char c) {
121 96 : if (c >= '0' && c <= '9') return c - '0';
122 32 : if (c >= 'a' && c <= 'f') return c - 'a' + 10;
123 0 : if (c >= 'A' && c <= 'F') return c - 'A' + 10;
124 0 : return -1;
125 96 : }
126 :
127 3 : int cloudsync_uuid_v7_parse (const char *str, int len, uint8_t out[UUID_LEN]) {
128 3 : if (!str || !out) return -1;
129 3 : if (len < 0) len = (int)strlen(str);
130 :
131 : // Accept the canonical dashed form (8-4-4-4-12) or bare 32-hex; dashes,
132 : // if present, must be at the canonical positions. Parse 32 hex nibbles.
133 3 : int nibbles = 0;
134 59 : for (int i = 0; i < len; ++i) {
135 56 : char c = str[i];
136 56 : if (c == '-') continue;
137 48 : int hi = cloudsync_hex_nibble(c);
138 48 : if (hi < 0) return -1;
139 48 : if (i + 1 >= len) return -1;
140 48 : int lo = cloudsync_hex_nibble(str[i + 1]);
141 48 : if (lo < 0) return -1;
142 48 : if (nibbles >= UUID_LEN) return -1;
143 48 : out[nibbles++] = (uint8_t)((hi << 4) | lo);
144 48 : ++i; // consumed the low nibble too
145 48 : }
146 3 : return (nibbles == UUID_LEN) ? 0 : -1;
147 3 : }
148 :
149 1003 : int cloudsync_uuid_v7_compare (uint8_t value1[UUID_LEN], uint8_t value2[UUID_LEN]) {
150 : // reconstruct the timestamp by reversing the bit shifts and combining the bytes
151 3009 : uint64_t t1 = ((uint64_t)value1[0] << 40) | ((uint64_t)value1[1] << 32) | ((uint64_t)value1[2] << 24) |
152 2006 : ((uint64_t)value1[3] << 16) | ((uint64_t)value1[4] << 8) | ((uint64_t)value1[5]);
153 3009 : uint64_t t2 = ((uint64_t)value2[0] << 40) | ((uint64_t)value2[1] << 32) | ((uint64_t)value2[2] << 24) |
154 2006 : ((uint64_t)value2[3] << 16) | ((uint64_t)value2[4] << 8) | ((uint64_t)value2[5]);
155 :
156 1003 : if (t1 == t2) return memcmp(value1, value2, UUID_LEN);
157 1 : return (t1 > t2) ? 1 : -1;
158 1003 : }
159 :
160 : // MARK: - General -
161 :
162 30850 : char *cloudsync_string_ndup_v2 (const char *str, size_t len, bool lowercase) {
163 30850 : if (str == NULL) return NULL;
164 :
165 30848 : char *s = (char *)cloudsync_memory_alloc((uint64_t)(len + 1));
166 30848 : if (!s) return NULL;
167 :
168 30848 : if (lowercase) {
169 : // convert each character to lowercase and copy it to the new string
170 12534 : for (size_t i = 0; i < len; i++) {
171 11106 : s[i] = (char)tolower(str[i]);
172 11106 : }
173 1428 : } else {
174 29420 : memcpy(s, str, len);
175 : }
176 :
177 : // null-terminate the string
178 30848 : s[len] = '\0';
179 :
180 30848 : return s;
181 30850 : }
182 :
183 3496 : char *cloudsync_string_ndup (const char *str, size_t len) {
184 3496 : return cloudsync_string_ndup_v2(str, len, false);
185 : }
186 :
187 2 : char *cloudsync_string_ndup_lowercase (const char *str, size_t len) {
188 2 : return cloudsync_string_ndup_v2(str, len, true);
189 : }
190 :
191 25924 : char *cloudsync_string_dup (const char *str) {
192 25924 : return cloudsync_string_ndup_v2(str, (str) ? strlen(str) : 0, false);
193 : }
194 :
195 1428 : char *cloudsync_string_dup_lowercase (const char *str) {
196 1428 : return cloudsync_string_ndup_v2(str, (str) ? strlen(str) : 0, true);
197 : }
198 :
199 8 : int cloudsync_blob_compare(const char *blob1, size_t size1, const char *blob2, size_t size2) {
200 8 : if (size1 != size2) return (size1 > size2) ? 1 : -1; // blobs are different if sizes are different
201 5 : return memcmp(blob1, blob2, size1); // use memcmp for byte-by-byte comparison
202 8 : }
203 :
204 50003 : void cloudsync_rowid_decode (int64_t rowid, int64_t *db_version, int64_t *seq) {
205 : // use unsigned 64-bit integer for intermediate calculations
206 : // when db_version is large enough, it can cause overflow, leading to negative values
207 : // to handle this correctly, we need to ensure the calculations are done in an unsigned 64-bit integer context
208 : // before converting back to int64_t as needed
209 50003 : uint64_t urowid = (uint64_t)rowid;
210 :
211 : // define the bit mask for seq (30 bits)
212 50003 : const uint64_t SEQ_MASK = 0x3FFFFFFF; // (2^30 - 1)
213 :
214 : // extract seq by masking the lower 30 bits
215 50003 : *seq = (int64_t)(urowid & SEQ_MASK);
216 :
217 : // extract db_version by shifting 30 bits to the right
218 50003 : *db_version = (int64_t)(urowid >> 30);
219 50003 : }
220 :
221 2 : char *cloudsync_string_replace_prefix(const char *input, char *prefix, char *replacement) {
222 : //const char *prefix = "sqlitecloud://";
223 : //const char *replacement = "https://";
224 2 : size_t prefix_len = strlen(prefix);
225 2 : size_t replacement_len = strlen(replacement);
226 :
227 2 : if (strncmp(input, prefix, prefix_len) == 0) {
228 : // allocate memory for new string
229 1 : size_t input_len = strlen(input);
230 1 : size_t new_len = input_len - prefix_len + replacement_len;
231 1 : char *result = cloudsync_memory_alloc(new_len + 1); // +1 for null terminator
232 1 : if (!result) return NULL;
233 :
234 : // copy replacement and the rest of the input string
235 1 : strcpy(result, replacement);
236 1 : strcpy(result + replacement_len, input + prefix_len);
237 1 : return result;
238 : }
239 :
240 : // If no match, return the original string
241 1 : return (char *)input;
242 2 : }
243 :
244 : /*
245 : Compute a normalized hash of a CREATE TABLE statement.
246 :
247 : * Normalization:
248 : * - Skips comments (-- and / * )
249 : * - Skips non-printable characters
250 : * - Collapses runs of whitespace to single space
251 : * - Case-insensitive outside quotes
252 : * - Preserves quoted string content exactly
253 : * - Handles escaped quotes
254 : * - Trims trailing spaces and semicolons from the effective hash
255 : */
256 310 : uint64_t fnv1a_hash (const char *data, size_t len) {
257 310 : uint64_t h = FNV_OFFSET_BASIS;
258 310 : int q = 0; // quote state: 0 / '\'' / '"'
259 310 : int cmt = 0; // comment state: 0 / 1=line / 2=block
260 310 : int last_space = 1; // prevent leading space
261 310 : uint64_t h_final = h; // hash state after last non-space, non-semicolon char
262 :
263 71333 : for (size_t i = 0; i < len; i++) {
264 71023 : int c = data[i];
265 71023 : int next = (i + 1 < len) ? data[i + 1] : 0;
266 :
267 : // detect start of comments
268 71023 : if (!q && !cmt && c == '-' && next == '-') {cmt = 1; i += 1; continue;}
269 71022 : if (!q && !cmt && c == '/' && next == '*') {cmt = 2; i += 1; continue;}
270 :
271 : // skip comments
272 71021 : if (cmt == 1) {if (c == '\n') cmt = 0; continue;}
273 71013 : if (cmt == 2) {if (c == '*' && next == '/') { cmt = 0; i += 1; } continue;}
274 :
275 : // handle quotes
276 71003 : if (c == '\'' || c == '"') {
277 1398 : if (q == c) {
278 439 : if (next == c) {HASH_CHAR(c); i += 1; continue;}
279 439 : q = 0;
280 1398 : } else if (!q) q = c;
281 1398 : HASH_CHAR(c);
282 1398 : last_space = 0;
283 1398 : continue;
284 : }
285 :
286 : // inside quote → hash exactly
287 69605 : if (q) {HASH_CHAR(c); last_space = 0; continue;}
288 :
289 : // skip non-printable
290 25453 : if (!isprint((unsigned char)c)) continue;
291 :
292 : // whitespace normalization
293 25437 : if (isspace((unsigned char)c)) {
294 : // look ahead to next non-space, non-comment char
295 2049 : size_t j = i + 1;
296 2059 : while (j < len && isspace((unsigned char)data[j])) j++;
297 :
298 2049 : int next_c = (j < len) ? data[j] : 0;
299 :
300 : // if next char is punctuation where space is irrelevant → skip space
301 2049 : if (next_c == '(' || next_c == ')' || next_c == ',' || next_c == ';' || next_c == 0) {
302 : // skip inserting space
303 8 : last_space = 1;
304 8 : continue;
305 : }
306 :
307 : // else, insert one space
308 2041 : if (!last_space) {HASH_CHAR(' '); last_space = 1;}
309 2041 : continue;
310 : }
311 :
312 : // skip semicolons at end
313 23388 : if (c == ';') {last_space = 1; continue;}
314 :
315 : // normal visible char
316 23388 : HASH_CHAR(tolower(c));
317 23388 : last_space = 0;
318 23388 : }
319 :
320 310 : return h_final;
321 : }
322 :
323 : // MARK: - Files -
324 :
325 : #ifdef CLOUDSYNC_DESKTOP_OS
326 :
327 0 : bool cloudsync_file_delete (const char *path) {
328 : #ifdef _WIN32
329 : return DeleteFileA(path);
330 : #else
331 0 : return (unlink(path) == 0);
332 : #endif
333 : }
334 :
335 0 : static bool cloudsync_file_read_all (int fd, char *buf, size_t n) {
336 0 : size_t off = 0;
337 0 : while (off < n) {
338 : #ifdef _WIN32
339 : int r = _read(fd, buf + off, (unsigned)(n - off));
340 : if (r <= 0) return false;
341 : #else
342 0 : ssize_t r = read(fd, buf + off, n - off);
343 0 : if (r < 0) {
344 0 : if (errno == EINTR) continue;
345 0 : return false;
346 : }
347 0 : if (r == 0) return false; // unexpected EOF
348 : #endif
349 0 : off += (size_t)r;
350 : }
351 0 : return true;
352 0 : }
353 :
354 0 : char *cloudsync_file_read (const char *path, int64_t *len) {
355 0 : int fd = -1;
356 0 : char *buffer = NULL;
357 :
358 : #ifdef _WIN32
359 : fd = _open(path, _O_RDONLY | _O_BINARY);
360 : #else
361 0 : fd = open(path, O_RDONLY);
362 : #endif
363 0 : if (fd < 0) goto abort_read;
364 :
365 : // Get size after open to reduce TOCTTOU
366 : #ifdef _WIN32
367 : struct _stat64 st;
368 : if (_fstat64(fd, &st) != 0 || st.st_size < 0) goto abort_read;
369 : int64_t isz = st.st_size;
370 : #else
371 : struct stat st;
372 0 : if (fstat(fd, &st) != 0 || st.st_size < 0) goto abort_read;
373 0 : int64_t isz = st.st_size;
374 : #endif
375 :
376 0 : size_t sz = (size_t)isz;
377 : // optional: guard against huge files that don't fit in size_t
378 0 : if ((int64_t)sz != isz) goto abort_read;
379 :
380 0 : buffer = (char *)cloudsync_memory_alloc(sz + 1);
381 0 : if (!buffer) goto abort_read;
382 0 : buffer[sz] = '\0';
383 :
384 0 : if (!cloudsync_file_read_all(fd, buffer, sz)) goto abort_read;
385 0 : if (len) *len = sz;
386 :
387 0 : file_close(fd);
388 0 : return buffer;
389 :
390 : abort_read:
391 : //fprintf(stderr, "file_read: failed to read '%s': %s\n", path, strerror(errno));
392 0 : if (len) *len = -1;
393 0 : if (buffer) cloudsync_memory_free(buffer);
394 0 : if (fd >= 0) file_close(fd);
395 0 : return NULL;
396 0 : }
397 :
398 0 : int cloudsync_file_create (const char *path) {
399 : #ifdef _WIN32
400 : int flags = _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY;
401 : int mode = _S_IWRITE; // Windows ignores most POSIX perms
402 : return _open(path, flags, mode);
403 : #else
404 0 : int flags = O_WRONLY | O_CREAT | O_TRUNC;
405 0 : mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
406 0 : return open(path, flags, mode);
407 : #endif
408 : }
409 :
410 0 : static bool cloudsync_file_write_all (int fd, const char *buf, size_t n) {
411 0 : size_t off = 0;
412 0 : while (off < n) {
413 : #ifdef _WIN32
414 : int w = _write(fd, buf + off, (unsigned)(n - off));
415 : if (w <= 0) return false;
416 : #else
417 0 : ssize_t w = write(fd, buf + off, n - off);
418 0 : if (w < 0) {
419 0 : if (errno == EINTR) continue;
420 0 : return false;
421 : }
422 0 : if (w == 0) return false;
423 : #endif
424 0 : off += (size_t)w;
425 : }
426 0 : return true;
427 0 : }
428 :
429 0 : bool cloudsync_file_write (const char *path, const char *buffer, size_t len) {
430 0 : int fd = cloudsync_file_create(path);
431 0 : if (fd < 0) return false;
432 :
433 0 : bool res = cloudsync_file_write_all(fd, buffer, len);
434 :
435 0 : file_close(fd);
436 0 : return res;
437 0 : }
438 :
439 : #endif
440 :
441 : // MARK: - Memory Debugger -
442 :
443 : #if CLOUDSYNC_DEBUG_MEMORY
444 : #include <execinfo.h>
445 : #include <inttypes.h>
446 : #include <assert.h>
447 :
448 : #include "khash.h"
449 : KHASH_MAP_INIT_INT64(HASHTABLE_INT64_VOIDPTR, void*)
450 :
451 : #define STACK_DEPTH 128
452 : #define BUILD_ERROR(...) char current_error[1024]; snprintf(current_error, sizeof(current_error), __VA_ARGS__)
453 : #define BUILD_STACK(v1,v2) size_t v1; char **v2 = _ptr_stacktrace(&v1)
454 :
455 : typedef struct {
456 : void *ptr;
457 : size_t size;
458 : bool deleted;
459 : size_t nrealloc;
460 :
461 : // record where it has been allocated/reallocated
462 : size_t nframe;
463 : char **frames;
464 :
465 : // record where it has been freed
466 : size_t nframe2;
467 : char **frames2;
468 : } mem_slot;
469 :
470 : static void memdebug_report (char *str, char **stack, size_t nstack, mem_slot *slot);
471 :
472 : static khash_t(HASHTABLE_INT64_VOIDPTR) *htable;
473 : static uint64_t nalloc, nrealloc, nfree, mem_current, mem_max;
474 :
475 : static void *_ptr_lookup (void *ptr) {
476 : khiter_t k = kh_get(HASHTABLE_INT64_VOIDPTR, htable, (int64_t)ptr);
477 : void *result = (k == kh_end(htable)) ? NULL : (void *)kh_value(htable, k);
478 : return result;
479 : }
480 :
481 : static bool _ptr_insert (void *ptr, mem_slot *slot) {
482 : int err = 0;
483 : khiter_t k = kh_put(HASHTABLE_INT64_VOIDPTR, htable, (int64_t)ptr, &err);
484 : if (err != -1) kh_value(htable, k) = (void *)slot;
485 : return (err != -1);
486 : }
487 :
488 : static char **_ptr_stacktrace (size_t *nframes) {
489 : #if _WIN32
490 : // http://www.codeproject.com/Articles/11132/Walking-the-callstack
491 : // https://spin.atomicobject.com/2013/01/13/exceptions-stack-traces-c/
492 : #else
493 : void *callstack[STACK_DEPTH];
494 : int n = backtrace(callstack, STACK_DEPTH);
495 : char **strs = backtrace_symbols(callstack, n);
496 : *nframes = (size_t)n;
497 : return strs;
498 : #endif
499 : }
500 :
501 : static mem_slot *_ptr_add (void *ptr, size_t size) {
502 : mem_slot *slot = (mem_slot *)calloc(1, sizeof(mem_slot));
503 : assert(slot);
504 :
505 : slot->ptr = ptr;
506 : slot->size = size;
507 : slot->frames = _ptr_stacktrace(&slot->nframe);
508 : bool ok = _ptr_insert(ptr, slot);
509 : assert(ok);
510 :
511 : ++nalloc;
512 : mem_current += size;
513 : if (mem_current > mem_max) mem_max = mem_current;
514 :
515 : return slot;
516 : }
517 :
518 : static void _ptr_remove (void *ptr) {
519 : mem_slot *slot = (mem_slot *)_ptr_lookup(ptr);
520 : if (!slot) {
521 : BUILD_ERROR("Unable to find old pointer to free.");
522 : memdebug_report(current_error, NULL, 0, NULL);
523 : return;
524 : }
525 :
526 : if (slot->deleted) {
527 : BUILD_ERROR("Pointer already freed.");
528 : BUILD_STACK(n, stack);
529 : memdebug_report(current_error, stack, n, slot);
530 : }
531 :
532 : size_t old_size = slot->size;
533 : slot->deleted = true;
534 : slot->frames2 = _ptr_stacktrace(&slot->nframe2);
535 :
536 : ++nfree;
537 : mem_current -= old_size;
538 : }
539 :
540 : static void _ptr_replace (void *old_ptr, void *new_ptr, size_t new_size) {
541 : if (old_ptr == NULL) {
542 : _ptr_add(new_ptr, new_size);
543 : return;
544 : }
545 :
546 : // remove old ptr (implicit free performed by realloc)
547 : _ptr_remove(old_ptr);
548 :
549 : // add newly allocated prt (implicit alloc performed by realloc)
550 : mem_slot *slot = _ptr_add(new_ptr, new_size);
551 : ++slot->nrealloc;
552 :
553 : ++nrealloc;
554 : if (mem_current > mem_max) mem_max = mem_current;
555 : }
556 :
557 : // MARK: -
558 :
559 : static bool stacktrace_is_internal(const char *s) {
560 : static const char *reserved[] = {"??? ", "libdyld.dylib ", "memdebug_", "_ptr_", NULL};
561 :
562 : const char **r = reserved;
563 : while (*r) {
564 : if (strstr(s, *r)) return true;
565 : ++r;
566 : }
567 : return false;
568 : }
569 :
570 : static void memdebug_report (char *str, char **stack, size_t nstack, mem_slot *slot) {
571 : printf("%s\n", str);
572 : for (size_t i=0; i<nstack; ++i) {
573 : if (stacktrace_is_internal(stack[i])) continue;
574 : printf("%s\n", stack[i]);
575 : }
576 :
577 : if (slot) {
578 : printf("\nallocated:\n");
579 : for (size_t i=0; i<slot->nframe; ++i) {
580 : if (stacktrace_is_internal(slot->frames[i])) continue;
581 : printf("%s\n", slot->frames[i]);
582 : }
583 :
584 : printf("\nfreed:\n");
585 : for (size_t i=0; i<slot->nframe2; ++i) {
586 : if (stacktrace_is_internal(slot->frames2[i])) continue;
587 : printf("%s\n", slot->frames2[i]);
588 : }
589 : }
590 : }
591 :
592 : void memdebug_init (int once) {
593 : if (htable == NULL) htable = kh_init(HASHTABLE_INT64_VOIDPTR);
594 : }
595 :
596 : void memdebug_finalize (void) {
597 : printf("\n========== MEMORY STATS ==========\n");
598 : printf("Allocations count: %" PRIu64 "\n", nalloc);
599 : printf("Reallocations count: %" PRIu64 "\n", nrealloc);
600 : printf("Free count: %" PRIu64 "\n", nfree);
601 : printf("Leaked: %" PRIu64 " (bytes)\n", mem_current);
602 : printf("Max memory usage: %" PRIu64 " (bytes)\n", mem_max);
603 : printf("==================================\n\n");
604 :
605 : if (mem_current > 0) {
606 : printf("\n========== LEAKS DETAILS ==========\n");
607 :
608 : khiter_t k;
609 : for (k = kh_begin(htable); k != kh_end(htable); ++k) {
610 : if (kh_exist(htable, k)) {
611 : mem_slot *slot = (mem_slot *)kh_value(htable, k);
612 : if ((!slot->ptr) || (slot->deleted)) continue;
613 :
614 : printf("Block %p size: %zu (reallocated %zu)\n", slot->ptr, slot->size, slot->nrealloc);
615 : printf("Call stack:\n");
616 : printf("===========\n");
617 : for (size_t j=0; j<slot->nframe; ++j) {
618 : if (stacktrace_is_internal(slot->frames[j])) continue;
619 : printf("%s\n", slot->frames[j]);
620 : }
621 : printf("===========\n\n");
622 : }
623 : }
624 : }
625 : }
626 :
627 : void *memdebug_alloc (uint64_t size) {
628 : void *ptr = dbmem_alloc(size);
629 : if (!ptr) {
630 : BUILD_ERROR("Unable to allocated a block of %" PRIu64" bytes", size);
631 : BUILD_STACK(n, stack);
632 : memdebug_report(current_error, stack, n, NULL);
633 : return NULL;
634 : }
635 : _ptr_add(ptr, size);
636 : return ptr;
637 : }
638 :
639 : void *memdebug_zeroalloc (uint64_t size) {
640 : void *ptr = memdebug_alloc(size);
641 : if (!ptr) return NULL;
642 :
643 : memset(ptr, 0, (size_t)size);
644 : return ptr;
645 : }
646 :
647 : void *memdebug_realloc (void *ptr, uint64_t new_size) {
648 : if (!ptr) return memdebug_alloc(new_size);
649 :
650 : mem_slot *slot = _ptr_lookup(ptr);
651 : if (!slot) {
652 : BUILD_ERROR("Pointer being reallocated was now previously allocated.");
653 : BUILD_STACK(n, stack);
654 : memdebug_report(current_error, stack, n, NULL);
655 : return NULL;
656 : }
657 :
658 : void *back_ptr = ptr;
659 : void *new_ptr = dbmem_realloc(ptr, new_size);
660 : if (!new_ptr) {
661 : BUILD_ERROR("Unable to reallocate a block of %" PRIu64 " bytes.", new_size);
662 : BUILD_STACK(n, stack);
663 : memdebug_report(current_error, stack, n, slot);
664 : return NULL;
665 : }
666 :
667 : _ptr_replace(back_ptr, new_ptr, new_size);
668 : return new_ptr;
669 : }
670 :
671 : char *memdebug_vmprintf (const char *format, va_list list) {
672 : char *ptr = dbmem_vmprintf(format, list);
673 : if (!ptr) {
674 : BUILD_ERROR("Unable to allocated for dbmem_vmprintf with format %s", format);
675 : BUILD_STACK(n, stack);
676 : memdebug_report(current_error, stack, n, NULL);
677 : return NULL;
678 : }
679 :
680 : _ptr_add(ptr, dbmem_size(ptr));
681 : return ptr;
682 : }
683 :
684 : char *memdebug_mprintf(const char *format, ...) {
685 : va_list ap;
686 : char *z;
687 :
688 : va_start(ap, format);
689 : z = memdebug_vmprintf(format, ap);
690 : va_end(ap);
691 :
692 : return z;
693 : }
694 :
695 : uint64_t memdebug_msize (void *ptr) {
696 : return dbmem_size(ptr);
697 : }
698 :
699 : void memdebug_free (void *ptr) {
700 : if (!ptr) {
701 : BUILD_ERROR("Trying to deallocate a NULL ptr.");
702 : BUILD_STACK(n, stack);
703 : memdebug_report(current_error, stack, n, NULL);
704 : }
705 :
706 : // ensure ptr has been previously allocated by malloc, calloc or realloc and not yet freed with free
707 : mem_slot *slot = _ptr_lookup(ptr);
708 :
709 : if (!slot) {
710 : BUILD_ERROR("Pointer being freed was not previously allocated.");
711 : BUILD_STACK(n, stack);
712 : memdebug_report(current_error, stack, n, NULL);
713 : return;
714 : }
715 :
716 : if (slot->deleted) {
717 : BUILD_ERROR("Pointer already freed.");
718 : BUILD_STACK(n, stack);
719 : memdebug_report(current_error, stack, n, slot);
720 : return;
721 : }
722 :
723 : _ptr_remove(ptr);
724 : dbmem_free(ptr);
725 : }
726 :
727 : #endif
|