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