Skip to content

Add GCHandle double-free detection via asserts and sentinel checks#125291

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/investigate-double-free-issue
Draft

Add GCHandle double-free detection via asserts and sentinel checks#125291
Copilot wants to merge 2 commits intomainfrom
copilot/investigate-double-free-issue

Conversation

Copy link
Contributor

Copilot AI commented Mar 7, 2026

Description

Investigating a rarely-reproducible assert in issue #117138, suspected to be a double-free of an OBJECTHANDLE/GCHandle in native VM code. Adds debug-build instrumentation to catch the violation on CI before it manifests as a hard-to-reproduce crash.

HndDestroyHandle (handletable.cpp)

The handle table already writes DEBUG_DestroyedHandleValue (0x7) into a slot when it is freed (debug builds only). Added an assert at the top of HndDestroyHandle that fires if the slot already holds that sentinel — meaning the handle was freed a second time before being reallocated:

#ifdef DEBUG_DestroyedHandleValue
    // Detect double-free: if the handle's referent is already the destroyed sentinel value,
    // this handle was freed before and not yet reallocated for a new purpose.
    _ASSERTE("Attempt to double-free a GC handle." && *(_UNCHECKED_OBJECTREF *)handle != DEBUG_DestroyedHandleValue);
#endif

~CEEInfo() (jitinterface.h)

Added a #ifdef _DEBUG O(n²) scan over m_pJitHandles before the free loop to detect duplicate entries — a duplicate means the same OBJECTHANDLE would be freed twice. Also defensively nulls m_pJitHandles and m_transientDetails after deletion.

#ifdef _DEBUG
    // Slow O(n²) check for duplicate handles, which would lead to a double-free.
    for (unsigned i = 0; i < count; i++)
        for (unsigned j = i + 1; j < count; j++)
            _ASSERTE("Duplicate handle in m_pJitHandles detected - this would cause a double-free." && elements[i] != elements[j]);
#endif

Both checks are zero-cost in release builds. The assert message convention (_ASSERTE("message" && condition)) matches the existing style in the same files (e.g., handletable.h, gchandleutilities.cpp).

Changes

  • Adds tests
  • Requires documentation update

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

- In HndDestroyHandle (handletable.cpp): assert that the handle's
  referent slot does not already contain the DEBUG_DestroyedHandleValue
  sentinel (0x7) when freeing. If it does, the handle was freed before
  without being reallocated, indicating a double-free.

- In ~CEEInfo() (jitinterface.h): add a debug-only O(n²) loop to check
  for duplicate entries in m_pJitHandles before freeing. A duplicate
  entry means the same handle would be freed twice. Also null out
  m_pJitHandles and m_transientDetails after deletion for defensive
  robustness.

Helps diagnose #117138.

Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com>
Copilot AI changed the title [WIP] Add assertions and sentinel values to catch double-free error Add GCHandle double-free detection via asserts and sentinel checks Mar 7, 2026
@EgorBo
Copy link
Member

EgorBo commented Mar 7, 2026

/azp run runtime-coreclr gcstress-extra

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants