LCOV - code coverage report
Current view: top level - src - utils.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 69.9 % 193 135
Test Date: 2026-07-17 12:57:54 Functions: 71.4 % 21 15

            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
        

Generated by: LCOV version 2.5-0