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