| … | … | @@ -7728,6 +7728,12 @@ void RenderingDevice::free_rid(RID p_rid) { |
| 7728 | 7728 | } |
| 7729 | 7729 | |
| 7730 | 7730 | void RenderingDevice::_free_internal(RID p_id) { |
| 7731 | #ifdef DEBUG_ENABLED |
| 7732 | // Set only on the invalid-free path below, so the tail of this function can |
| 7733 | // record which IDs were genuinely freed. That history is what lets a later |
| 7734 | // invalid free be reported as a DOUBLE free rather than just "invalid". |
| 7735 | bool dbg_was_invalid = false; |
| 7736 | #endif |
| 7731 | 7737 | #ifdef DEV_ENABLED |
| 7732 | 7738 | String resource_name; |
| 7733 | 7739 | if (resource_names.has(p_id)) { |
| … | … | @@ -7858,6 +7864,9 @@ void RenderingDevice::_free_internal(RID p_id) { |
| 7858 | 7864 | frames[frame].buffers_to_dispose_of.push_back(*hit_sbt); |
| 7859 | 7865 | hit_sbt_owner.free(p_id); |
| 7860 | 7866 | } else { |
| 7867 | #ifdef DEBUG_ENABLED |
| 7868 | dbg_was_invalid = true; |
| 7869 | #endif |
| 7861 | 7870 | static int trace_count = 0; |
| 7862 | 7871 | String trace_suffix; |
| 7863 | 7872 | if (trace_count < 20) { |
| … | … | @@ -7875,11 +7884,31 @@ void RenderingDevice::_free_internal(RID p_id) { |
| 7875 | 7884 | static int invalid_free_count = 0; |
| 7876 | 7885 | invalid_free_count++; |
| 7877 | 7886 | |
| 7887 | // backtrace_dump() yields nothing usable in these builds even with |
| 7888 | // debug_symbols, so instead of asking WHERE the free came from, answer the |
| 7889 | // question that actually splits the candidates: was this ID ever valid? |
| 7890 | // |
| 7891 | // "double free" -> it was allocated and freed by us before, so something |
| 7892 | // is holding a stale handle (e.g. a cache that outlived |
| 7893 | // the resource) and freeing it a second time. |
| 7894 | // "never valid" -> it was never a live RID here: uninitialised or |
| 7895 | // corrupted memory, or an ID from another RD instance. |
| 7896 | // |
| 7897 | // The RID is also decoded: RID_Alloc packs (index << 32) | validator, so a |
| 7898 | // recurring index with a changing validator is slot reuse -- the signature |
| 7899 | // of stale handles surviving a free/realloc cycle. |
| 7900 | String dbg_decoded; |
| 7901 | #ifdef DEBUG_ENABLED |
| 7902 | String dbg_kind = _dbg_freed_ids.has(p_id.get_id()) ? "DOUBLE FREE (freed before)" : "never valid"; |
| 7903 | dbg_decoded = " [index=" + itos(p_id.get_id() >> 32) + |
| 7904 | " validator=" + itos(p_id.get_id() & 0xFFFFFFFF) + " " + dbg_kind + "]"; |
| 7905 | #endif |
| 7906 | |
| 7878 | 7907 | if (invalid_free_count <= PRINT_LIMIT) { |
| 7879 | 7908 | #ifdef DEV_ENABLED |
| 7880 | | ERR_PRINT("Attempted to free invalid ID: " + itos(p_id.get_id()) + " " + resource_name + trace_suffix); |
| 7909 | ERR_PRINT("Attempted to free invalid ID: " + itos(p_id.get_id()) + " " + resource_name + dbg_decoded + trace_suffix); |
| 7881 | 7910 | #else |
| 7882 | | ERR_PRINT("Attempted to free invalid ID: " + itos(p_id.get_id()) + trace_suffix); |
| 7911 | ERR_PRINT("Attempted to free invalid ID: " + itos(p_id.get_id()) + dbg_decoded + trace_suffix); |
| 7883 | 7912 | #endif |
| 7884 | 7913 | if (invalid_free_count == PRINT_LIMIT) { |
| 7885 | 7914 | ERR_PRINT("Attempted to free invalid ID: reached " + itos(PRINT_LIMIT) + |
| … | … | @@ -7890,6 +7919,12 @@ void RenderingDevice::_free_internal(RID p_id) { |
| 7890 | 7919 | } |
| 7891 | 7920 | } |
| 7892 | 7921 | |
| 7922 | #ifdef DEBUG_ENABLED |
| 7923 | if (!dbg_was_invalid) { |
| 7924 | _dbg_freed_ids.insert(p_id.get_id()); |
| 7925 | } |
| 7926 | #endif |
| 7927 | |
| 7893 | 7928 | frames_pending_resources_for_processing = uint32_t(frames.size()); |
| 7894 | 7929 | } |
| 7895 | 7930 | |