| 351 | } |
| 352 | |
| 353 | size_t dump_alloc_leaks(FILE *out) { |
| 354 | // Disarm so fprintf/Sym*/etc don't churn the map during the dump. |
| 355 | bool was_armed = g_armed.exchange(false); |
| 356 | (void)was_armed; |
| 357 | tl_inside = true; |
| 358 | |
| 359 | auto t0 = std::chrono::steady_clock::now(); |
| 360 | |
| 361 | // Snapshot under lock; std::malloc keeps storage out of the tracker. |
| 362 | Group *groups = nullptr; |
| 363 | size_t groupCount = 0; |
| 364 | size_t groupCap = 0; |
| 365 | size_t totalLeaks = 0; |
| 366 | size_t totalBytes = 0; |
| 367 | size_t largestSingle = 0; |
| 368 | bool groupsExhausted = false; |
| 369 | |
| 370 | { |
| 371 | std::lock_guard<std::mutex> lock(getMutex()); |
| 372 | LeakMap &m = getMap(); |
| 373 | for (size_t i = 0; i < m.capacity; ++i) { |
| 374 | void *k = m.entries[i].key; |
| 375 | if (k == nullptr || k == kTombstone) continue; |
| 376 | const AllocInfo &info = m.entries[i].info; |
| 377 | ++totalLeaks; |
| 378 | totalBytes += info.size; |
| 379 | if (info.size > largestSingle) largestSingle = info.size; |
| 380 | |
| 381 | if (groupsExhausted) continue; |
| 382 | |
| 383 | uint64_t h = hash_frames(info.frames, info.frameCount); |
| 384 | |
| 385 | Group *g = nullptr; |
| 386 | for (size_t j = 0; j < groupCount; ++j) { |
| 387 | if (groups[j].hash == h) { g = &groups[j]; break; } |
| 388 | } |
| 389 | if (!g) { |
| 390 | if (groupCount == groupCap) { |
| 391 | size_t newCap = groupCap ? groupCap * 2 : 64; |
| 392 | Group *resized = (Group*)std::realloc(groups, newCap * sizeof(Group)); |
| 393 | if (!resized) { |
| 394 | // OOM mid-shutdown: keep counting totals, drop new sites. |
| 395 | groupsExhausted = true; |
| 396 | continue; |
| 397 | } |
| 398 | groups = resized; |
| 399 | groupCap = newCap; |
| 400 | } |
| 401 | g = &groups[groupCount++]; |
| 402 | g->hash = h; |
| 403 | g->count = 0; |
| 404 | g->totalBytes = 0; |
| 405 | g->minSize = SIZE_MAX; |
| 406 | g->maxSize = 0; |
| 407 | g->samplePtr = k; |
| 408 | g->frameCount = info.frameCount; |
| 409 | memcpy(g->frames, info.frames, info.frameCount * sizeof(void*)); |
| 410 | } |
no test coverage detected